def _createTray(self): from PyQt5.QtWidgets import QSystemTrayIcon from PyQt5.QtGui import QIcon from piony.common.system import expand_pj tray = QSystemTrayIcon() tray.setIcon(QIcon(expand_pj(":/res/tray-normal.png"))) tray.show() return tray
def __init__(self, manager: Manager, icon, parent=None, testing=False) -> None: QSystemTrayIcon.__init__(self, icon, parent) self.setToolTip("ActivityWatch" + (" (testing)" if testing else "")) self.manager = manager self.testing = testing self._build_rootmenu()
def __init__(self, icon, parent=None): QSystemTrayIcon.__init__(self, icon, parent) self.parent = parent self.timer = QtCore.QTimer(self) self.timer.timeout.connect(self.update) self.timer.setSingleShot(True) self.update()
def create_tray_icon(self): if QSystemTrayIcon.isSystemTrayAvailable(): logger.debug('System tray icon is available, showing') self.tray_icon = QSystemTrayIcon(QIcon(':/i/xnova_logo_32.png'), self) self.tray_icon.setToolTip(self.tr('XNova Commander')) self.tray_icon.activated.connect(self.on_tray_icon_activated) self.tray_icon.show() else: self.tray_icon = None
def __init__(self, icon, com, parent=None): QSystemTrayIcon.__init__(self, icon, parent) menu = QMenu(parent) showAction = menu.addAction("Mostrar") showAction.triggered.connect(self.show_action) exitAction = menu.addAction("Fechar") exitAction.triggered.connect(self.close_event) self.activated.connect(self.tray_activated) self.setContextMenu(menu) self.com = com self.show()
def __init__(self, parent=None): QSystemTrayIcon.__init__(self, parent=None) # Initial state self.set_disabled() # Right Click right_menu = RightClicked() self.setContextMenu(right_menu) # left click self.activated.connect(self.toggle)
def start(): # Start logging all events if '--drop' in sys.argv: drop_db() utils.start_app_logging() if sys.platform == 'win32': from server import SingleInstance single_app = SingleInstance() if single_app.already_running(): running_warning() app = QApplication(sys.argv) if not QSystemTrayIcon.isSystemTrayAvailable(): QMessageBox.critical( None, "Systray", "Could not detect a system tray on this system" ) sys.exit(1) QApplication.setQuitOnLastWindowClosed(False) osf = OSFApp() osf.start() osf.hide() sys.exit(app.exec_())
def __init__(self, *args): super(QApplication, self).__init__(*args) # Tray Icon self._system_tray = QSystemTrayIcon() self._system_tray.setIcon(QIcon('ui/icon.png')) self._system_tray.setToolTip("Parse99") self._system_tray.show() # Settings self.settings = settings.Settings("parse99") # Plugins self._plugins = {'maps': Maps(self.settings)} # Timer self._timer = QTimer() self._timer.timeout.connect(self._parse) # Thread self._thread = None # Menu self._system_tray.setContextMenu(self._get_menu()) # File self._log_file = "" self._file_size = 0 self._last_line_read = 0 self._log_new_lines = [] # Start self.toggle('on')
def __init__(self, icon,parent=None): QSystemTrayIcon.__init__(self,icon, parent) menu = QMenu(parent) showAction = menu.addAction("Show Moodly") showAction.triggered.connect(self.trigger.emit) exitAction = menu.addAction("Exit") exitAction.triggered.connect(self.qtrigger.emit) self.updateAction = menu.addAction("Update Now") self.updateAction.triggered.connect(self.uptrigger.emit) self.updateAction.setEnabled(False) self.activated.connect(self.activateIcon) self.setContextMenu(menu)
def start_qt_app(config): import sys from PyQt5.QtWidgets import QApplication, QSystemTrayIcon, QMessageBox from quamash import QEventLoop from ui.main import Window from ui.qt_gui_connection import qSignal app = QApplication(sys.argv) loop = QEventLoop(app) asyncio.set_event_loop(loop) if not QSystemTrayIcon.isSystemTrayAvailable(): QMessageBox.critical(None, "Systray", "I couldn't detect any system tray on this system.") sys.exit(1) QApplication.setQuitOnLastWindowClosed(False) gui_connection = qSignal() window = Window(gui_connection) def closeApp(): print("Close app signal") for task in asyncio.Task.all_tasks(): print(task) task.cancel() loop.stop() gui_connection.closeApp.connect(closeApp) with loop: #asyncio.run_coroutine_threadsafe(timer(loop, config, gui_connection), loop) try: loop.run_until_complete(timer(loop, config, gui_connection)) except asyncio.CancelledError: pass
def __init__(self): super().__init__() self.running = False self.setWindowTitle('PySwicher v{}'.format(VERSION)) # Logging config self.log_textbox = QPlainTextEditLogger(self) logging.getLogger().addHandler(self.log_textbox) self.log_textbox.setFormatter(logging.Formatter('[%(asctime)s][%(levelname)s]: %(message)s')) self.log_textbox.setLevel(self.get_numeric_loglevel(options['log_level'])) self.log_to_file = False # System tray configuration self.tray_menu = QMenu(self) self.systemTrayIcon = QSystemTrayIcon() self.systemTrayIcon.setVisible(False) self.systemTrayIcon.setIcon(QtGui.QIcon('C:\\Users\\Admin\\Pictures\\tray_stop.jpg')) self.systemTrayIcon.activated.connect(self.sys_tray) self.exit_action = self.tray_menu.addAction('Exit') self.exit_action.triggered.connect(self.exit_app) self.systemTrayIcon.setContextMenu(self.tray_menu) self.click_tray_timer = QtCore.QTimer(self) # Fix for systemtray click trigger self.click_tray_timer.setSingleShot(True) self.click_tray_timer.timeout.connect(self.click_timeout) self.main_window_ui() self.starter()
def __init__(self): self._app = QApplication([sys.argv[0]]) from dbus.mainloop.pyqt5 import DBusQtMainLoop super(Magneto, self).__init__(main_loop_class = DBusQtMainLoop) self._window = QSystemTrayIcon(self._app) icon_name = self.icons.get("okay") self._window.setIcon(QIcon.fromTheme(icon_name)) self._window.activated.connect(self._applet_activated) self._menu = QMenu(_("Magneto Entropy Updates Applet")) self._window.setContextMenu(self._menu) self._menu_items = {} for item in self._menu_item_list: if item is None: self._menu.addSeparator() continue myid, _unused, mytxt, myslot_func = item name = self.get_menu_image(myid) action_icon = QIcon.fromTheme(name) w = QAction(action_icon, mytxt, self._window, triggered=myslot_func) self._menu_items[myid] = w self._menu.addAction(w) self._menu.hide()
def __init__(self, parent=None): QMainWindow.__init__(self, parent) self.ui = Ui_MainWindow() self.ui.setupUi(self) self.connect_handlers() # Sub Views self.server_form = ServerForm() self.server_form.set_main_window(self) self.server_form.hide() # Initiate empty list self.data = [] self.notifications = [] # Refresh list self.refresh_list() # Set icons icon = QtGui.QIcon(os.path.join(os.path.dirname(__file__), "icon.png")) self.setWindowIcon(icon) self.msgIcon = QSystemTrayIcon.MessageIcon(QSystemTrayIcon.Information) self.systray_icon = QSystemTrayIcon(icon) self.systray_icon.activated.connect(self.toggle_window) self.systray_icon.show()
def __init__(self, *args): super(DemoImpl, self).__init__(*args) loadUi('dict2.ui',self) self.setLayout(self.verticalLayout) self.plainTextEdit.setReadOnly(True) self.setWindowFlags(self.windowFlags() | Qt.WindowSystemMenuHint | Qt.WindowMinMaxButtonsHint) self.trayicon = QSystemTrayIcon() self.traymenu = QMenu() self.quitAction = QAction('GQuit', self) self.quitAction.triggered.connect(self.close) self.quitAction.setShortcut(QKeySequence('Ctrl+q')) self.addAction(self.quitAction) self.traymenu.addAction('&Normal', self.showNormal, QKeySequence('Ctrl+n')) self.traymenu.addAction('Mi&nimize', self.showMinimized, QKeySequence('Ctrl+i')) self.traymenu.addAction('&Maximum', self.showMaximized, QKeySequence('Ctrl+m')) self.traymenu.addAction('&Quit',self.close, QKeySequence('Ctrl+q')) self.trayicon.setContextMenu(self.traymenu) self.ticon = QIcon('icon_dict2.ico') self.trayicon.setIcon(self.ticon) self.trayicon.setToolTip('YYDict') self.trayicon.activated.connect(self.on_systemTrayIcon_activated) self.traymsg_firstshow = True self.button1.clicked.connect(self.searchword) self.comboBox.activated.connect(self.searchword)
def run(manager, testing=False): logger.info("Creating trayicon...") # print(QIcon.themeSearchPaths()) app = QApplication(sys.argv) # Without this, Ctrl+C will have no effect signal.signal(signal.SIGINT, exit) # Ensure cleanup happens on SIGTERM signal.signal(signal.SIGTERM, exit) timer = QtCore.QTimer() timer.start(100) # You may change this if you wish. timer.timeout.connect(lambda: None) # Let the interpreter run each 500 ms. if not QSystemTrayIcon.isSystemTrayAvailable(): QMessageBox.critical(None, "Systray", "I couldn't detect any system tray on this system. Either get one or run the ActivityWatch modules from the console.") sys.exit(1) widget = QWidget() icon = QIcon(":/logo.png") trayIcon = TrayIcon(manager, icon, widget, testing=testing) trayIcon.show() trayIcon.showMessage("ActivityWatch", "ActivityWatch is starting up...") QApplication.setQuitOnLastWindowClosed(False) # Run the application, blocks until quit return app.exec_()
def update_tray_icon(self, use_monochrome_icon): if not QSystemTrayIcon.isSystemTrayAvailable() or not self.tray_icon: return if use_monochrome_icon: self.tray_icon.setIcon(QIcon(QPixmap(get_image_path('monochrome_tribler.png')))) else: self.tray_icon.setIcon(QIcon(QPixmap(get_image_path('tribler.png')))) self.tray_icon.show()
def __init__(self, parent=None): super(SystemTray, self).__init__(parent) self.tray_icon_menu = QMenu(self) self.tray_icon = QSystemTrayIcon(self) self.tray_icon.setContextMenu(self.tray_icon_menu) self.icon_management = IconManagement(self.tray_icon) self.connection_handler = ConnectionHandler(FREQUENCY_CHECK_MS, TIME_OUT_CALL_S, self) self.connection_handler.value_changed.connect(self.internet_connection) self.connection_handler.start()
def createTrayIcon(self): self.quitAction = QAction('Quit', self) self.quitAction.triggered.connect(self.closeEvent) self.trayIconMenu = QMenu(self) self.trayIconMenu.addAction(self.quitAction) self.trayIcon = QSystemTrayIcon(self) self.trayIcon.setContextMenu(self.trayIconMenu) self.trayIcon.setIcon(QIcon(os.path.join('images', 'flower.png'))) self.trayIcon.show()
def createTrayIcon(self): self.trayIconMenu = QMenu(self) self.trayIconMenu.addAction(self.minimizeAction) self.trayIconMenu.addAction(self.maximizeAction) self.trayIconMenu.addAction(self.restoreAction) self.trayIconMenu.addSeparator() self.trayIconMenu.addAction(self.quitAction) self.trayIcon = QSystemTrayIcon(self) self.trayIcon.setContextMenu(self.trayIconMenu)
def __init__(self, parent=None): super(MainApp, self).__init__(parent) # Init notification notify2.init('webcam-manager') # Init tray icon self.tray_icon = QSystemTrayIcon(QIcon.fromTheme('camera-web'), parent) self.initTrayIcon() self.tray_icon.show()
def createTrayIcon(self): self.trayIconMenu = QMenu(self) self.trayIconMenu.addAction(self.aboutShow) self.trayIconMenu.addAction(self.detailsShow) self.trayIconMenu.addAction(self.checkNow) self.trayIconMenu.addAction(self.restoreAction) self.trayIconMenu.addAction(self.quitAction) self.trayIcon = QSystemTrayIcon(self) self.trayIcon.setContextMenu(self.trayIconMenu) self.trayIcon.activated.connect(self.trayIconActivated)
def setupTrayIcon(self): _icon = QIcon(os.path.join('web', 'static', 'img', 'Logo.144x144.png')) self.trayIcon = QSystemTrayIcon(_icon, self) menu = QMenu(self) menu.addAction("Show", self.show) menu.addAction("Hide", self.hide) menu.addAction("Exit", self.reallyClose) self.trayIcon.setContextMenu(menu) self.trayIcon.show() self.trayIcon.showMessage("Antix Printer Server", "Is running")
def setupStatusIcon(self): icon = QIcon() icon.addPixmap(QPixmap(":/clock.svg"), QIcon.Normal, QIcon.Off) self.statusIcon = QSystemTrayIcon(self) self.statusIcon.setIcon(icon) self.statusIcon.activated.connect(lambda: self.hide() if self.isVisible() else self.show()) self.statusIcon.setToolTip("Mark's Time Tracker") self.statusIcon.show()
def inittray(self): menu = QMenu() menu.addAction("Add tile", self.mAddTile) menu.addAction("Add app", self.mAddDE) self.lmaction = menu.addAction("[ ] Layout mode", self.mLayoutMode) menu.addAction("Close", self.mClose) self.tray = QSystemTrayIcon(QIcon('icons/maintile.png')) self.tray.show() self.tray.setContextMenu(menu) self.tray.activated.connect(self.trayclicked) self.tray.show()
def createTrayIcon(self): self.trayIconMenu = QMenu(self) self.trayIconMenu.addAction(self.openAction) self.trayIconMenu.addAction(self.settingsAction) self.trayIconMenu.addSeparator() self.trayIconMenu.addAction(self.takeBreakAction) self.trayIconMenu.addAction(self.pauseAction) self.trayIconMenu.addSeparator() self.trayIconMenu.addAction(self.quitAction) self.trayIcon = QSystemTrayIcon(self) self.trayIcon.setContextMenu(self.trayIconMenu)
def gui(self): self.systray = QSystemTrayIcon() self.icon = QIcon(':/sansimera.png') self.systray.setIcon(self.icon) self.systray.setToolTip('Σαν σήμερα...') self.menu = QMenu() self.exitAction = QAction('&Έξοδος', self) self.refreshAction = QAction('&Ανανέωση', self) self.aboutAction = QAction('&Σχετικά', self) self.notification_interval = QAction('Ει&δοποίηση εορταζόντων', self) self.menu.addAction(self.notification_interval) self.menu.addAction(self.refreshAction) self.menu.addAction(self.aboutAction) self.menu.addAction(self.exitAction) self.systray.setContextMenu(self.menu) self.notification_interval.triggered.connect(self.interval_namedays) self.exitAction.triggered.connect(exit) self.refreshAction.triggered.connect(self.refresh) self.aboutAction.triggered.connect(self.about) self.browser = QTextBrowser() self.browser.setOpenExternalLinks(True) self.setGeometry(600, 500, 400, 300) self.setWindowIcon(self.icon) self.setWindowTitle('Σαν σήμερα...') self.setCentralWidget(self.browser) self.systray.show() self.systray.activated.connect(self.activate) self.browser.append('Λήψη...') nicon = QIcon(':/next') picon = QIcon(':/previous') ricon = QIcon(':/refresh') iicon = QIcon(':/info') qicon = QIcon(':/exit') inicon = QIcon(':/notifications') self.nextAction = QAction('Επόμενο', self) self.nextAction.setIcon(nicon) self.previousAction = QAction('Προηγούμενο', self) self.refreshAction.triggered.connect(self.refresh) self.nextAction.triggered.connect(self.nextItem) self.previousAction.triggered.connect(self.previousItem) self.previousAction.setIcon(picon) self.refreshAction.setIcon(ricon) self.exitAction.setIcon(qicon) self.aboutAction.setIcon(iicon) self.notification_interval.setIcon(inicon) controls = QToolBar() self.addToolBar(Qt.BottomToolBarArea, controls) controls.setObjectName('Controls') controls.addAction(self.previousAction) controls.addAction(self.nextAction) controls.addAction(self.refreshAction) self.restoreState(self.settings.value("MainWindow/State", QByteArray())) self.refresh()
class Systray(QObject): trayIconMenu = None def __init__(self, parent): super().__init__(parent) self.trayIconMenu = ContextMenu(None) icon = QIcon(":/image/thunder.ico") self.trayIcon = QSystemTrayIcon(self) self.trayIcon.setIcon(icon) self.trayIcon.setContextMenu(self.trayIconMenu) self.trayIcon.setVisible(True) self.trayIcon.activated.connect(self.slotSystrayActivated) @pyqtSlot(QSystemTrayIcon.ActivationReason) def slotSystrayActivated(self, reason): if reason == QSystemTrayIcon.Context: # right pass elif reason == QSystemTrayIcon.MiddleClick: # middle pass elif reason == QSystemTrayIcon.DoubleClick: # double click pass elif reason == QSystemTrayIcon.Trigger: # left if app.mainWin.isHidden() or app.mainWin.isMinimized(): app.mainWin.restore() else: app.mainWin.minimize()
class Icon(QWidget): def __init__(self, app, newEntry, listWin): super().__init__() self.newEntry = newEntry self.listWin = listWin self.app = app self.initUI() def initUI(self): menu = QMenu() Ajouter = QAction(QIcon(''), '&Ajouter un tag', menu) Ajouter.triggered.connect(self.newEntry.show) menu.addAction(Ajouter) ouvrir = QAction(QIcon(''), '&Ouvrir', menu) ouvrir.triggered.connect(self.listWin.show) menu.addAction(ouvrir) Quitter = QAction(QIcon(''), '&Quitter', menu) Quitter.triggered.connect(self.app.exit) menu.addAction(Quitter) self.icon = QSystemTrayIcon() self.icon.setIcon(QIcon('./icone.png')) self.icon.setContextMenu(menu) self.icon.show()
def setup_tray(self, isolated): Lumberjack.info('< MainApp > - -> (setup_tray)') self.trayIcon = QSystemTrayIcon(QIcon(':/app_icons/rc/PAT_icon.png'), self) self.trayMenu = QMenu(self) showAction = self.trayMenu.addAction("Open PAT") self.trayMenu.addSeparator() exitAction = self.trayMenu.addAction("Exit") self.trayIcon.setContextMenu(self.trayMenu) self.trayMenu.triggered.connect(self.handle_tray_event) self.trayIcon.activated.connect(self.handle_tray_event) self.trayIcon.show() if isolated: self.trayIcon.showMessage('PAT Service', 'PAT service is now running..')
def enable(self): if not self._supported: return self._trayicon = QSystemTrayIcon() # On OS X, the context menu is activated with either mouse buttons, # and activation messages are still sent, so ignore those... if not sys.platform.startswith('darwin'): self._trayicon.activated.connect(self._on_activated) if self._context_menu is not None: self._trayicon.setContextMenu(self._context_menu) self._enabled = True self._update_state() self._trayicon.show()
class Reminder(ShowCenterMixin, OsxMenuMixin, TimerMixin): def __init__(self): self.app = QApplication(sys.argv) self.app.setQuitOnLastWindowClosed(False) self.app.setApplicationName(SPINBOX_WINDOW_TITLE) self.screen_height = self.app.primaryScreen().geometry().height() styleSheet = open(APP_STYLESHEET).read() if sys.platform == 'darwin': c = self.app.palette().color(QPalette.Highlight) color = 'rgba({},{},{},10%)'.format(c.red(), c.green(), c.blue()) styleSheet = styleSheet.replace('palette(highlight)', color) self.app.setStyleSheet(styleSheet) self.setup_icon() self.setup_menu() self.setup_popup() self.setup_window() self.init_saved_alarm() self.idle() self.run() def on_button_clicked(self, *args): self.window.hide() h = self.spins[0].value() m = self.spins[1].value() s = self.spins[2].value() self.start_timer(h, m, s) def setup_window(self): self.window = QWidget() self.window.setProperty('objectName', 'window') self.window.setWindowTitle(SPINBOX_WINDOW_TITLE) self.window.setWindowFlags(self.window.windowFlags() | Qt.WindowStaysOnTopHint | Qt.Dialog) vlayout = QVBoxLayout(self.window) hlayout = QHBoxLayout() hlayout.setSpacing(SPINBOX_WINDOW_SPACING) self.spins = [] for label in ['h', 'm', 's']: button_spin = ButtonSpinBox(label=label) spin = button_spin.spin self.spins.append(spin) hlayout.addLayout(button_spin) button = QPushButton(BUTTON_LABEL) button.setProperty('objectName', 'start') vlayout.addLayout(hlayout) vlayout.addWidget(button) button.clicked.connect(self.on_button_clicked) self.window.closeEvent = self.on_window_close def on_window_close(self, *args): self.window.hide() def setup_icon(self): self.icon_normal = QIcon(ALARM_PATH) self.icon = QSystemTrayIcon(self.icon_normal) self.icon_urgent = QIcon(ALARM_URGENT_PATH) self.icon_active = QIcon(ALARM_ACTIVE_PATH) self.icon.activated.connect(self.activate_menu) def setup_menu(self): self.menu = QMenu() clock_item = QWidgetAction(self.menu) self.clock = QPushButton(' ') self.clock.setProperty('objectName', 'menu') font = self.clock.font() font.setPixelSize(CLOCK_FONT_SIZE) self.clock.setFont(font) clock_item.setDefaultWidget(self.clock) self.clock.clicked.connect(self.activate_window) exit_item = QWidgetAction(self.menu) label = QPushButton(EXIT_LABEL) label.setProperty('objectName', 'menu') exit_item.setDefaultWidget(label) label.clicked.connect(self.activate_exit) self.menu.addAction(clock_item) self.menu.addAction(exit_item) if sys.platform == 'darwin': clock_item.button = self.clock exit_item.button = label self.menu.actions = [clock_item, exit_item] self.set_up_menu_macos(self.menu) def setup_popup(self): self.popup = QWidget() self.popup.setProperty('objectName', 'popup') vlayout = QVBoxLayout(self.popup) label = QLabel(ALERT_TEXT) vlayout.addWidget(label) self.popup.setWindowFlags(Qt.Sheet | Qt.Popup | Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint) self.popup.mouseReleaseEvent = self.on_popup_release def on_popup_release(self, *args): self.popup.hide() def activate_menu(self, reason): if reason != QSystemTrayIcon.Trigger: return if self.icon.icon().cacheKey() == self.icon_urgent.cacheKey(): self.clear_alarm() self.set_icon(self.icon_normal) self.update_clock() if sys.platform == 'darwin': self.on_menu_activated_macos() self.icon.setContextMenu(self.menu) self.icon.setContextMenu(None) return icon_pos = self.icon.geometry().bottomRight() width = min(self.menu.geometry().width(), 135) if icon_pos.y() > self.screen_height / 2: pos = icon_pos - QPoint(width, 40) self.menu.popup(pos, self.menu.actions()[-1]) else: pos = icon_pos - QPoint(width, 0) self.menu.popup(pos) def activate_window(self, *args): if sys.platform == 'darwin': clock_action = self.menu.actions[0] clock_action.activate(clock_action.Trigger) self.show_center(self.window) def activate_exit(self, *args): os._exit(0) def set_icon(self, icon): self.icon.setIcon(icon) def update_clock_text(self, text): self.clock.setText(text) def show_popup(self): self.show_center(self.popup) def is_menu_visible(self): if sys.platform == 'darwin': return True return self.menu.isVisible() def run(self): timer = QTimer() timer.setInterval(1000) timer.timeout.connect(self.idle) timer.start(1000) self.icon.show() sys.exit(self.app.exec_())
def __init__(self, core_args=None, core_env=None): QMainWindow.__init__(self) QCoreApplication.setOrganizationDomain("nl") QCoreApplication.setOrganizationName("TUDelft") QCoreApplication.setApplicationName("Tribler") QCoreApplication.setAttribute(Qt.AA_UseHighDpiPixmaps) self.gui_settings = QSettings() api_port = int( get_gui_setting(self.gui_settings, "api_port", DEFAULT_API_PORT)) dispatcher.update_worker_settings(port=api_port) self.navigation_stack = [] self.tribler_started = False self.tribler_settings = None self.debug_window = None self.core_manager = CoreManager(api_port) self.pending_requests = {} self.pending_uri_requests = [] self.download_uri = None self.dialog = None self.new_version_dialog = None self.start_download_dialog_active = False self.request_mgr = None self.search_request_mgr = None self.search_suggestion_mgr = None self.selected_torrent_files = [] self.vlc_available = True self.has_search_results = False self.last_search_query = None self.last_search_time = None self.start_time = time.time() self.exception_handler_called = False self.token_refresh_timer = None sys.excepthook = self.on_exception uic.loadUi(get_ui_file_path('mainwindow.ui'), self) TriblerRequestManager.window = self self.tribler_status_bar.hide() # Load dynamic widgets uic.loadUi(get_ui_file_path('torrent_channel_list_container.ui'), self.channel_page_container) self.channel_torrents_list = self.channel_page_container.items_list self.channel_torrents_detail_widget = self.channel_page_container.details_tab_widget self.channel_torrents_detail_widget.initialize_details_widget() self.channel_torrents_list.itemClicked.connect( self.channel_page.clicked_item) uic.loadUi(get_ui_file_path('torrent_channel_list_container.ui'), self.search_page_container) self.search_results_list = self.search_page_container.items_list self.search_torrents_detail_widget = self.search_page_container.details_tab_widget self.search_torrents_detail_widget.initialize_details_widget() self.search_results_list.itemClicked.connect( self.on_channel_item_click) self.search_results_list.itemClicked.connect( self.search_results_page.clicked_item) self.token_balance_widget.mouseReleaseEvent = self.on_token_balance_click def on_state_update(new_state): self.loading_text_label.setText(new_state) self.core_manager.core_state_update.connect(on_state_update) self.magnet_handler = MagnetHandler(self.window) QDesktopServices.setUrlHandler("magnet", self.magnet_handler, "on_open_magnet_link") self.debug_pane_shortcut = QShortcut(QKeySequence("Ctrl+d"), self) self.debug_pane_shortcut.activated.connect( self.clicked_menu_button_debug) # Remove the focus rect on OS X for widget in self.findChildren(QLineEdit) + self.findChildren( QListWidget) + self.findChildren(QTreeWidget): widget.setAttribute(Qt.WA_MacShowFocusRect, 0) self.menu_buttons = [ self.left_menu_button_home, self.left_menu_button_search, self.left_menu_button_my_channel, self.left_menu_button_subscriptions, self.left_menu_button_video_player, self.left_menu_button_downloads, self.left_menu_button_discovered ] self.video_player_page.initialize_player() self.search_results_page.initialize_search_results_page() self.settings_page.initialize_settings_page() self.subscribed_channels_page.initialize() self.edit_channel_page.initialize_edit_channel_page() self.downloads_page.initialize_downloads_page() self.home_page.initialize_home_page() self.loading_page.initialize_loading_page() self.discovering_page.initialize_discovering_page() self.discovered_page.initialize_discovered_page() self.trust_page.initialize_trust_page() self.stackedWidget.setCurrentIndex(PAGE_LOADING) # Create the system tray icon if QSystemTrayIcon.isSystemTrayAvailable(): self.tray_icon = QSystemTrayIcon() use_monochrome_icon = get_gui_setting(self.gui_settings, "use_monochrome_icon", False, is_bool=True) self.update_tray_icon(use_monochrome_icon) # Create the tray icon menu menu = self.create_add_torrent_menu() show_downloads_action = QAction('Show downloads', self) show_downloads_action.triggered.connect( self.clicked_menu_button_downloads) token_balance_action = QAction('Show token balance', self) token_balance_action.triggered.connect( lambda: self.on_token_balance_click(None)) quit_action = QAction('Quit Tribler', self) quit_action.triggered.connect(self.close_tribler) menu.addSeparator() menu.addAction(show_downloads_action) menu.addAction(token_balance_action) menu.addSeparator() menu.addAction(quit_action) self.tray_icon.setContextMenu(menu) else: self.tray_icon = None self.hide_left_menu_playlist() self.left_menu_button_debug.setHidden(True) self.top_menu_button.setHidden(True) self.left_menu.setHidden(True) self.token_balance_widget.setHidden(True) self.settings_button.setHidden(True) self.add_torrent_button.setHidden(True) self.top_search_bar.setHidden(True) # Set various icons self.top_menu_button.setIcon(QIcon(get_image_path('menu.png'))) self.search_completion_model = QStringListModel() completer = QCompleter() completer.setModel(self.search_completion_model) completer.setCompletionMode(QCompleter.UnfilteredPopupCompletion) self.item_delegate = QStyledItemDelegate() completer.popup().setItemDelegate(self.item_delegate) completer.popup().setStyleSheet(""" QListView { background-color: #404040; } QListView::item { color: #D0D0D0; padding-top: 5px; padding-bottom: 5px; } QListView::item:hover { background-color: #707070; } """) self.top_search_bar.setCompleter(completer) # Toggle debug if developer mode is enabled self.window().left_menu_button_debug.setHidden(not get_gui_setting( self.gui_settings, "debug", False, is_bool=True)) # Start Tribler self.core_manager.start(core_args=core_args, core_env=core_env) self.core_manager.events_manager.received_search_result_channel.connect( self.search_results_page.received_search_result_channel) self.core_manager.events_manager.received_search_result_torrent.connect( self.search_results_page.received_search_result_torrent) self.core_manager.events_manager.torrent_finished.connect( self.on_torrent_finished) self.core_manager.events_manager.new_version_available.connect( self.on_new_version_available) self.core_manager.events_manager.tribler_started.connect( self.on_tribler_started) self.core_manager.events_manager.events_started.connect( self.on_events_started) self.core_manager.events_manager.low_storage_signal.connect( self.on_low_storage) # Install signal handler for ctrl+c events def sigint_handler(*_): self.close_tribler() signal.signal(signal.SIGINT, sigint_handler) self.installEventFilter(self.video_player_page) # Resize the window according to the settings center = QApplication.desktop().availableGeometry(self).center() pos = self.gui_settings.value( "pos", QPoint(center.x() - self.width() * 0.5, center.y() - self.height() * 0.5)) size = self.gui_settings.value("size", self.size()) self.move(pos) self.resize(size) self.show()
class TreeMainControl(QObject): """Class to handle all global controls. Provides methods for all controls and stores local control objects. """ def __init__(self, pathObjects, parent=None): """Initialize the main tree controls Arguments: pathObjects -- a list of file objects to open parent -- the parent QObject if given """ super().__init__(parent) self.localControls = [] self.activeControl = None self.trayIcon = None self.isTrayMinimized = False self.configDialog = None self.sortDialog = None self.numberingDialog = None self.findTextDialog = None self.findConditionDialog = None self.findReplaceDialog = None self.filterTextDialog = None self.filterConditionDialog = None self.basicHelpView = None self.passwords = {} self.creatingLocalControlFlag = False globalref.mainControl = self self.allActions = {} try: # check for existing TreeLine session socket = QLocalSocket() socket.connectToServer('treeline3-session', QIODevice.WriteOnly) # if found, send files to open and exit TreeLine if socket.waitForConnected(1000): socket.write( bytes(repr([str(path) for path in pathObjects]), 'utf-8')) if socket.waitForBytesWritten(1000): socket.close() sys.exit(0) # start local server to listen for attempt to start new session self.serverSocket = QLocalServer() # remove any old servers still around after a crash in linux self.serverSocket.removeServer('treeline3-session') self.serverSocket.listen('treeline3-session') self.serverSocket.newConnection.connect(self.getSocket) except AttributeError: print(_('Warning: Could not create local socket')) mainVersion = '.'.join(__version__.split('.')[:2]) globalref.genOptions = options.Options('general', 'TreeLine', mainVersion, 'bellz') optiondefaults.setGenOptionDefaults(globalref.genOptions) globalref.miscOptions = options.Options('misc') optiondefaults.setMiscOptionDefaults(globalref.miscOptions) globalref.histOptions = options.Options('history') optiondefaults.setHistOptionDefaults(globalref.histOptions) globalref.toolbarOptions = options.Options('toolbar') optiondefaults.setToolbarOptionDefaults(globalref.toolbarOptions) globalref.keyboardOptions = options.Options('keyboard') optiondefaults.setKeyboardOptionDefaults(globalref.keyboardOptions) try: globalref.genOptions.readFile() globalref.miscOptions.readFile() globalref.histOptions.readFile() globalref.toolbarOptions.readFile() globalref.keyboardOptions.readFile() except IOError: errorDir = options.Options.basePath if not errorDir: errorDir = _('missing directory') QMessageBox.warning( None, 'TreeLine', _('Error - could not write config file to {}').format( errorDir)) options.Options.basePath = None iconPathList = self.findResourcePaths('icons', iconPath) globalref.toolIcons = icondict.IconDict( [path / 'toolbar' for path in iconPathList], ['', '32x32', '16x16']) globalref.toolIcons.loadAllIcons() windowIcon = globalref.toolIcons.getIcon('treelogo') if windowIcon: QApplication.setWindowIcon(windowIcon) globalref.treeIcons = icondict.IconDict(iconPathList, ['', 'tree']) icon = globalref.treeIcons.getIcon('default') qApp.setStyle(QStyleFactory.create('Fusion')) self.colorSet = colorset.ColorSet() if globalref.miscOptions['ColorTheme'] != 'system': self.colorSet.setAppColors() self.recentFiles = recentfiles.RecentFileList() if globalref.genOptions['AutoFileOpen'] and not pathObjects: recentPath = self.recentFiles.firstPath() if recentPath: pathObjects = [recentPath] self.setupActions() self.systemFont = QApplication.font() self.updateAppFont() if globalref.genOptions['MinToSysTray']: self.createTrayIcon() qApp.focusChanged.connect(self.updateActionsAvail) if pathObjects: for pathObj in pathObjects: self.openFile(pathObj, True) else: self.createLocalControl() def getSocket(self): """Open a socket from an attempt to open a second Treeline instance. Opens the file (or raise and focus if open) in this instance. """ socket = self.serverSocket.nextPendingConnection() if socket and socket.waitForReadyRead(1000): data = str(socket.readAll(), 'utf-8') try: paths = ast.literal_eval(data) if paths: for path in paths: pathObj = pathlib.Path(path) if pathObj != self.activeControl.filePathObj: self.openFile(pathObj, True) else: self.activeControl.activeWindow.activateAndRaise() else: self.activeControl.activeWindow.activateAndRaise() except (SyntaxError, ValueError, TypeError, RuntimeError): pass def findResourcePaths(self, resourceName, preferredPath=''): """Return list of potential non-empty pathlib objects for the resource. List includes preferred, module and user option paths. Arguments: resourceName -- the typical name of the resource directory preferredPath -- add this as the second path if given """ # use abspath() - pathlib's resolve() can be buggy with network drives modPath = pathlib.Path(os.path.abspath(sys.path[0])) if modPath.is_file(): modPath = modPath.parent # for frozen binary pathList = [modPath / '..' / resourceName, modPath / resourceName] if options.Options.basePath: basePath = pathlib.Path(options.Options.basePath) pathList.insert(0, basePath / resourceName) if preferredPath: pathList.insert(1, pathlib.Path(preferredPath)) return [ pathlib.Path(os.path.abspath(str(path))) for path in pathList if path.is_dir() and list(path.iterdir()) ] def findResourceFile(self, fileName, resourceName, preferredPath=''): """Return a path object for a resource file. Add a language code before the extension if it exists. Arguments: fileName -- the name of the file to find resourceName -- the typical name of the resource directory preferredPath -- search this path first if given """ fileList = [fileName] if globalref.lang and globalref.lang != 'C': fileList[0:0] = [ fileName.replace('.', '_{0}.'.format(globalref.lang)), fileName.replace('.', '_{0}.'.format(globalref.lang[:2])) ] for fileName in fileList: for path in self.findResourcePaths(resourceName, preferredPath): if (path / fileName).is_file(): return path / fileName return None def defaultPathObj(self, dirOnly=False): """Return a reasonable default file path object. Used for open, save-as, import and export. Arguments: dirOnly -- if True, do not include basename of file """ pathObj = None if self.activeControl: pathObj = self.activeControl.filePathObj if not pathObj: pathObj = self.recentFiles.firstDir() if not pathObj: pathObj = pathlib.Path.home() if dirOnly: pathObj = pathObj.parent return pathObj def openFile(self, pathObj, forceNewWindow=False, checkModified=False, importOnFail=True): """Open the file given by path if not already open. If already open in a different window, focus and raise the window. Arguments: pathObj -- the path object to read forceNewWindow -- if True, use a new window regardless of option checkModified -- if True & not new win, prompt if file modified importOnFail -- if True, prompts for import on non-TreeLine files """ match = [ control for control in self.localControls if pathObj == control.filePathObj ] if match and self.activeControl not in match: control = match[0] control.activeWindow.activateAndRaise() self.updateLocalControlRef(control) return if checkModified and not (forceNewWindow or globalref.genOptions['OpenNewWindow'] or self.activeControl.checkSaveChanges()): return if not self.checkAutoSave(pathObj): if not self.localControls: self.createLocalControl() return QApplication.setOverrideCursor(Qt.WaitCursor) try: self.createLocalControl(pathObj, None, forceNewWindow) self.recentFiles.addItem(pathObj) if not (globalref.genOptions['SaveTreeStates'] and self.recentFiles.retrieveTreeState(self.activeControl)): self.activeControl.expandRootNodes() self.activeControl.selectRootSpot() QApplication.restoreOverrideCursor() except IOError: QApplication.restoreOverrideCursor() QMessageBox.warning( QApplication.activeWindow(), 'TreeLine', _('Error - could not read file {0}').format(pathObj)) self.recentFiles.removeItem(pathObj) except (ValueError, KeyError, TypeError): fileObj = pathObj.open('rb') fileObj, encrypted = self.decryptFile(fileObj) if not fileObj: if not self.localControls: self.createLocalControl() QApplication.restoreOverrideCursor() return fileObj, compressed = self.decompressFile(fileObj) if compressed or encrypted: try: textFileObj = io.TextIOWrapper(fileObj, encoding='utf-8') self.createLocalControl(textFileObj, None, forceNewWindow) fileObj.close() textFileObj.close() self.recentFiles.addItem(pathObj) if not (globalref.genOptions['SaveTreeStates'] and self.recentFiles.retrieveTreeState( self.activeControl)): self.activeControl.expandRootNodes() self.activeControl.selectRootSpot() self.activeControl.compressed = compressed self.activeControl.encrypted = encrypted QApplication.restoreOverrideCursor() return except (ValueError, KeyError, TypeError): pass fileObj.close() importControl = imports.ImportControl(pathObj) structure = importControl.importOldTreeLine() if structure: self.createLocalControl(pathObj, structure, forceNewWindow) self.activeControl.printData.readData( importControl.treeLineRootAttrib) self.recentFiles.addItem(pathObj) self.activeControl.expandRootNodes() self.activeControl.imported = True QApplication.restoreOverrideCursor() return QApplication.restoreOverrideCursor() if importOnFail: importControl = imports.ImportControl(pathObj) structure = importControl.interactiveImport(True) if structure: self.createLocalControl(pathObj, structure, forceNewWindow) self.activeControl.imported = True return else: QMessageBox.warning( QApplication.activeWindow(), 'TreeLine', _('Error - invalid TreeLine file {0}').format(pathObj)) self.recentFiles.removeItem(pathObj) if not self.localControls: self.createLocalControl() def decryptFile(self, fileObj): """Check for encryption and decrypt the fileObj if needed. Return a tuple of the file object and True if it was encrypted. Return None for the file object if the user cancels. Arguments: fileObj -- the file object to check and decrypt """ if fileObj.read(len(encryptPrefix)) != encryptPrefix: fileObj.seek(0) return (fileObj, False) fileContents = fileObj.read() fileName = fileObj.name fileObj.close() while True: pathObj = pathlib.Path(fileName) password = self.passwords.get(pathObj, '') if not password: QApplication.restoreOverrideCursor() dialog = miscdialogs.PasswordDialog( False, pathObj.name, QApplication.activeWindow()) if dialog.exec_() != QDialog.Accepted: return (None, True) QApplication.setOverrideCursor(Qt.WaitCursor) password = dialog.password if miscdialogs.PasswordDialog.remember: self.passwords[pathObj] = password try: text = p3.p3_decrypt(fileContents, password.encode()) fileIO = io.BytesIO(text) fileIO.name = fileName return (fileIO, True) except p3.CryptError: try: del self.passwords[pathObj] except KeyError: pass def decompressFile(self, fileObj): """Check for compression and decompress the fileObj if needed. Return a tuple of the file object and True if it was compressed. Arguments: fileObj -- the file object to check and decompress """ prefix = fileObj.read(2) fileObj.seek(0) if prefix != b'\037\213': return (fileObj, False) try: newFileObj = gzip.GzipFile(fileobj=fileObj) except zlib.error: return (fileObj, False) newFileObj.name = fileObj.name return (newFileObj, True) def checkAutoSave(self, pathObj): """Check for presence of auto save file & prompt user. Return True if OK to contimue, False if aborting or already loaded. Arguments: pathObj -- the base path object to search for a backup """ if not globalref.genOptions['AutoSaveMinutes']: return True basePath = pathObj pathObj = pathlib.Path(str(pathObj) + '~') if not pathObj.is_file(): return True msgBox = QMessageBox( QMessageBox.Information, 'TreeLine', _('Backup file "{}" exists.\nA previous ' 'session may have crashed').format(pathObj), QMessageBox.NoButton, QApplication.activeWindow()) restoreButton = msgBox.addButton(_('&Restore Backup'), QMessageBox.ApplyRole) deleteButton = msgBox.addButton(_('&Delete Backup'), QMessageBox.DestructiveRole) cancelButton = msgBox.addButton(_('&Cancel File Open'), QMessageBox.RejectRole) msgBox.exec_() if msgBox.clickedButton() == restoreButton: self.openFile(pathObj) if self.activeControl.filePathObj != pathObj: return False try: basePath.unlink() pathObj.rename(basePath) except OSError: QMessageBox.warning( QApplication.activeWindow(), 'TreeLine', _('Error - could not rename "{0}" to "{1}"').format( pathObj, basePath)) return False self.activeControl.filePathObj = basePath self.activeControl.updateWindowCaptions() self.recentFiles.removeItem(pathObj) self.recentFiles.addItem(basePath) return False elif msgBox.clickedButton() == deleteButton: try: pathObj.unlink() except OSError: QMessageBox.warning( QApplication.activeWindow(), 'TreeLine', _('Error - could not remove backup file {}').format( pathObj)) else: # cancel button return False return True def createLocalControl(self, pathObj=None, treeStruct=None, forceNewWindow=False): """Create a new local control object and add it to the list. Use an imported structure if given or open the file if path is given. Arguments: pathObj -- the path object or file object for the control to open treeStruct -- the imported structure to use forceNewWindow -- if True, use a new window regardless of option """ self.creatingLocalControlFlag = True localControl = treelocalcontrol.TreeLocalControl( self.allActions, pathObj, treeStruct, forceNewWindow) localControl.controlActivated.connect(self.updateLocalControlRef) localControl.controlClosed.connect(self.removeLocalControlRef) self.localControls.append(localControl) self.updateLocalControlRef(localControl) self.creatingLocalControlFlag = False localControl.updateRightViews() localControl.updateCommandsAvail() def updateLocalControlRef(self, localControl): """Set the given local control as active. Called by signal from a window becoming active. Also updates non-modal dialogs. Arguments: localControl -- the new active local control """ if localControl != self.activeControl: self.activeControl = localControl if self.configDialog and self.configDialog.isVisible(): self.configDialog.setRefs(self.activeControl) def removeLocalControlRef(self, localControl): """Remove ref to local control based on a closing signal. Also do application exit clean ups if last control closing. Arguments: localControl -- the local control that is closing """ try: self.localControls.remove(localControl) except ValueError: return # skip for unreporducible bug - odd race condition? if globalref.genOptions['SaveTreeStates']: self.recentFiles.saveTreeState(localControl) if not self.localControls and not self.creatingLocalControlFlag: if globalref.genOptions['SaveWindowGeom']: localControl.windowList[0].saveWindowGeom() else: localControl.windowList[0].resetWindowGeom() self.recentFiles.writeItems() localControl.windowList[0].saveToolbarPosition() globalref.histOptions.writeFile() if self.trayIcon: self.trayIcon.hide() # stop listening for session connections try: self.serverSocket.close() del self.serverSocket except AttributeError: pass if self.localControls: # make sure a window is active (may not be focused), to avoid # bugs due to a deleted current window newControl = self.localControls[0] newControl.setActiveWin(newControl.windowList[0]) localControl.deleteLater() def createTrayIcon(self): """Create a new system tray icon if not already created. """ if QSystemTrayIcon.isSystemTrayAvailable: if not self.trayIcon: self.trayIcon = QSystemTrayIcon(qApp.windowIcon(), qApp) self.trayIcon.activated.connect(self.toggleTrayShow) self.trayIcon.show() def trayMinimize(self): """Minimize to tray based on window minimize signal. """ if self.trayIcon and QSystemTrayIcon.isSystemTrayAvailable: # skip minimize to tray if not all windows minimized for control in self.localControls: for window in control.windowList: if not window.isMinimized(): return for control in self.localControls: for window in control.windowList: window.hide() self.isTrayMinimized = True def toggleTrayShow(self): """Toggle show and hide application based on system tray icon click. """ if self.isTrayMinimized: for control in self.localControls: for window in control.windowList: window.show() window.showNormal() self.activeControl.activeWindow.treeView.setFocus() else: for control in self.localControls: for window in control.windowList: window.hide() self.isTrayMinimized = not self.isTrayMinimized def updateConfigDialog(self): """Update the config dialog for changes if it exists. """ if self.configDialog: self.configDialog.reset() def currentStatusBar(self): """Return the status bar from the current main window. """ return self.activeControl.activeWindow.statusBar() def windowActions(self): """Return a list of window menu actions from each local control. """ actions = [] for control in self.localControls: actions.extend( control.windowActions( len(actions) + 1, control == self.activeControl)) return actions def updateActionsAvail(self, oldWidget, newWidget): """Update command availability based on focus changes. Arguments: oldWidget -- the previously focused widget newWidget -- the newly focused widget """ self.allActions['FormatSelectAll'].setEnabled( hasattr(newWidget, 'selectAll') and not hasattr(newWidget, 'editTriggers')) def setupActions(self): """Add the actions for contols at the global level. """ fileNewAct = QAction(_('&New...'), self, toolTip=_('New File'), statusTip=_('Start a new file')) fileNewAct.triggered.connect(self.fileNew) self.allActions['FileNew'] = fileNewAct fileOpenAct = QAction(_('&Open...'), self, toolTip=_('Open File'), statusTip=_('Open a file from disk')) fileOpenAct.triggered.connect(self.fileOpen) self.allActions['FileOpen'] = fileOpenAct fileSampleAct = QAction(_('Open Sa&mple...'), self, toolTip=_('Open Sample'), statusTip=_('Open a sample file')) fileSampleAct.triggered.connect(self.fileOpenSample) self.allActions['FileOpenSample'] = fileSampleAct fileImportAct = QAction(_('&Import...'), self, statusTip=_('Open a non-TreeLine file')) fileImportAct.triggered.connect(self.fileImport) self.allActions['FileImport'] = fileImportAct fileQuitAct = QAction(_('&Quit'), self, statusTip=_('Exit the application')) fileQuitAct.triggered.connect(self.fileQuit) self.allActions['FileQuit'] = fileQuitAct dataConfigAct = QAction( _('&Configure Data Types...'), self, statusTip=_('Modify data types, fields & output lines'), checkable=True) dataConfigAct.triggered.connect(self.dataConfigDialog) self.allActions['DataConfigType'] = dataConfigAct dataVisualConfigAct = QAction( _('Show C&onfiguration Structure...'), self, statusTip=_('Show read-only visualization of type structure')) dataVisualConfigAct.triggered.connect(self.dataVisualConfig) self.allActions['DataVisualConfig'] = dataVisualConfigAct dataSortAct = QAction(_('Sor&t Nodes...'), self, statusTip=_('Define node sort operations'), checkable=True) dataSortAct.triggered.connect(self.dataSortDialog) self.allActions['DataSortNodes'] = dataSortAct dataNumberingAct = QAction(_('Update &Numbering...'), self, statusTip=_('Update node numbering fields'), checkable=True) dataNumberingAct.triggered.connect(self.dataNumberingDialog) self.allActions['DataNumbering'] = dataNumberingAct toolsFindTextAct = QAction( _('&Find Text...'), self, statusTip=_('Find text in node titles & data'), checkable=True) toolsFindTextAct.triggered.connect(self.toolsFindTextDialog) self.allActions['ToolsFindText'] = toolsFindTextAct toolsFindConditionAct = QAction( _('&Conditional Find...'), self, statusTip=_('Use field conditions to find nodes'), checkable=True) toolsFindConditionAct.triggered.connect(self.toolsFindConditionDialog) self.allActions['ToolsFindCondition'] = toolsFindConditionAct toolsFindReplaceAct = QAction( _('Find and &Replace...'), self, statusTip=_('Replace text strings in node data'), checkable=True) toolsFindReplaceAct.triggered.connect(self.toolsFindReplaceDialog) self.allActions['ToolsFindReplace'] = toolsFindReplaceAct toolsFilterTextAct = QAction( _('&Text Filter...'), self, statusTip=_('Filter nodes to only show text matches'), checkable=True) toolsFilterTextAct.triggered.connect(self.toolsFilterTextDialog) self.allActions['ToolsFilterText'] = toolsFilterTextAct toolsFilterConditionAct = QAction( _('C&onditional Filter...'), self, statusTip=_('Use field conditions to filter nodes'), checkable=True) toolsFilterConditionAct.triggered.connect( self.toolsFilterConditionDialog) self.allActions['ToolsFilterCondition'] = toolsFilterConditionAct toolsGenOptionsAct = QAction( _('&General Options...'), self, statusTip=_('Set user preferences for all files')) toolsGenOptionsAct.triggered.connect(self.toolsGenOptions) self.allActions['ToolsGenOptions'] = toolsGenOptionsAct toolsShortcutAct = QAction(_('Set &Keyboard Shortcuts...'), self, statusTip=_('Customize keyboard commands')) toolsShortcutAct.triggered.connect(self.toolsCustomShortcuts) self.allActions['ToolsShortcuts'] = toolsShortcutAct toolsToolbarAct = QAction(_('C&ustomize Toolbars...'), self, statusTip=_('Customize toolbar buttons')) toolsToolbarAct.triggered.connect(self.toolsCustomToolbars) self.allActions['ToolsToolbars'] = toolsToolbarAct toolsFontsAct = QAction( _('Customize Fo&nts...'), self, statusTip=_('Customize fonts in various views')) toolsFontsAct.triggered.connect(self.toolsCustomFonts) self.allActions['ToolsFonts'] = toolsFontsAct toolsColorsAct = QAction( _('Custo&mize Colors...'), self, statusTip=_('Customize GUI colors and themes')) toolsColorsAct.triggered.connect(self.toolsCustomColors) self.allActions['ToolsColors'] = toolsColorsAct formatSelectAllAct = QAction( _('&Select All'), self, statusTip=_('Select all text in an editor')) formatSelectAllAct.setEnabled(False) formatSelectAllAct.triggered.connect(self.formatSelectAll) self.allActions['FormatSelectAll'] = formatSelectAllAct helpBasicAct = QAction(_('&Basic Usage...'), self, statusTip=_('Display basic usage instructions')) helpBasicAct.triggered.connect(self.helpViewBasic) self.allActions['HelpBasic'] = helpBasicAct helpFullAct = QAction( _('&Full Documentation...'), self, statusTip=_('Open a TreeLine file with full documentation')) helpFullAct.triggered.connect(self.helpViewFull) self.allActions['HelpFull'] = helpFullAct helpAboutAct = QAction( _('&About TreeLine...'), self, statusTip=_('Display version info about this program')) helpAboutAct.triggered.connect(self.helpAbout) self.allActions['HelpAbout'] = helpAboutAct for name, action in self.allActions.items(): icon = globalref.toolIcons.getIcon(name.lower()) if icon: action.setIcon(icon) key = globalref.keyboardOptions[name] if not key.isEmpty(): action.setShortcut(key) def fileNew(self): """Start a new blank file. """ if (globalref.genOptions['OpenNewWindow'] or self.activeControl.checkSaveChanges()): searchPaths = self.findResourcePaths('templates', templatePath) if searchPaths: dialog = miscdialogs.TemplateFileDialog( _('New File'), _('&Select Template'), searchPaths) if dialog.exec_() == QDialog.Accepted: self.createLocalControl(dialog.selectedPath()) self.activeControl.filePathObj = None self.activeControl.updateWindowCaptions() self.activeControl.expandRootNodes() else: self.createLocalControl() self.activeControl.selectRootSpot() def fileOpen(self): """Prompt for a filename and open it. """ if (globalref.genOptions['OpenNewWindow'] or self.activeControl.checkSaveChanges()): filters = ';;'.join((globalref.fileFilters['trlnopen'], globalref.fileFilters['all'])) fileName, selFilter = QFileDialog.getOpenFileName( QApplication.activeWindow(), _('TreeLine - Open File'), str(self.defaultPathObj(True)), filters) if fileName: self.openFile(pathlib.Path(fileName)) def fileOpenSample(self): """Open a sample file from the doc directories. """ if (globalref.genOptions['OpenNewWindow'] or self.activeControl.checkSaveChanges()): searchPaths = self.findResourcePaths('samples', samplePath) dialog = miscdialogs.TemplateFileDialog(_('Open Sample File'), _('&Select Sample'), searchPaths, False) if dialog.exec_() == QDialog.Accepted: self.createLocalControl(dialog.selectedPath()) name = dialog.selectedName() + '.trln' self.activeControl.filePathObj = pathlib.Path(name) self.activeControl.updateWindowCaptions() self.activeControl.expandRootNodes() self.activeControl.imported = True def fileImport(self): """Prompt for an import type, then a file to import. """ importControl = imports.ImportControl() structure = importControl.interactiveImport() if structure: self.createLocalControl(importControl.pathObj, structure) if importControl.treeLineRootAttrib: self.activeControl.printData.readData( importControl.treeLineRootAttrib) self.activeControl.imported = True def fileQuit(self): """Close all windows to exit the applications. """ for control in self.localControls[:]: control.closeWindows() def dataConfigDialog(self, show): """Show or hide the non-modal data config dialog. Arguments: show -- true if dialog should be shown, false to hide it """ if show: if not self.configDialog: self.configDialog = configdialog.ConfigDialog() dataConfigAct = self.allActions['DataConfigType'] self.configDialog.dialogShown.connect(dataConfigAct.setChecked) self.configDialog.setRefs(self.activeControl, True) self.configDialog.show() else: self.configDialog.close() def dataVisualConfig(self): """Show a TreeLine file to visualize the config structure. """ structure = ( self.activeControl.structure.treeFormats.visualConfigStructure( str(self.activeControl.filePathObj))) self.createLocalControl(treeStruct=structure, forceNewWindow=True) self.activeControl.filePathObj = pathlib.Path('structure.trln') self.activeControl.updateWindowCaptions() self.activeControl.expandRootNodes() self.activeControl.imported = True win = self.activeControl.activeWindow win.rightTabs.setCurrentWidget(win.outputSplitter) def dataSortDialog(self, show): """Show or hide the non-modal data sort nodes dialog. Arguments: show -- true if dialog should be shown, false to hide it """ if show: if not self.sortDialog: self.sortDialog = miscdialogs.SortDialog() dataSortAct = self.allActions['DataSortNodes'] self.sortDialog.dialogShown.connect(dataSortAct.setChecked) self.sortDialog.show() else: self.sortDialog.close() def dataNumberingDialog(self, show): """Show or hide the non-modal update node numbering dialog. Arguments: show -- true if dialog should be shown, false to hide it """ if show: if not self.numberingDialog: self.numberingDialog = miscdialogs.NumberingDialog() dataNumberingAct = self.allActions['DataNumbering'] self.numberingDialog.dialogShown.connect( dataNumberingAct.setChecked) self.numberingDialog.show() if not self.numberingDialog.checkForNumberingFields(): self.numberingDialog.close() else: self.numberingDialog.close() def toolsFindTextDialog(self, show): """Show or hide the non-modal find text dialog. Arguments: show -- true if dialog should be shown """ if show: if not self.findTextDialog: self.findTextDialog = miscdialogs.FindFilterDialog() toolsFindTextAct = self.allActions['ToolsFindText'] self.findTextDialog.dialogShown.connect( toolsFindTextAct.setChecked) self.findTextDialog.selectAllText() self.findTextDialog.show() else: self.findTextDialog.close() def toolsFindConditionDialog(self, show): """Show or hide the non-modal conditional find dialog. Arguments: show -- true if dialog should be shown """ if show: if not self.findConditionDialog: dialogType = conditional.FindDialogType.findDialog self.findConditionDialog = (conditional.ConditionDialog( dialogType, _('Conditional Find'))) toolsFindConditionAct = self.allActions['ToolsFindCondition'] (self.findConditionDialog.dialogShown.connect( toolsFindConditionAct.setChecked)) else: self.findConditionDialog.loadTypeNames() self.findConditionDialog.show() else: self.findConditionDialog.close() def toolsFindReplaceDialog(self, show): """Show or hide the non-modal find and replace text dialog. Arguments: show -- true if dialog should be shown """ if show: if not self.findReplaceDialog: self.findReplaceDialog = miscdialogs.FindReplaceDialog() toolsFindReplaceAct = self.allActions['ToolsFindReplace'] self.findReplaceDialog.dialogShown.connect( toolsFindReplaceAct.setChecked) else: self.findReplaceDialog.loadTypeNames() self.findReplaceDialog.show() else: self.findReplaceDialog.close() def toolsFilterTextDialog(self, show): """Show or hide the non-modal filter text dialog. Arguments: show -- true if dialog should be shown """ if show: if not self.filterTextDialog: self.filterTextDialog = miscdialogs.FindFilterDialog(True) toolsFilterTextAct = self.allActions['ToolsFilterText'] self.filterTextDialog.dialogShown.connect( toolsFilterTextAct.setChecked) self.filterTextDialog.selectAllText() self.filterTextDialog.show() else: self.filterTextDialog.close() def toolsFilterConditionDialog(self, show): """Show or hide the non-modal conditional filter dialog. Arguments: show -- true if dialog should be shown """ if show: if not self.filterConditionDialog: dialogType = conditional.FindDialogType.filterDialog self.filterConditionDialog = (conditional.ConditionDialog( dialogType, _('Conditional Filter'))) toolsFilterConditionAct = ( self.allActions['ToolsFilterCondition']) (self.filterConditionDialog.dialogShown.connect( toolsFilterConditionAct.setChecked)) else: self.filterConditionDialog.loadTypeNames() self.filterConditionDialog.show() else: self.filterConditionDialog.close() def toolsGenOptions(self): """Set general user preferences for all files. """ oldAutoSaveMinutes = globalref.genOptions['AutoSaveMinutes'] dialog = options.OptionDialog(globalref.genOptions, QApplication.activeWindow()) dialog.setWindowTitle(_('General Options')) if (dialog.exec_() == QDialog.Accepted and globalref.genOptions.modified): globalref.genOptions.writeFile() self.recentFiles.updateOptions() if globalref.genOptions['MinToSysTray']: self.createTrayIcon() elif self.trayIcon: self.trayIcon.hide() autoSaveMinutes = globalref.genOptions['AutoSaveMinutes'] for control in self.localControls: for window in control.windowList: window.updateWinGenOptions() control.structure.undoList.setNumLevels() control.updateAll(False) if autoSaveMinutes != oldAutoSaveMinutes: control.resetAutoSave() def toolsCustomShortcuts(self): """Show dialog to customize keyboard commands. """ actions = self.activeControl.activeWindow.allActions dialog = miscdialogs.CustomShortcutsDialog(actions, QApplication.activeWindow()) dialog.exec_() def toolsCustomToolbars(self): """Show dialog to customize toolbar buttons. """ actions = self.activeControl.activeWindow.allActions dialog = miscdialogs.CustomToolbarDialog(actions, self.updateToolbars, QApplication.activeWindow()) dialog.exec_() def updateToolbars(self): """Update toolbars after changes in custom toolbar dialog. """ for control in self.localControls: for window in control.windowList: window.setupToolbars() def toolsCustomFonts(self): """Show dialog to customize fonts in various views. """ dialog = miscdialogs.CustomFontDialog(QApplication.activeWindow()) dialog.updateRequired.connect(self.updateCustomFonts) dialog.exec_() def toolsCustomColors(self): """Show dialog to customize GUI colors ans themes. """ self.colorSet.showDialog(QApplication.activeWindow()) def updateCustomFonts(self): """Update fonts in all windows based on a dialog signal. """ self.updateAppFont() for control in self.localControls: for window in control.windowList: window.updateFonts() control.printData.setDefaultFont() for control in self.localControls: control.updateAll(False) def updateAppFont(self): """Update application default font from settings. """ appFont = QFont(self.systemFont) appFontName = globalref.miscOptions['AppFont'] if appFontName: appFont.fromString(appFontName) QApplication.setFont(appFont) def formatSelectAll(self): """Select all text in any currently focused editor. """ try: QApplication.focusWidget().selectAll() except AttributeError: pass def helpViewBasic(self): """Display basic usage instructions. """ if not self.basicHelpView: path = self.findResourceFile('basichelp.html', 'doc', docPath) if not path: QMessageBox.warning(QApplication.activeWindow(), 'TreeLine', _('Error - basic help file not found')) return self.basicHelpView = helpview.HelpView(path, _('TreeLine Basic Usage'), globalref.toolIcons) self.basicHelpView.show() def helpViewFull(self): """Open a TreeLine file with full documentation. """ path = self.findResourceFile('documentation.trln', 'doc', docPath) if not path: QMessageBox.warning(QApplication.activeWindow(), 'TreeLine', _('Error - documentation file not found')) return self.createLocalControl(path, forceNewWindow=True) self.activeControl.filePathObj = pathlib.Path('documentation.trln') self.activeControl.updateWindowCaptions() self.activeControl.expandRootNodes() self.activeControl.imported = True win = self.activeControl.activeWindow win.rightTabs.setCurrentWidget(win.outputSplitter) def helpAbout(self): """ Display version info about this program. """ pyVersion = '.'.join([repr(num) for num in sys.version_info[:3]]) textLines = [ _('TreeLine version {0}').format(__version__), _('written by {0}').format(__author__), '', _('Library versions:'), ' Python: {0}'.format(pyVersion), ' Qt: {0}'.format(qVersion()), ' PyQt: {0}'.format(PYQT_VERSION_STR), ' OS: {0}'.format(platform.platform()) ] dialog = miscdialogs.AboutDialog('TreeLine', textLines, QApplication.windowIcon(), QApplication.activeWindow()) dialog.exec_()
class MainWindow(QMainWindow): """Voice Changer main window.""" def __init__(self, parent=None): super(MainWindow, self).__init__() self.statusBar().showMessage("Move Dial to Deform Microphone Voice !.") self.setWindowTitle(__doc__) self.setMinimumSize(240, 240) self.setMaximumSize(480, 480) self.resize(self.minimumSize()) self.setWindowIcon(QIcon.fromTheme("audio-input-microphone")) self.tray = QSystemTrayIcon(self) self.center() QShortcut("Ctrl+q", self, activated=lambda: self.close()) self.menuBar().addMenu("&File").addAction("Quit", lambda: exit()) self.menuBar().addMenu("Sound").addAction( "STOP !", lambda: call("killall rec", shell=True) ) windowMenu = self.menuBar().addMenu("&Window") windowMenu.addAction("Hide", lambda: self.hide()) windowMenu.addAction("Minimize", lambda: self.showMinimized()) windowMenu.addAction("Maximize", lambda: self.showMaximized()) windowMenu.addAction("Restore", lambda: self.showNormal()) windowMenu.addAction("FullScreen", lambda: self.showFullScreen()) windowMenu.addAction("Center", lambda: self.center()) windowMenu.addAction("Top-Left", lambda: self.move(0, 0)) windowMenu.addAction("To Mouse", lambda: self.move_to_mouse_position()) # widgets group0 = QGroupBox("Voice Deformation") self.setCentralWidget(group0) self.process = QProcess(self) self.process.error.connect( lambda: self.statusBar().showMessage("Info: Process Killed", 5000) ) self.control = QDial() self.control.setRange(-10, 20) self.control.setSingleStep(5) self.control.setValue(0) self.control.setCursor(QCursor(Qt.OpenHandCursor)) self.control.sliderPressed.connect( lambda: self.control.setCursor(QCursor(Qt.ClosedHandCursor)) ) self.control.sliderReleased.connect( lambda: self.control.setCursor(QCursor(Qt.OpenHandCursor)) ) self.control.valueChanged.connect( lambda: self.control.setToolTip(f"<b>{self.control.value()}") ) self.control.valueChanged.connect( lambda: self.statusBar().showMessage( f"Voice deformation: {self.control.value()}", 5000 ) ) self.control.valueChanged.connect(self.run) self.control.valueChanged.connect(lambda: self.process.kill()) # Graphic effect self.glow = QGraphicsDropShadowEffect(self) self.glow.setOffset(0) self.glow.setBlurRadius(99) self.glow.setColor(QColor(99, 255, 255)) self.control.setGraphicsEffect(self.glow) self.glow.setEnabled(False) # Timer to start self.slider_timer = QTimer(self) self.slider_timer.setSingleShot(True) self.slider_timer.timeout.connect(self.on_slider_timer_timeout) # an icon and set focus QLabel(self.control).setPixmap( QIcon.fromTheme("audio-input-microphone").pixmap(32) ) self.control.setFocus() QVBoxLayout(group0).addWidget(self.control) self.menu = QMenu(__doc__) self.menu.addAction(__doc__).setDisabled(True) self.menu.setIcon(self.windowIcon()) self.menu.addSeparator() self.menu.addAction( "Show / Hide", lambda: self.hide() if self.isVisible() else self.showNormal(), ) self.menu.addAction("STOP !", lambda: call("killall rec", shell=True)) self.menu.addSeparator() self.menu.addAction("Quit", lambda: exit()) self.tray.setContextMenu(self.menu) self.make_trayicon() def run(self): """Run/Stop the QTimer.""" if self.slider_timer.isActive(): self.slider_timer.stop() self.glow.setEnabled(True) call("killall rec ; killall play", shell=True) self.slider_timer.start(3000) def on_slider_timer_timeout(self): """Run subprocess to deform voice.""" self.glow.setEnabled(False) value = int(self.control.value()) * 100 command = f'play -q -V0 "|rec -q -V0 -n -d -R riaa bend pitch {value} "' print(f"Voice Deformation Value: {value}") print(f"Voice Deformation Command: {command}") self.process.start(command) if self.isVisible(): self.statusBar().showMessage("Minimizing to System TrayIcon", 3000) print("Minimizing Main Window to System TrayIcon now...") sleep(3) self.hide() def center(self): """Center Window on the Current Screen,with Multi-Monitor support.""" window_geometry = self.frameGeometry() mousepointer_position = QApplication.desktop().cursor().pos() screen = QApplication.desktop().screenNumber(mousepointer_position) centerPoint = QApplication.desktop().screenGeometry(screen).center() window_geometry.moveCenter(centerPoint) self.move(window_geometry.topLeft()) def move_to_mouse_position(self): """Center the Window on the Current Mouse position.""" window_geometry = self.frameGeometry() window_geometry.moveCenter(QApplication.desktop().cursor().pos()) self.move(window_geometry.topLeft()) def make_trayicon(self): """Make a Tray Icon.""" if self.windowIcon() and __doc__: self.tray.setIcon(self.windowIcon()) self.tray.setToolTip(__doc__) self.tray.activated.connect( lambda: self.hide() if self.isVisible() else self.showNormal() ) return self.tray.show()
class QtApplication(QApplication, Application): """Application subclass that provides a Qt application object.""" pluginsLoaded = Signal() applicationRunning = Signal() def __init__(self, tray_icon_name: str = None, **kwargs) -> None: plugin_path = "" if sys.platform == "win32": if hasattr(sys, "frozen"): plugin_path = os.path.join( os.path.dirname(os.path.abspath(sys.executable)), "PyQt5", "plugins") Logger.log("i", "Adding QT5 plugin path: %s", plugin_path) QCoreApplication.addLibraryPath(plugin_path) else: import site for sitepackage_dir in site.getsitepackages(): QCoreApplication.addLibraryPath( os.path.join(sitepackage_dir, "PyQt5", "plugins")) elif sys.platform == "darwin": plugin_path = os.path.join(self.getInstallPrefix(), "Resources", "plugins") if plugin_path: Logger.log("i", "Adding QT5 plugin path: %s", plugin_path) QCoreApplication.addLibraryPath(plugin_path) # use Qt Quick Scene Graph "basic" render loop os.environ["QSG_RENDER_LOOP"] = "basic" super().__init__(sys.argv, **kwargs) # type: ignore self._qml_import_paths = [] #type: List[str] self._main_qml = "main.qml" #type: str self._qml_engine = None #type: Optional[QQmlApplicationEngine] self._main_window = None #type: Optional[MainWindow] self._tray_icon_name = tray_icon_name #type: Optional[str] self._tray_icon = None #type: Optional[str] self._tray_icon_widget = None #type: Optional[QSystemTrayIcon] self._theme = None #type: Optional[Theme] self._renderer = None #type: Optional[QtRenderer] self._job_queue = None #type: Optional[JobQueue] self._version_upgrade_manager = None #type: Optional[VersionUpgradeManager] self._is_shutting_down = False #type: bool self._recent_files = [] #type: List[QUrl] self._configuration_error_message = None #type: Optional[ConfigurationErrorMessage] self._http_network_request_manager = HttpRequestManager(parent=self) #Metadata required for the file dialogues. self.setOrganizationDomain("https://ultimaker.com/") self.setOrganizationName("Ultimaker B.V.") def addCommandLineOptions(self) -> None: super().addCommandLineOptions() # This flag is used by QApplication. We don't process it. self._cli_parser.add_argument( "-qmljsdebugger", help="For Qt's QML debugger compatibility") def initialize(self) -> None: super().initialize() preferences = Application.getInstance().getPreferences() preferences.addPreference("view/force_empty_shader_cache", False) preferences.addPreference("view/opengl_version_detect", OpenGLContext.OpenGlVersionDetect.Autodetect) # Read preferences here (upgrade won't work) to get: # - The language in use, so the splash window can be shown in the correct language. # - The OpenGL 'force' parameters. try: preferences_filename = Resources.getPath(Resources.Preferences, self._app_name + ".cfg") self._preferences.readFromFile(preferences_filename) except FileNotFoundError: Logger.log( "i", "Preferences file not found, ignore and use default language '%s'", self._default_language) # Initialize the package manager to remove and install scheduled packages. self._package_manager = self._package_manager_class(self, parent=self) self._mesh_file_handler = MeshFileHandler(self) #type: MeshFileHandler self._workspace_file_handler = WorkspaceFileHandler( self) #type: WorkspaceFileHandler # Remove this and you will get Windows 95 style for all widgets if you are using Qt 5.10+ self.setStyle("fusion") if preferences.getValue("view/force_empty_shader_cache"): self.setAttribute(Qt.AA_DisableShaderDiskCache) self.setAttribute(Qt.AA_UseDesktopOpenGL) if preferences.getValue( "view/opengl_version_detect" ) != OpenGLContext.OpenGlVersionDetect.ForceModern: major_version, minor_version, profile = OpenGLContext.detectBestOpenGLVersion( preferences.getValue("view/opengl_version_detect") == OpenGLContext.OpenGlVersionDetect.ForceLegacy) else: Logger.info( "Force 'modern' OpenGL (4.1 core) -- overrides 'force legacy opengl' preference." ) major_version, minor_version, profile = ( 4, 1, QSurfaceFormat.CoreProfile) if major_version is None or minor_version is None or profile is None: Logger.log( "e", "Startup failed because OpenGL version probing has failed: tried to create a 2.0 and 4.1 context. Exiting" ) if not self.getIsHeadLess(): QMessageBox.critical( None, "Failed to probe OpenGL", "Could not probe OpenGL. This program requires OpenGL 2.0 or higher. Please check your video card drivers." ) sys.exit(1) else: opengl_version_str = OpenGLContext.versionAsText( major_version, minor_version, profile) Logger.log("d", "Detected most suitable OpenGL context version: %s", opengl_version_str) if not self.getIsHeadLess(): OpenGLContext.setDefaultFormat(major_version, minor_version, profile=profile) self._qml_import_paths.append( os.path.join(os.path.dirname(sys.executable), "qml")) self._qml_import_paths.append( os.path.join(self.getInstallPrefix(), "Resources", "qml")) Logger.log("i", "Initializing job queue ...") self._job_queue = JobQueue() self._job_queue.jobFinished.connect(self._onJobFinished) Logger.log("i", "Initializing version upgrade manager ...") self._version_upgrade_manager = VersionUpgradeManager(self) def startSplashWindowPhase(self) -> None: super().startSplashWindowPhase() i18n_catalog = i18nCatalog("uranium") self.showSplashMessage( i18n_catalog.i18nc("@info:progress", "Initializing package manager...")) self._package_manager.initialize() signal.signal(signal.SIGINT, signal.SIG_DFL) # This is done here as a lot of plugins require a correct gl context. If you want to change the framework, # these checks need to be done in your <framework>Application.py class __init__(). self._configuration_error_message = ConfigurationErrorMessage( self, i18n_catalog.i18nc("@info:status", "Your configuration seems to be corrupt."), lifetime=0, title=i18n_catalog.i18nc("@info:title", "Configuration errors")) # Remove, install, and then loading plugins self.showSplashMessage( i18n_catalog.i18nc("@info:progress", "Loading plugins...")) # Remove and install the plugins that have been scheduled self._plugin_registry.initializeBeforePluginsAreLoaded() self._loadPlugins() self._plugin_registry.checkRequiredPlugins(self.getRequiredPlugins()) self.pluginsLoaded.emit() self.showSplashMessage( i18n_catalog.i18nc("@info:progress", "Updating configuration...")) with self._container_registry.lockFile(): VersionUpgradeManager.getInstance().upgrade() # Load preferences again because before we have loaded the plugins, we don't have the upgrade routine for # the preferences file. Now that we have, load the preferences file again so it can be upgraded and loaded. self.showSplashMessage( i18n_catalog.i18nc("@info:progress", "Loading preferences...")) try: preferences_filename = Resources.getPath(Resources.Preferences, self._app_name + ".cfg") with open(preferences_filename, "r", encoding="utf-8") as f: serialized = f.read() # This performs the upgrade for Preferences self._preferences.deserialize(serialized) self._preferences.setValue("general/plugins_to_remove", "") self._preferences.writeToFile(preferences_filename) except (FileNotFoundError, UnicodeDecodeError): Logger.log( "i", "The preferences file cannot be found or it is corrupted, so we will use default values" ) self.processEvents() # Force the configuration file to be written again since the list of plugins to remove maybe changed try: self._preferences_filename = Resources.getPath( Resources.Preferences, self._app_name + ".cfg") self._preferences.readFromFile(self._preferences_filename) except FileNotFoundError: Logger.log( "i", "The preferences file '%s' cannot be found, will use default values", self._preferences_filename) self._preferences_filename = Resources.getStoragePath( Resources.Preferences, self._app_name + ".cfg") Logger.info("Completed loading preferences.") # FIXME: This is done here because we now use "plugins.json" to manage plugins instead of the Preferences file, # but the PluginRegistry will still import data from the Preferences files if present, such as disabled plugins, # so we need to reset those values AFTER the Preferences file is loaded. self._plugin_registry.initializeAfterPluginsAreLoaded() # Check if we have just updated from an older version self._preferences.addPreference("general/last_run_version", "") last_run_version_str = self._preferences.getValue( "general/last_run_version") if not last_run_version_str: last_run_version_str = self._version last_run_version = Version(last_run_version_str) current_version = Version(self._version) if last_run_version < current_version: self._just_updated_from_old_version = True self._preferences.setValue("general/last_run_version", str(current_version)) self._preferences.writeToFile(self._preferences_filename) # Preferences: recent files self._preferences.addPreference("%s/recent_files" % self._app_name, "") file_names = self._preferences.getValue("%s/recent_files" % self._app_name).split(";") for file_name in file_names: if not os.path.isfile(file_name): continue self._recent_files.append(QUrl.fromLocalFile(file_name)) if not self.getIsHeadLess(): # Initialize System tray icon and make it invisible because it is used only to show pop up messages self._tray_icon = None if self._tray_icon_name: try: self._tray_icon = QIcon( Resources.getPath(Resources.Images, self._tray_icon_name)) self._tray_icon_widget = QSystemTrayIcon(self._tray_icon) self._tray_icon_widget.setVisible(False) Logger.info("Created system tray icon.") except FileNotFoundError: Logger.log("w", "Could not find the icon %s", self._tray_icon_name) def initializeEngine(self) -> None: # TODO: Document native/qml import trickery self._qml_engine = QQmlApplicationEngine(self) self.processEvents() self._qml_engine.setOutputWarningsToStandardError(False) self._qml_engine.warnings.connect(self.__onQmlWarning) for path in self._qml_import_paths: self._qml_engine.addImportPath(path) if not hasattr(sys, "frozen"): self._qml_engine.addImportPath( os.path.join(os.path.dirname(__file__), "qml")) self._qml_engine.rootContext().setContextProperty( "QT_VERSION_STR", QT_VERSION_STR) self.processEvents() self._qml_engine.rootContext().setContextProperty( "screenScaleFactor", self._screenScaleFactor()) self.registerObjects(self._qml_engine) Bindings.register() # Preload theme. The theme will be loaded on first use, which will incur a ~0.1s freeze on the MainThread. # Do it here, while the splash screen is shown. Also makes this freeze explicit and traceable. self.getTheme() self.processEvents() i18n_catalog = i18nCatalog("uranium") self.showSplashMessage( i18n_catalog.i18nc("@info:progress", "Loading UI...")) self._qml_engine.load(self._main_qml) self.engineCreatedSignal.emit() recentFilesChanged = pyqtSignal() @pyqtProperty("QVariantList", notify=recentFilesChanged) def recentFiles(self) -> List[QUrl]: return self._recent_files fileProvidersChanged = pyqtSignal() @pyqtProperty("QVariantList", notify=fileProvidersChanged) def fileProviders(self) -> List[FileProvider]: return self.getFileProviders() def _onJobFinished(self, job: Job) -> None: if isinstance(job, WriteFileJob) and not job.getResult(): return if isinstance(job, (ReadMeshJob, ReadFileJob, WriteFileJob)) and job.getAddToRecentFiles(): self.addFileToRecentFiles(job.getFileName()) def addFileToRecentFiles(self, file_name: str) -> None: file_path = QUrl.fromLocalFile(file_name) if file_path in self._recent_files: self._recent_files.remove(file_path) self._recent_files.insert(0, file_path) if len(self._recent_files) > 10: del self._recent_files[10] pref = "" for path in self._recent_files: pref += path.toLocalFile() + ";" self.getPreferences().setValue( "%s/recent_files" % self.getApplicationName(), pref) self.recentFilesChanged.emit() def run(self) -> None: super().run() def hideMessage(self, message: Message) -> None: with self._message_lock: if message in self._visible_messages: message.hide( send_signal=False ) # we're in handling hideMessageSignal so we don't want to resend it self._visible_messages.remove(message) self.visibleMessageRemoved.emit(message) def showMessage(self, message: Message) -> None: with self._message_lock: if message not in self._visible_messages: self._visible_messages.append(message) message.setLifetimeTimer(QTimer()) message.setInactivityTimer(QTimer()) self.visibleMessageAdded.emit(message) # also show toast message when the main window is minimized self.showToastMessage(self._app_name, message.getText()) def _onMainWindowStateChanged(self, window_state: int) -> None: if self._tray_icon and self._tray_icon_widget: visible = window_state == Qt.WindowMinimized self._tray_icon_widget.setVisible(visible) # Show toast message using System tray widget. def showToastMessage(self, title: str, message: str) -> None: if self.checkWindowMinimizedState() and self._tray_icon_widget: # NOTE: Qt 5.8 don't support custom icon for the system tray messages, but Qt 5.9 does. # We should use the custom icon when we switch to Qt 5.9 self._tray_icon_widget.showMessage(title, message) def setMainQml(self, path: str) -> None: self._main_qml = path def exec_(self, *args: Any, **kwargs: Any) -> None: self.applicationRunning.emit() super().exec_(*args, **kwargs) @pyqtSlot() def reloadQML(self) -> None: # only reload when it is a release build if not self.getIsDebugMode(): return if self._qml_engine and self._theme: self._qml_engine.clearComponentCache() self._theme.reload() self._qml_engine.load(self._main_qml) # Hide the window. For some reason we can't close it yet. This needs to be done in the onComponentCompleted. for obj in self._qml_engine.rootObjects(): if obj != self._qml_engine.rootObjects()[-1]: obj.hide() @pyqtSlot() def purgeWindows(self) -> None: # Close all root objects except the last one. # Should only be called by onComponentCompleted of the mainWindow. if self._qml_engine: for obj in self._qml_engine.rootObjects(): if obj != self._qml_engine.rootObjects()[-1]: obj.close() @pyqtSlot("QList<QQmlError>") def __onQmlWarning(self, warnings: List[QQmlError]) -> None: for warning in warnings: Logger.log("w", warning.toString()) engineCreatedSignal = Signal() def isShuttingDown(self) -> bool: return self._is_shutting_down def registerObjects( self, engine ) -> None: #type: ignore #Don't type engine, because the type depends on the platform you're running on so it always gives an error somewhere. engine.rootContext().setContextProperty("PluginRegistry", PluginRegistry.getInstance()) def getRenderer(self) -> QtRenderer: if not self._renderer: self._renderer = QtRenderer() return cast(QtRenderer, self._renderer) mainWindowChanged = Signal() def getMainWindow(self) -> Optional[MainWindow]: return self._main_window def setMainWindow(self, window: MainWindow) -> None: if window != self._main_window: if self._main_window is not None: self._main_window.windowStateChanged.disconnect( self._onMainWindowStateChanged) self._main_window = window if self._main_window is not None: self._main_window.windowStateChanged.connect( self._onMainWindowStateChanged) self.mainWindowChanged.emit() def setVisible(self, visible: bool) -> None: if self._main_window is not None: self._main_window.visible = visible @property def isVisible(self) -> bool: if self._main_window is not None: return self._main_window.visible #type: ignore #MyPy doesn't realise that self._main_window cannot be None here. return False def getTheme(self) -> Optional[Theme]: if self._theme is None: if self._qml_engine is None: Logger.log( "e", "The theme cannot be accessed before the engine is initialised" ) return None self._theme = UM.Qt.Bindings.Theme.Theme.getInstance( self._qml_engine) return self._theme # Handle a function that should be called later. def functionEvent(self, event: QEvent) -> None: e = _QtFunctionEvent(event) QCoreApplication.postEvent(self, e) # Handle Qt events def event(self, event: QEvent) -> bool: if event.type() == _QtFunctionEvent.QtFunctionEvent: event._function_event.call() return True return super().event(event) def windowClosed(self, save_data: bool = True) -> None: Logger.log("d", "Shutting down %s", self.getApplicationName()) self._is_shutting_down = True # garbage collect tray icon so it gets properly closed before the application is closed self._tray_icon_widget = None if save_data: try: self.savePreferences() except Exception as e: Logger.log("e", "Exception while saving preferences: %s", repr(e)) try: self.applicationShuttingDown.emit() except Exception as e: Logger.log("e", "Exception while emitting shutdown signal: %s", repr(e)) try: self.getBackend().close() except Exception as e: Logger.log("e", "Exception while closing backend: %s", repr(e)) if self._tray_icon_widget: self._tray_icon_widget.deleteLater() self.quit() def checkWindowMinimizedState(self) -> bool: if self._main_window is not None and self._main_window.windowState( ) == Qt.WindowMinimized: return True else: return False @pyqtSlot(result="QObject*") def getBackend(self) -> Backend: """Get the backend of the application (the program that does the heavy lifting). The backend is also a QObject, which can be used from qml. """ return self._backend @pyqtProperty("QVariant", constant=True) def backend(self) -> Backend: """Property used to expose the backend It is made static as the backend is not supposed to change during runtime. This makes the connection between backend and QML more reliable than the pyqtSlot above. :returns: Backend :type{Backend} """ return self.getBackend() splash = None # type: Optional[QSplashScreen] """Create a class variable so we can manage the splash in the CrashHandler dialog when the Application instance is not yet created, e.g. when an error occurs during the initialization """ def createSplash(self) -> None: if not self.getIsHeadLess(): try: QtApplication.splash = self._createSplashScreen() except FileNotFoundError: QtApplication.splash = None else: if QtApplication.splash: QtApplication.splash.show() self.processEvents() def showSplashMessage(self, message: str) -> None: """Display text on the splash screen.""" if not QtApplication.splash: self.createSplash() if QtApplication.splash: self.processEvents( ) # Process events from previous loading phase before updating the message QtApplication.splash.showMessage( message, Qt.AlignHCenter | Qt.AlignVCenter) # Now update the message self.processEvents() # And make sure it is immediately visible elif self.getIsHeadLess(): Logger.log("d", message) def closeSplash(self) -> None: """Close the splash screen after the application has started.""" if QtApplication.splash: QtApplication.splash.close() QtApplication.splash = None def createQmlComponent( self, qml_file_path: str, context_properties: Dict[str, "QObject"] = None) -> Optional["QObject"]: """Create a QML component from a qml file. :param qml_file_path:: The absolute file path to the root qml file. :param context_properties:: Optional dictionary containing the properties that will be set on the context of the qml instance before creation. :return: None in case the creation failed (qml error), else it returns the qml instance. :note If the creation fails, this function will ensure any errors are logged to the logging service. """ if self._qml_engine is None: # Protect in case the engine was not initialized yet return None path = QUrl.fromLocalFile(qml_file_path) component = QQmlComponent(self._qml_engine, path) result_context = QQmlContext( self._qml_engine.rootContext() ) #type: ignore #MyPy doens't realise that self._qml_engine can't be None here. if context_properties is not None: for name, value in context_properties.items(): result_context.setContextProperty(name, value) result = component.create(result_context) for err in component.errors(): Logger.log("e", str(err.toString())) if result is None: return None # We need to store the context with the qml object, else the context gets garbage collected and the qml objects # no longer function correctly/application crashes. result.attached_context = result_context return result @pyqtSlot() def deleteAll(self, only_selectable=True) -> None: """Delete all nodes containing mesh data in the scene. :param only_selectable:. Set this to False to delete objects from all build plates """ self.getController().deleteAllNodesWithMeshData(only_selectable) @pyqtSlot() def resetWorkspace(self) -> None: self._workspace_metadata_storage.clear() self.deleteAll() self.workspaceLoaded.emit("") def getMeshFileHandler(self) -> MeshFileHandler: """Get the MeshFileHandler of this application.""" return self._mesh_file_handler def getWorkspaceFileHandler(self) -> WorkspaceFileHandler: return self._workspace_file_handler @pyqtSlot(result=QObject) def getPackageManager(self) -> PackageManager: return self._package_manager def getHttpRequestManager(self) -> "HttpRequestManager": return self._http_network_request_manager @classmethod def getInstance(cls, *args, **kwargs) -> "QtApplication": """Gets the instance of this application. This is just to further specify the type of Application.getInstance(). :return: The instance of this application. """ return cast(QtApplication, super().getInstance(**kwargs)) def _createSplashScreen(self) -> QSplashScreen: return QSplashScreen( QPixmap( Resources.getPath(Resources.Images, self.getApplicationName() + ".png"))) def _screenScaleFactor(self) -> float: # OSX handles sizes of dialogs behind our backs, but other platforms need # to know about the device pixel ratio if sys.platform == "darwin": return 1.0 else: # determine a device pixel ratio from font metrics, using the same logic as UM.Theme fontPixelRatio = QFontMetrics( QCoreApplication.instance().font()).ascent() / 11 # round the font pixel ratio to quarters fontPixelRatio = int(fontPixelRatio * 4) / 4 return fontPixelRatio @pyqtProperty(str, constant=True) def applicationDisplayName(self) -> str: return self.getApplicationDisplayName()
class ElectrumGui(BaseElectrumGui, Logger): network_dialog: Optional['NetworkDialog'] lightning_dialog: Optional['LightningDialog'] watchtower_dialog: Optional['WatchtowerDialog'] @profiler def __init__(self, *, config: 'SimpleConfig', daemon: 'Daemon', plugins: 'Plugins'): set_language(config.get('language', get_default_language())) BaseElectrumGui.__init__(self, config=config, daemon=daemon, plugins=plugins) Logger.__init__(self) self.logger.info( f"Qt GUI starting up... Qt={QtCore.QT_VERSION_STR}, PyQt={QtCore.PYQT_VERSION_STR}" ) # Uncomment this call to verify objects are being properly # GC-ed when windows are closed #network.add_jobs([DebugMem([Abstract_Wallet, SPV, Synchronizer, # ElectrumWindow], interval=5)]) QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads) if hasattr(QtCore.Qt, "AA_ShareOpenGLContexts"): QtCore.QCoreApplication.setAttribute( QtCore.Qt.AA_ShareOpenGLContexts) if hasattr(QGuiApplication, 'setDesktopFileName'): QGuiApplication.setDesktopFileName('electrum-grlc.desktop') self.gui_thread = threading.current_thread() self.windows = [] # type: List[ElectrumWindow] self.efilter = OpenFileEventFilter(self.windows) self.app = QElectrumApplication(sys.argv) self.app.installEventFilter(self.efilter) self.app.setWindowIcon(read_QIcon("electrum-grlc.png")) self._cleaned_up = False # timer self.timer = QTimer(self.app) self.timer.setSingleShot(False) self.timer.setInterval(500) # msec self.network_dialog = None self.lightning_dialog = None self.watchtower_dialog = None self.network_updated_signal_obj = QNetworkUpdatedSignalObject() self._num_wizards_in_progress = 0 self._num_wizards_lock = threading.Lock() self.dark_icon = self.config.get("dark_icon", False) self.tray = None self._init_tray() self.app.new_window_signal.connect(self.start_new_window) self.app.quit_signal.connect(self.app.quit, Qt.QueuedConnection) self.set_dark_theme_if_needed() run_hook('init_qt', self) def _init_tray(self): self.tray = QSystemTrayIcon(self.tray_icon(), None) self.tray.setToolTip('Electrum-GRLC') self.tray.activated.connect(self.tray_activated) self.build_tray_menu() self.tray.show() def set_dark_theme_if_needed(self): use_dark_theme = self.config.get('qt_gui_color_theme', 'default') == 'dark' if use_dark_theme: try: import qdarkstyle self.app.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5()) except BaseException as e: use_dark_theme = False self.logger.warning(f'Error setting dark theme: {repr(e)}') # Apply any necessary stylesheet patches patch_qt_stylesheet(use_dark_theme=use_dark_theme) # Even if we ourselves don't set the dark theme, # the OS/window manager/etc might set *a dark theme*. # Hence, try to choose colors accordingly: ColorScheme.update_from_widget(QWidget(), force_dark=use_dark_theme) def build_tray_menu(self): if not self.tray: return # Avoid immediate GC of old menu when window closed via its action if self.tray.contextMenu() is None: m = QMenu() self.tray.setContextMenu(m) else: m = self.tray.contextMenu() m.clear() network = self.daemon.network m.addAction(_("Network"), self.show_network_dialog) if network and network.lngossip: m.addAction(_("Lightning Network"), self.show_lightning_dialog) if network and network.local_watchtower: m.addAction(_("Local Watchtower"), self.show_watchtower_dialog) for window in self.windows: name = window.wallet.basename() submenu = m.addMenu(name) submenu.addAction(_("Show/Hide"), window.show_or_hide) submenu.addAction(_("Close"), window.close) m.addAction(_("Dark/Light"), self.toggle_tray_icon) m.addSeparator() m.addAction(_("Exit Electrum"), self.app.quit) def tray_icon(self): if self.dark_icon: return read_QIcon('electrum_dark_icon.png') else: return read_QIcon('electrum_light_icon.png') def toggle_tray_icon(self): if not self.tray: return self.dark_icon = not self.dark_icon self.config.set_key("dark_icon", self.dark_icon, True) self.tray.setIcon(self.tray_icon()) def tray_activated(self, reason): if reason == QSystemTrayIcon.DoubleClick: if all([w.is_hidden() for w in self.windows]): for w in self.windows: w.bring_to_top() else: for w in self.windows: w.hide() def _cleanup_before_exit(self): if self._cleaned_up: return self._cleaned_up = True self.app.new_window_signal.disconnect() self.efilter = None # If there are still some open windows, try to clean them up. for window in list(self.windows): window.close() window.clean_up() if self.network_dialog: self.network_dialog.close() self.network_dialog.clean_up() self.network_dialog = None self.network_updated_signal_obj = None if self.lightning_dialog: self.lightning_dialog.close() self.lightning_dialog = None if self.watchtower_dialog: self.watchtower_dialog.close() self.watchtower_dialog = None # Shut down the timer cleanly self.timer.stop() self.timer = None # clipboard persistence. see http://www.mail-archive.com/[email protected]/msg17328.html event = QtCore.QEvent(QtCore.QEvent.Clipboard) self.app.sendEvent(self.app.clipboard(), event) if self.tray: self.tray.hide() self.tray.deleteLater() self.tray = None def _maybe_quit_if_no_windows_open(self) -> None: """Check if there are any open windows and decide whether we should quit.""" # keep daemon running after close if self.config.get('daemon'): return # check if a wizard is in progress with self._num_wizards_lock: if self._num_wizards_in_progress > 0 or len(self.windows) > 0: return self.app.quit() def new_window(self, path, uri=None): # Use a signal as can be called from daemon thread self.app.new_window_signal.emit(path, uri) def show_lightning_dialog(self): if not self.daemon.network.has_channel_db(): return if not self.lightning_dialog: self.lightning_dialog = LightningDialog(self) self.lightning_dialog.bring_to_top() def show_watchtower_dialog(self): if not self.watchtower_dialog: self.watchtower_dialog = WatchtowerDialog(self) self.watchtower_dialog.bring_to_top() def show_network_dialog(self): if self.network_dialog: self.network_dialog.on_update() self.network_dialog.show() self.network_dialog.raise_() return self.network_dialog = NetworkDialog( network=self.daemon.network, config=self.config, network_updated_signal_obj=self.network_updated_signal_obj) self.network_dialog.show() def _create_window_for_wallet(self, wallet): w = ElectrumWindow(self, wallet) self.windows.append(w) self.build_tray_menu() w.warn_if_testnet() w.warn_if_watching_only() return w def count_wizards_in_progress(func): def wrapper(self: 'ElectrumGui', *args, **kwargs): with self._num_wizards_lock: self._num_wizards_in_progress += 1 try: return func(self, *args, **kwargs) finally: with self._num_wizards_lock: self._num_wizards_in_progress -= 1 self._maybe_quit_if_no_windows_open() return wrapper @count_wizards_in_progress def start_new_window(self, path, uri, *, app_is_starting=False) -> Optional[ElectrumWindow]: '''Raises the window for the wallet if it is open. Otherwise opens the wallet and creates a new window for it''' wallet = None try: wallet = self.daemon.load_wallet(path, None) except Exception as e: self.logger.exception('') custom_message_box(icon=QMessageBox.Warning, parent=None, title=_('Error'), text=_('Cannot load wallet') + ' (1):\n' + repr(e)) # if app is starting, still let wizard to appear if not app_is_starting: return if not wallet: try: wallet = self._start_wizard_to_select_or_create_wallet(path) except (WalletFileException, BitcoinException) as e: self.logger.exception('') custom_message_box(icon=QMessageBox.Warning, parent=None, title=_('Error'), text=_('Cannot load wallet') + ' (2):\n' + repr(e)) if not wallet: return # create or raise window try: for window in self.windows: if window.wallet.storage.path == wallet.storage.path: break else: window = self._create_window_for_wallet(wallet) except Exception as e: self.logger.exception('') custom_message_box(icon=QMessageBox.Warning, parent=None, title=_('Error'), text=_('Cannot create window for wallet') + ':\n' + repr(e)) if app_is_starting: wallet_dir = os.path.dirname(path) path = os.path.join(wallet_dir, get_new_wallet_name(wallet_dir)) self.start_new_window(path, uri) return if uri: window.pay_to_URI(uri) window.bring_to_top() window.setWindowState(window.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive) window.activateWindow() return window def _start_wizard_to_select_or_create_wallet( self, path) -> Optional[Abstract_Wallet]: wizard = InstallWizard(self.config, self.app, self.plugins, gui_object=self) try: path, storage = wizard.select_storage(path, self.daemon.get_wallet) # storage is None if file does not exist if storage is None: wizard.path = path # needed by trustedcoin plugin wizard.run('new') storage, db = wizard.create_storage(path) else: db = WalletDB(storage.read(), manual_upgrades=False) wizard.run_upgrades(storage, db) except (UserCancelled, GoBack): return except WalletAlreadyOpenInMemory as e: return e.wallet finally: wizard.terminate() # return if wallet creation is not complete if storage is None or db.get_action(): return wallet = Wallet(db, storage, config=self.config) wallet.start_network(self.daemon.network) self.daemon.add_wallet(wallet) return wallet def close_window(self, window: ElectrumWindow): if window in self.windows: self.windows.remove(window) self.build_tray_menu() # save wallet path of last open window if not self.windows: self.config.save_last_wallet(window.wallet) run_hook('on_close_window', window) self.daemon.stop_wallet(window.wallet.storage.path) def init_network(self): # Show network dialog if config does not exist if self.daemon.network: if self.config.get('auto_connect') is None: wizard = InstallWizard(self.config, self.app, self.plugins, gui_object=self) wizard.init_network(self.daemon.network) wizard.terminate() def main(self): # setup Ctrl-C handling and tear-down code first, so that user can easily exit whenever self.app.setQuitOnLastWindowClosed( False) # so _we_ can decide whether to quit self.app.lastWindowClosed.connect(self._maybe_quit_if_no_windows_open) self.app.aboutToQuit.connect(self._cleanup_before_exit) signal.signal(signal.SIGINT, lambda *args: self.app.quit()) # hook for crash reporter Exception_Hook.maybe_setup(config=self.config) # first-start network-setup try: self.init_network() except UserCancelled: return except GoBack: return except Exception as e: self.logger.exception('') return # start wizard to select/create wallet self.timer.start() path = self.config.get_wallet_path(use_gui_last_wallet=True) try: if not self.start_new_window( path, self.config.get('url'), app_is_starting=True): return except Exception as e: self.logger.error( "error loading wallet (or creating window for it)") send_exception_to_crash_reporter(e) # Let Qt event loop start properly so that crash reporter window can appear. # We will shutdown when the user closes that window, via lastWindowClosed signal. # main loop self.logger.info("starting Qt main loop") self.app.exec_() # on some platforms the exec_ call may not return, so use _cleanup_before_exit def stop(self): self.logger.info('closing GUI') self.app.quit_signal.emit()
def _init_tray(self): self.tray = QSystemTrayIcon(self.tray_icon(), None) self.tray.setToolTip('Electrum-GRLC') self.tray.activated.connect(self.tray_activated) self.build_tray_menu() self.tray.show()
def __init__(self, tray_icon_name=None, **kwargs): plugin_path = "" if sys.platform == "win32": if hasattr(sys, "frozen"): plugin_path = os.path.join( os.path.dirname(os.path.abspath(sys.executable)), "PyQt5", "plugins") Logger.log("i", "Adding QT5 plugin path: %s" % (plugin_path)) QCoreApplication.addLibraryPath(plugin_path) else: import site for sitepackage_dir in site.getsitepackages(): QCoreApplication.addLibraryPath( os.path.join(sitepackage_dir, "PyQt5", "plugins")) elif sys.platform == "darwin": plugin_path = os.path.join(Application.getInstallPrefix(), "Resources", "plugins") if plugin_path: Logger.log("i", "Adding QT5 plugin path: %s" % (plugin_path)) QCoreApplication.addLibraryPath(plugin_path) os.environ["QSG_RENDER_LOOP"] = "basic" super().__init__(sys.argv, **kwargs) self.setAttribute(Qt.AA_UseDesktopOpenGL) major_version, minor_version, profile = OpenGLContext.detectBestOpenGLVersion( ) if major_version is None and minor_version is None and profile is None: Logger.log( "e", "Startup failed because OpenGL version probing has failed: tried to create a 2.0 and 4.1 context. Exiting" ) QMessageBox.critical( None, "Failed to probe OpenGL", "Could not probe OpenGL. This program requires OpenGL 2.0 or higher. Please check your video card drivers." ) sys.exit(1) else: Logger.log( "d", "Detected most suitable OpenGL context version: %s" % (OpenGLContext.versionAsText(major_version, minor_version, profile))) OpenGLContext.setDefaultFormat(major_version, minor_version, profile=profile) self._plugins_loaded = False # Used to determine when it's safe to use the plug-ins. self._main_qml = "main.qml" self._engine = None self._renderer = None self._main_window = None self._theme = None self._shutting_down = False self._qml_import_paths = [] self._qml_import_paths.append( os.path.join(os.path.dirname(sys.executable), "qml")) self._qml_import_paths.append( os.path.join(Application.getInstallPrefix(), "Resources", "qml")) self.parseCommandLine() Logger.log("i", "Command line arguments: %s", self._parsed_command_line) self._splash = None signal.signal(signal.SIGINT, signal.SIG_DFL) # This is done here as a lot of plugins require a correct gl context. If you want to change the framework, # these checks need to be done in your <framework>Application.py class __init__(). i18n_catalog = i18nCatalog("uranium") self.showSplashMessage( i18n_catalog.i18nc("@info:progress", "Loading plugins...")) self._loadPlugins() self._plugin_registry.checkRequiredPlugins(self.getRequiredPlugins()) self.pluginsLoaded.emit() self.showSplashMessage( i18n_catalog.i18nc("@info:progress", "Updating configuration...")) upgraded = UM.VersionUpgradeManager.VersionUpgradeManager.getInstance( ).upgrade() if upgraded: # Preferences might have changed. Load them again. # Note that the language can't be updated, so that will always revert to English. preferences = Preferences.getInstance() try: preferences.readFromFile( Resources.getPath(Resources.Preferences, self._application_name + ".cfg")) except FileNotFoundError: pass self.showSplashMessage( i18n_catalog.i18nc("@info:progress", "Loading preferences...")) try: file_name = Resources.getPath(Resources.Preferences, self.getApplicationName() + ".cfg") Preferences.getInstance().readFromFile(file_name) except FileNotFoundError: pass self.getApplicationName() Preferences.getInstance().addPreference( "%s/recent_files" % self.getApplicationName(), "") self._recent_files = [] file_names = Preferences.getInstance().getValue( "%s/recent_files" % self.getApplicationName()).split(";") for file_name in file_names: if not os.path.isfile(file_name): continue self._recent_files.append(QUrl.fromLocalFile(file_name)) JobQueue.getInstance().jobFinished.connect(self._onJobFinished) # Initialize System tray icon and make it invisible because it is used only to show pop up messages self._tray_icon = None self._tray_icon_widget = None if tray_icon_name: self._tray_icon = QIcon( Resources.getPath(Resources.Images, tray_icon_name)) self._tray_icon_widget = QSystemTrayIcon(self._tray_icon) self._tray_icon_widget.setVisible(False)
class QtApplication(QApplication, Application): pluginsLoaded = Signal() applicationRunning = Signal() def __init__(self, tray_icon_name=None, **kwargs): plugin_path = "" if sys.platform == "win32": if hasattr(sys, "frozen"): plugin_path = os.path.join( os.path.dirname(os.path.abspath(sys.executable)), "PyQt5", "plugins") Logger.log("i", "Adding QT5 plugin path: %s" % (plugin_path)) QCoreApplication.addLibraryPath(plugin_path) else: import site for sitepackage_dir in site.getsitepackages(): QCoreApplication.addLibraryPath( os.path.join(sitepackage_dir, "PyQt5", "plugins")) elif sys.platform == "darwin": plugin_path = os.path.join(Application.getInstallPrefix(), "Resources", "plugins") if plugin_path: Logger.log("i", "Adding QT5 plugin path: %s" % (plugin_path)) QCoreApplication.addLibraryPath(plugin_path) os.environ["QSG_RENDER_LOOP"] = "basic" super().__init__(sys.argv, **kwargs) self.setAttribute(Qt.AA_UseDesktopOpenGL) major_version, minor_version, profile = OpenGLContext.detectBestOpenGLVersion( ) if major_version is None and minor_version is None and profile is None: Logger.log( "e", "Startup failed because OpenGL version probing has failed: tried to create a 2.0 and 4.1 context. Exiting" ) QMessageBox.critical( None, "Failed to probe OpenGL", "Could not probe OpenGL. This program requires OpenGL 2.0 or higher. Please check your video card drivers." ) sys.exit(1) else: Logger.log( "d", "Detected most suitable OpenGL context version: %s" % (OpenGLContext.versionAsText(major_version, minor_version, profile))) OpenGLContext.setDefaultFormat(major_version, minor_version, profile=profile) self._plugins_loaded = False # Used to determine when it's safe to use the plug-ins. self._main_qml = "main.qml" self._engine = None self._renderer = None self._main_window = None self._theme = None self._shutting_down = False self._qml_import_paths = [] self._qml_import_paths.append( os.path.join(os.path.dirname(sys.executable), "qml")) self._qml_import_paths.append( os.path.join(Application.getInstallPrefix(), "Resources", "qml")) self.parseCommandLine() Logger.log("i", "Command line arguments: %s", self._parsed_command_line) self._splash = None signal.signal(signal.SIGINT, signal.SIG_DFL) # This is done here as a lot of plugins require a correct gl context. If you want to change the framework, # these checks need to be done in your <framework>Application.py class __init__(). i18n_catalog = i18nCatalog("uranium") self.showSplashMessage( i18n_catalog.i18nc("@info:progress", "Loading plugins...")) self._loadPlugins() self._plugin_registry.checkRequiredPlugins(self.getRequiredPlugins()) self.pluginsLoaded.emit() self.showSplashMessage( i18n_catalog.i18nc("@info:progress", "Updating configuration...")) upgraded = UM.VersionUpgradeManager.VersionUpgradeManager.getInstance( ).upgrade() if upgraded: # Preferences might have changed. Load them again. # Note that the language can't be updated, so that will always revert to English. preferences = Preferences.getInstance() try: preferences.readFromFile( Resources.getPath(Resources.Preferences, self._application_name + ".cfg")) except FileNotFoundError: pass self.showSplashMessage( i18n_catalog.i18nc("@info:progress", "Loading preferences...")) try: file_name = Resources.getPath(Resources.Preferences, self.getApplicationName() + ".cfg") Preferences.getInstance().readFromFile(file_name) except FileNotFoundError: pass self.getApplicationName() Preferences.getInstance().addPreference( "%s/recent_files" % self.getApplicationName(), "") self._recent_files = [] file_names = Preferences.getInstance().getValue( "%s/recent_files" % self.getApplicationName()).split(";") for file_name in file_names: if not os.path.isfile(file_name): continue self._recent_files.append(QUrl.fromLocalFile(file_name)) JobQueue.getInstance().jobFinished.connect(self._onJobFinished) # Initialize System tray icon and make it invisible because it is used only to show pop up messages self._tray_icon = None self._tray_icon_widget = None if tray_icon_name: self._tray_icon = QIcon( Resources.getPath(Resources.Images, tray_icon_name)) self._tray_icon_widget = QSystemTrayIcon(self._tray_icon) self._tray_icon_widget.setVisible(False) recentFilesChanged = pyqtSignal() @pyqtProperty("QVariantList", notify=recentFilesChanged) def recentFiles(self): return self._recent_files def _onJobFinished(self, job): if (not isinstance(job, ReadMeshJob) and not isinstance(job, ReadFileJob)) or not job.getResult(): return f = QUrl.fromLocalFile(job.getFileName()) if f in self._recent_files: self._recent_files.remove(f) self._recent_files.insert(0, f) if len(self._recent_files) > 10: del self._recent_files[10] pref = "" for path in self._recent_files: pref += path.toLocalFile() + ";" Preferences.getInstance().setValue( "%s/recent_files" % self.getApplicationName(), pref) self.recentFilesChanged.emit() def run(self): pass def hideMessage(self, message): with self._message_lock: if message in self._visible_messages: self._visible_messages.remove(message) self.visibleMessageRemoved.emit(message) def showMessage(self, message): with self._message_lock: if message not in self._visible_messages: self._visible_messages.append(message) message.setLifetimeTimer(QTimer()) message.setInactivityTimer(QTimer()) self.visibleMessageAdded.emit(message) # also show toast message when the main window is minimized self.showToastMessage(self._application_name, message.getText()) def _onMainWindowStateChanged(self, window_state): if self._tray_icon: visible = window_state == Qt.WindowMinimized self._tray_icon_widget.setVisible(visible) # Show toast message using System tray widget. def showToastMessage(self, title: str, message: str): if self.checkWindowMinimizedState() and self._tray_icon_widget: # NOTE: Qt 5.8 don't support custom icon for the system tray messages, but Qt 5.9 does. # We should use the custom icon when we switch to Qt 5.9 self._tray_icon_widget.showMessage(title, message) def setMainQml(self, path): self._main_qml = path def initializeEngine(self): # TODO: Document native/qml import trickery Bindings.register() self._engine = QQmlApplicationEngine() self._engine.setOutputWarningsToStandardError(False) self._engine.warnings.connect(self.__onQmlWarning) for path in self._qml_import_paths: self._engine.addImportPath(path) if not hasattr(sys, "frozen"): self._engine.addImportPath( os.path.join(os.path.dirname(__file__), "qml")) self._engine.rootContext().setContextProperty("QT_VERSION_STR", QT_VERSION_STR) self._engine.rootContext().setContextProperty( "screenScaleFactor", self._screenScaleFactor()) self.registerObjects(self._engine) self._engine.load(self._main_qml) self.engineCreatedSignal.emit() def exec_(self, *args, **kwargs): self.applicationRunning.emit() super().exec_(*args, **kwargs) @pyqtSlot() def reloadQML(self): # only reload when it is a release build if not self.getIsDebugMode(): return self._engine.clearComponentCache() self._theme.reload() self._engine.load(self._main_qml) # Hide the window. For some reason we can't close it yet. This needs to be done in the onComponentCompleted. for obj in self._engine.rootObjects(): if obj != self._engine.rootObjects()[-1]: obj.hide() @pyqtSlot() def purgeWindows(self): # Close all root objects except the last one. # Should only be called by onComponentCompleted of the mainWindow. for obj in self._engine.rootObjects(): if obj != self._engine.rootObjects()[-1]: obj.close() @pyqtSlot("QList<QQmlError>") def __onQmlWarning(self, warnings): for warning in warnings: Logger.log("w", warning.toString()) engineCreatedSignal = Signal() def isShuttingDown(self): return self._shutting_down def registerObjects(self, engine): engine.rootContext().setContextProperty("PluginRegistry", PluginRegistry.getInstance()) def getRenderer(self): if not self._renderer: self._renderer = QtRenderer() return self._renderer @classmethod def addCommandLineOptions(self, parser, parsed_command_line={}): super().addCommandLineOptions(parser, parsed_command_line=parsed_command_line) parser.add_argument( "--disable-textures", dest="disable-textures", action="store_true", default=False, help= "Disable Qt texture loading as a workaround for certain crashes.") parser.add_argument("-qmljsdebugger", help="For Qt's QML debugger compatibility") mainWindowChanged = Signal() def getMainWindow(self): return self._main_window def setMainWindow(self, window): if window != self._main_window: if self._main_window is not None: self._main_window.windowStateChanged.disconnect( self._onMainWindowStateChanged) self._main_window = window if self._main_window is not None: self._main_window.windowStateChanged.connect( self._onMainWindowStateChanged) self.mainWindowChanged.emit() def setVisible(self, visible): if self._engine is None: self.initializeEngine() if self._main_window is not None: self._main_window.visible = visible @property def isVisible(self): if self._main_window is not None: return self._main_window.visible def getTheme(self): if self._theme is None: if self._engine is None: Logger.log( "e", "The theme cannot be accessed before the engine is initialised" ) return None self._theme = UM.Qt.Bindings.Theme.Theme.getInstance(self._engine) return self._theme # Handle a function that should be called later. def functionEvent(self, event): e = _QtFunctionEvent(event) QCoreApplication.postEvent(self, e) # Handle Qt events def event(self, event): if event.type() == _QtFunctionEvent.QtFunctionEvent: event._function_event.call() return True return super().event(event) def windowClosed(self): Logger.log("d", "Shutting down %s", self.getApplicationName()) self._shutting_down = True try: Preferences.getInstance().writeToFile( Resources.getStoragePath(Resources.Preferences, self.getApplicationName() + ".cfg")) except Exception as e: Logger.log("e", "Exception while saving preferences: %s", repr(e)) try: self.applicationShuttingDown.emit() except Exception as e: Logger.log("e", "Exception while emitting shutdown signal: %s", repr(e)) try: self.getBackend().close() except Exception as e: Logger.log("e", "Exception while closing backend: %s", repr(e)) self.quit() def checkWindowMinimizedState(self): if self._main_window is not None and self._main_window.windowState( ) == Qt.WindowMinimized: return True else: return False ## Get the backend of the application (the program that does the heavy lifting). # The backend is also a QObject, which can be used from qml. # \returns Backend \type{Backend} @pyqtSlot(result="QObject*") def getBackend(self): return self._backend ## Property used to expose the backend # It is made static as the backend is not supposed to change during runtime. # This makes the connection between backend and QML more reliable than the pyqtSlot above. # \returns Backend \type{Backend} @pyqtProperty("QVariant", constant=True) def backend(self): return self.getBackend() ## Load a Qt translation catalog. # # This method will locate, load and install a Qt message catalog that can be used # by Qt's translation system, like qsTr() in QML files. # # \param file_name The file name to load, without extension. It will be searched for in # the i18nLocation Resources directory. If it can not be found a warning # will be logged but no error will be thrown. # \param language The language to load translations for. This can be any valid language code # or 'default' in which case the language is looked up based on system locale. # If the specified language can not be found, this method will fall back to # loading the english translations file. # # \note When `language` is `default`, the language to load can be changed with the # environment variable "LANGUAGE". def loadQtTranslation(self, file_name, language="default"): # TODO Add support for specifying a language from preferences path = None if language == "default": path = self._getDefaultLanguage(file_name) else: path = Resources.getPath(Resources.i18n, language, "LC_MESSAGES", file_name + ".qm") # If all else fails, fall back to english. if not path: Logger.log( "w", "Could not find any translations matching {0} for file {1}, falling back to english" .format(language, file_name)) try: path = Resources.getPath(Resources.i18n, "en_US", "LC_MESSAGES", file_name + ".qm") except FileNotFoundError: Logger.log( "w", "Could not find English translations for file {0}. Switching to developer english." .format(file_name)) return translator = QTranslator() if not translator.load(path): Logger.log("e", "Unable to load translations %s", file_name) return # Store a reference to the translator. # This prevents the translator from being destroyed before Qt has a chance to use it. self._translators[file_name] = translator # Finally, install the translator so Qt can use it. self.installTranslator(translator) def createSplash(self): if not self.getCommandLineOption("headless"): try: self._splash = self._createSplashScreen() except FileNotFoundError: self._splash = None else: if self._splash: self._splash.show() self.processEvents() ## Display text on the splash screen. def showSplashMessage(self, message): if not self._splash: self.createSplash() if self._splash: self._splash.showMessage(message, Qt.AlignHCenter | Qt.AlignVCenter) self.processEvents() elif self.getCommandLineOption("headless"): Logger.log("d", message) ## Close the splash screen after the application has started. def closeSplash(self): if self._splash: self._splash.close() self._splash = None ## Create a QML component from a qml file. # \param qml_file_path: The absolute file path to the root qml file. # \param context_properties: Optional dictionary containing the properties that will be set on the context of the # qml instance before creation. # \return None in case the creation failed (qml error), else it returns the qml instance. # \note If the creation fails, this function will ensure any errors are logged to the logging service. def createQmlComponent( self, qml_file_path: str, context_properties: Dict[str, "QObject"] = None) -> Optional["QObject"]: path = QUrl.fromLocalFile(qml_file_path) component = QQmlComponent(self._engine, path) result_context = QQmlContext(self._engine.rootContext()) if context_properties is not None: for name, value in context_properties.items(): result_context.setContextProperty(name, value) result = component.create(result_context) for err in component.errors(): Logger.log("e", str(err.toString())) if result is None: return None # We need to store the context with the qml object, else the context gets garbage collected and the qml objects # no longer function correctly/application crashes. result.attached_context = result_context return result def _createSplashScreen(self): return QSplashScreen( QPixmap( Resources.getPath(Resources.Images, self.getApplicationName() + ".png"))) def _screenScaleFactor(self): # OSX handles sizes of dialogs behind our backs, but other platforms need # to know about the device pixel ratio if sys.platform == "darwin": return 1.0 else: # determine a device pixel ratio from font metrics, using the same logic as UM.Theme fontPixelRatio = QFontMetrics( QCoreApplication.instance().font()).ascent() / 11 # round the font pixel ratio to quarters fontPixelRatio = int(fontPixelRatio * 4) / 4 return fontPixelRatio def _getDefaultLanguage(self, file_name): # If we have a language override set in the environment, try and use that. lang = os.getenv("URANIUM_LANGUAGE") if lang: try: return Resources.getPath(Resources.i18n, lang, "LC_MESSAGES", file_name + ".qm") except FileNotFoundError: pass # Else, try and get the current language from preferences lang = Preferences.getInstance().getValue("general/language") if lang: try: return Resources.getPath(Resources.i18n, lang, "LC_MESSAGES", file_name + ".qm") except FileNotFoundError: pass # If none of those are set, try to use the environment's LANGUAGE variable. lang = os.getenv("LANGUAGE") if lang: try: return Resources.getPath(Resources.i18n, lang, "LC_MESSAGES", file_name + ".qm") except FileNotFoundError: pass # If looking up the language from the enviroment or preferences fails, try and use Qt's system locale instead. locale = QLocale.system() # First, try and find a directory for any of the provided languages for lang in locale.uiLanguages(): try: return Resources.getPath(Resources.i18n, lang, "LC_MESSAGES", file_name + ".qm") except FileNotFoundError: pass # If that fails, see if we can extract a language code from the # preferred language, regardless of the country code. This will turn # "en-GB" into "en" for example. lang = locale.uiLanguages()[0] lang = lang[0:lang.find("-")] for subdirectory in os.path.listdir(Resources.getPath(Resources.i18n)): if subdirectory == "en_7S": #Never automatically go to Pirate. continue if not os.path.isdir( Resources.getPath(Resources.i18n, subdirectory)): continue if subdirectory.startswith( lang + "_"): #Only match the language code, not the country code. return Resources.getPath(Resources.i18n, lang, "LC_MESSAGES", file_name + ".qm") return None
class Window(QMainWindow, Ui_MainWindow): def __init__(self): super().__init__() self.setupUi(self) self.Tray() self.Listadd() self.step = 0 self.loop = 1 self.flag = self.listtag = self.fulltag = True self.player = vlc.MediaPlayer() qr = self.frameGeometry() cp = QDesktopWidget().availableGeometry().center() qr.moveCenter(cp) self.move(qr.topLeft()) def Tray(self): self.tp = QSystemTrayIcon(self) self.tp.setIcon(QIcon(idirC)) self.tp.activated.connect(self.Activated) self.tp.setToolTip('CPlayer') tpMenu = QMenu() a1 = QAction((QIcon(idirC)), '显示主页面', self, triggered=(self.Showmain)) a2 = QAction((QIcon(idirC)), '隐藏主页面', self, triggered=(self.Min)) a3 = QAction((QIcon(idirabout)), '关于', self, triggered=(self.About)) a4 = QAction((QIcon(idirexit)), '退出', self, triggered=(self.Quit)) tpMenu.addAction(a1) tpMenu.addAction(a2) tpMenu.addAction(a3) tpMenu.addAction(a4) self.tp.setContextMenu(tpMenu) self.tp.show() def closeEvent(self, event): event.ignore() self.hide() def Activated(self, reason): if reason == QSystemTrayIcon.MiddleClick: self.Min() else: if reason == QSystemTrayIcon.Trigger: self.Showmain() def Showmain(self): self.showNormal() self.activateWindow() def Min(self): self.hide() def About(self): QMessageBox.information( self, '关于', '作者:cnzb\nGithub:https://github.com/cnzbpy/simplepy\nGitee:https://gitee.com/cnzbpy/simplepy' ) def Quit(self): self.tp = None app.exit() def resizeEvent(self, event): self.ratio() def keyPressEvent(self, event): if event.key() == Qt.Key_P: self.Listhide() if event.key() == Qt.Key_T: self.Fastback() if event.key() == Qt.Key_L: self.Loop() if event.key() == Qt.Key_Space: self.Play() if event.key() == Qt.Key_S: self.Stop() if event.key() == Qt.Key_F: self.Full() if event.key() == Qt.Key_J: self.Fastforward() if event.key() == Qt.Key_M: self.Mute() if event.key() == Qt.Key_A: self.svolume.setValue(self.svolume.value() + 1) if event.key() == Qt.Key_R: self.svolume.setValue(self.svolume.value() - 1) def eventFilter(self, sender, event): if (event.type() == event.ChildRemoved): self.Moved() return False def Listmenu(self, position): lm = QMenu() addact = QAction("添加到播放列表", self, triggered=self.Add) removeact = QAction("从播放列表移除", self, triggered=self.Remove) renameact = QAction('重命名', self, triggered=self.Rename) clearact = QAction('清空播放列表', self, triggered=self.Clear) saveact = QAction('保存当前播放列表', self, triggered=self.Saved) lm.addAction(addact) if self.list.itemAt(position): lm.addAction(removeact) lm.addAction(renameact) lm.addAction(clearact) lm.addAction(saveact) lm.exec_(self.list.mapToGlobal(position)) def Listadd(self): self.l = [] self.list.installEventFilter(self) if os.path.isfile('CPlayerlist.txt'): with open('CPlayerlist.txt', 'rb') as f: playencode = (chardet.detect(f.read()))['encoding'] with open('CPlayerlist.txt', encoding=playencode, errors='ignore') as f: for i in f: i = i.strip() name = i[0:i.find(',')] filelist = i[i.find(',') + 1:len(i)] self.list.addItem(name) self.l.append(filelist) def Add(self): filelists, _ = QFileDialog.getOpenFileNames(self, '添加到播放列表', '.', '媒体文件(*)') for filelist in filelists: name = filelist[filelist.rfind('/') + 1:filelist.rfind('.')] self.list.addItem(name) self.l.append(filelist) def Remove(self): ltmp = [] for i in self.list.selectedIndexes(): ltmp.append(i.row()) ltmp.sort(reverse=True) for j in ltmp: self.list.takeItem(j) self.l.pop(j) def Rename(self): item = self.list.item(self.list.currentRow()) item.setFlags(item.flags() | Qt.ItemIsEditable) self.list.editItem(item) def Clear(self): self.l = [] self.list.clear() if os.path.isfile('CPlayerlist.txt'): os.remove('CPlayerlist.txt') def Drag(self): self.tmp1 = [] self.tmp2 = self.l[:] for i in range(self.list.count()): self.tmp1.append(self.list.item(i).text()) def Moved(self): for i in range(self.list.count()): if self.list.item(i).text() == self.tmp1[i]: continue else: self.l[i] = self.tmp2[self.tmp1.index( self.list.item(i).text())] def Saved(self): with open('CPlayerlist.txt', 'w') as f: for i in range(self.list.count()): f.write('%s,%s\n' % (self.list.item(i).text(), self.l[i])) QMessageBox.information(self, '保存', '播放列表保存成功!') def Listhide(self): if self.listtag: self.frame.hide() self.listtag = False else: self.frame.show() self.listtag = True self.ratio() def ratio(self): QApplication.processEvents() self.player.video_set_aspect_ratio( '%s:%s' % (self.lmedia.width(), self.lmedia.height())) def Loop(self): if self.loop == 0: self.loop = 1 self.bloop.setIcon(QIcon(idirwithloop)) self.bloop.setToolTip('循环播放,快捷键“l”') else: self.loop = 0 self.bloop.setIcon(QIcon(idirwithoutloop)) self.bloop.setToolTip('取消循环,快捷键“l”') def set_window(self, winid): if platform.system() == 'Windows': self.player.set_hwnd(winid) elif platform.system() == 'Linux': self.player.set_xwindow(winid) else: self.player.set_nsobject(winid) def Play(self): if self.flag: try: self.playitem = self.l[self.list.currentRow()] self.player.set_mrl("%s" % self.playitem) self.set_window(int(self.lmedia.winId())) self.ratio() self.player.play() self.timer = QTimer() self.timer.start(100) self.timer.timeout.connect(self.Show) self.steptimer = QTimer() self.steptimer.start(1000) self.steptimer.timeout.connect(self.Step) self.flag = False self.bplay.setIcon(QIcon(idirpause)) self.bplay.setToolTip('暂停,快捷键“Space”') except: QMessageBox.warning(self, '错误', '找不到要播放的文件!') else: if self.l[self.list.currentRow()] == self.playitem: if self.player.is_playing(): self.player.pause() self.steptimer.stop() self.bplay.setIcon(QIcon(idirplay)) self.bplay.setToolTip('播放,快捷键“Space”') else: self.player.play() self.steptimer.start() self.bplay.setIcon(QIcon(idirpause)) self.bplay.setToolTip('暂停,快捷键“Space”') else: self.playitem = self.l[self.list.currentRow()] self.step = 0 self.stime.setValue(0) self.player.set_mrl("%s" % self.playitem) self.player.play() self.timer.start() self.steptimer.start() self.bplay.setIcon(QIcon(idirpause)) self.bplay.setToolTip('暂停,快捷键“Space”') def Show(self): self.mediatime = self.player.get_length() / 1000 self.stime.setMaximum(int(self.mediatime)) mediamin, mediasec = divmod(self.mediatime, 60) mediahour, mediamin = divmod(mediamin, 60) playmin, playsec = divmod(self.step, 60) playhour, playmin = divmod(playmin, 60) self.ltime.setText( '%02d:%02d:%02d/%02d:%02d:%02d' % (playhour, playmin, playsec, mediahour, mediamin, mediasec)) def Stop(self): if self.flag == False: Thread(target=self.Threadstop, daemon=True).start() self.timer.stop() self.steptimer.stop() self.step = 0 self.loop = 1 self.flag = True self.stime.setValue(0) self.ltime.setText('') self.bplay.setIcon(QIcon(idirplay)) self.bplay.setToolTip('播放,快捷键“Space”') def Threadstop(self): self.player.stop() def Full(self): if self.fulltag: self.frame.hide() self.frame_2.hide() self.showFullScreen() self.bfull.setIcon(QIcon(idirexitfullscreen)) self.bfull.setToolTip('退出全屏,快捷键“f”') self.fulltag = False else: self.frame.show() self.frame_2.show() self.showNormal() self.bfull.setIcon(QIcon(idirexpandfullscreen)) self.bfull.setToolTip('全屏,快捷键“f”') self.fulltag = True def Curvol(self): self.curvol = self.svolume.value() def Mute(self): if self.flag == False: if self.player.audio_get_volume() != 0: self.player.audio_set_volume(0) self.bmute.setIcon(QIcon(idirwithoutvolume)) self.bmute.setToolTip('取消静音,快捷键“m”') self.tag = False else: if self.svolume.value() != 0: self.player.audio_set_volume(self.svolume.value()) else: self.player.audio_set_volume(self.curvol) self.svolume.setValue(self.curvol) self.bmute.setIcon(QIcon(idirwithvolume)) self.bmute.setToolTip('静音,快捷键“m”') self.tag = True def Volume(self): if self.flag == False: if self.svolume.value() == 0: self.bmute.setIcon(QIcon(idirwithoutvolume)) self.bmute.setToolTip('取消静音,快捷键“m”') else: self.bmute.setIcon(QIcon(idirwithvolume)) self.bmute.setToolTip('静音,快捷键“m”') self.player.audio_set_volume(self.svolume.value()) def Step(self): if self.step >= int(self.mediatime): self.step = int(self.mediatime) if self.loop == 0: self.step = 0 self.stime.setValue(0) self.flag = True self.Play() else: if not self.player.is_playing( ) and self.player.get_state != vlc.State.Paused: self.Stop() else: self.step += 1 self.stime.setValue(self.step) def Slidechanged(self): self.step = self.stime.value() def Slidemoved(self): if self.flag == False: self.player.set_position(self.step / int(self.mediatime)) def Fastforward(self): if self.flag == False: self.step += 10 if self.step >= int(self.mediatime): self.stime.setValue(int(self.mediatime)) self.stime.setValue(self.step) self.player.set_position(self.step / int(self.mediatime)) def Fastback(self): if self.flag == False: self.step -= 10 if self.step <= 0: self.step = 0 self.stime.setValue(0) self.stime.setValue(self.step) self.player.set_position(self.step / int(self.mediatime))
def __init__(self): super(PVPNApplet, self).__init__() self.country_codes = country_codes # Keep a list of country codes # Init QSystemTrayIcon self.tray_icon = QSystemTrayIcon(self) self.tray_icon.setIcon(QIcon('icons/16x16/protonvpn-disconnected.png')) # Init libnotify Notify.init('ProtonVPN') # Refresh server list, store the resulting servers so we can populate the menu self.servers = self.update_available_servers() # Menu actions connect_fastest_action = QAction('Connect fastest', self) reconnect_action = QAction('Reconnect', self) disconnect_action = QAction('Disconnect', self) status_action = QAction('Status', self) connect_fastest_sc_action = QAction('Secure Core', self) connect_fastest_p2p_action = QAction('P2P', self) connect_fastest_tor_action = QAction('Tor', self) connect_random_action = QAction('Random', self) show_protonvpn_applet_version_action = QAction('About ProtonVPN-Applet', self) show_protonvpn_version_action = QAction('About ProtonVPN', self) quit_action = QAction('Exit', self) self.show_notifications_action = QAction('Show Notifications') self.show_notifications_action.setCheckable(True) self.show_notifications_action.setChecked(False) # Triggers quit_action.triggered.connect(qApp.quit) connect_fastest_action.triggered.connect(self.connect_fastest) disconnect_action.triggered.connect(self.disconnect_vpn) status_action.triggered.connect(self.status_vpn) show_protonvpn_applet_version_action.triggered.connect(self.show_protonvpn_applet_version) show_protonvpn_version_action.triggered.connect(self.get_protonvpn_version) connect_fastest_sc_action.triggered.connect(self.connect_fastest_sc) connect_fastest_p2p_action.triggered.connect(self.connect_fastest_p2p) connect_fastest_tor_action.triggered.connect(self.connect_fastest_tor) connect_random_action.triggered.connect(self.connect_random) reconnect_action.triggered.connect(self.reconnect_vpn) # Generate connection menu for specific countries connect_country_actions = [] for country_name in self.get_available_countries(self.servers): # Get the ISO-3166 Alpha-2 country code country_name_to_code = {v: k for k, v in country_codes.country_codes.items()} country_code = country_name_to_code[country_name] # Dynamically create functions for connecting to each country; each function just passes its respective # country code to `self.connect_fastest_cc()` setattr(self, f'connect_fastest_{country_code}', functools.partial(self.connect_fastest_cc, country_code)) # Generate an action for each country; set up the trigger; append to actions list country_action = QAction(f'{country_name}', self) country_action.triggered.connect(getattr(self, f'connect_fastest_{country_code}')) connect_country_actions.append(country_action) # Create a scrollable country connection menu connect_country_menu = QMenu("Country...", self) connect_country_menu.setStyleSheet('QMenu { menu-scrollable: 1; }') connect_country_menu.addActions(connect_country_actions) # Generate connection menu connection_menu = QMenu("Other connections...", self) connection_menu.addMenu(connect_country_menu) connection_menu.addAction(connect_fastest_sc_action) connection_menu.addAction(connect_fastest_p2p_action) connection_menu.addAction(connect_fastest_tor_action) connection_menu.addAction(connect_random_action) # Draw menu tray_menu = QMenu() tray_menu.addAction(connect_fastest_action) tray_menu.addAction(reconnect_action) tray_menu.addMenu(connection_menu) tray_menu.addAction(disconnect_action) tray_menu.addAction(status_action) tray_menu.addSeparator() tray_menu.addAction(self.show_notifications_action) tray_menu.addAction(show_protonvpn_applet_version_action) tray_menu.addAction(show_protonvpn_version_action) tray_menu.addAction(quit_action) self.tray_icon.setContextMenu(tray_menu) self.tray_icon.show() # Polling thread self.start_polling()
class MainWindow(QMainWindow): def __init__(self): QMainWindow.__init__(self) self.ui = Ui_MainWindow() self.ui.setupUi(self) self.ui.actionAdd_camera.triggered.connect(self.handle_add_camera_triggered) self.ui.actionOpen_dataset.triggered.connect(self.handle_open_dataset_triggered) self.ui.actionSave_dataset.triggered.connect(self.handle_save_dataset_triggered) self.ui.actionClose_dataset.triggered.connect(self.handle_close_dataset_triggered) self.ui.actionExport_to_JSON.triggered.connect(self.handle_export_to_json_triggered) self.startup_page = QWidget() self.startup_page_ui = Ui_StartupPage() self.startup_page_ui.setupUi(self.startup_page) self.startup_page_ui.addCamera_button.setDefaultAction(self.ui.actionAdd_camera) self.startup_page_ui.openDataset_button.setDefaultAction(self.ui.actionOpen_dataset) self.startup_page_ui.openDataset_button.setMenu(None) self.startup_page_ui.openDataset_button.setArrowType(Qt.NoArrow) self.startup_page_ui.label_recent_datasets_image_placeholder.setPixmap( QPixmap(':/icons/document-open-recent.svg')) self.startup_page_ui.label_recent_cameras_image_placeholder.setPixmap( QPixmap(':/icons/document-open-recent.svg')) self.startup_dataset_buttons: List[QToolButton] = [] self.startup_camera_buttons: List[QToolButton] = [] self.state = self.get_config_state() self.recent_datasets: List[Path] = [] self.recent_datasets_menu = QMenu() self.recent_datasets_menu.setToolTipsVisible(True) self.recent_dataset_qactions = [] for ds in self.state['recent_datasets']: self.add_dataset_entry_to_recent(Path(ds)) if len(self.recent_datasets) == 0: self.startup_page_ui.verticalLayout_recentDatasets.addWidget(self.startup_page_ui.label_noRecentDatasets) self.startup_page_ui.label_noRecentDatasets.setAlignment(Qt.AlignHCenter) self.startup_page_ui.label_noRecentDatasets.show() self.recent_datasets_menu.triggered.connect(lambda action: self.open_dataset(Path(action.toolTip()))) self.ui.actionOpen_dataset.setMenu(self.recent_datasets_menu) self.recent_cameras: List[Path] = [] self.recent_cameras_menu = QMenu() self.recent_cameras_menu.setToolTipsVisible(True) self.recent_cameras_qactions: Dict[str, QAction] = {} for cam in self.state['recent_cameras']: self.add_camera_entry_to_recent(Path(cam)) if len(self.recent_cameras) == 0: self.startup_page_ui.verticalLayout_recentCameras.addWidget(self.startup_page_ui.label_noRecentCameras) self.startup_page_ui.label_noRecentCameras.setAlignment(Qt.AlignHCenter) self.startup_page_ui.label_noRecentCameras.show() self.recent_cameras_menu.triggered.connect(self.handle_add_recent_camera_triggered) self.ui.actionAdd_camera.setMenu(self.recent_cameras_menu) self.dataset = Dataset() self.processing_widget = CameraProcessingWidget() self.processing_widget.set_dataset(self.dataset) self.processing_widget.camera_loaded.connect(self.handle_camera_added) self.processing_widget.processing_started.connect(self.show_progress_bar) self.processing_widget.processing_updated.connect(self.update_progress_bar) self.processing_widget.processing_stopped.connect(self.hide_progress_bar) self.processing_widget.stick_verification_needed.connect(self.notify_user) self.processing_widget.no_cameras_open.connect(self.handle_no_cameras_open) self.ui.stackedWidget.removeWidget(self.ui.page) self.ui.stackedWidget.removeWidget(self.ui.page_2) self.ui.stackedWidget.addWidget(self.startup_page) self.ui.stackedWidget.addWidget(self.processing_widget) self.ui.stackedWidget.setCurrentIndex(0) self.setCentralWidget(self.ui.stackedWidget) self.ui.toolBar.hide() self.progress_bar = QProgressBar() self.progress_bar.hide() skipped_q_indicator = QPixmap(24, 24) skipped_q_indicator.fill(QColor(150, 150, 150)) skipped_label = QLabel() skipped_label.setPixmap(skipped_q_indicator) bad_q_indicator = QPixmap(24, 24) bad_q_indicator.fill(QColor(200, 0, 0)) bad_label = QLabel() bad_label.setPixmap(bad_q_indicator) ok_q_indicator = QPixmap(24, 24) ok_q_indicator.fill(QColor(200, 100, 0)) ok_label = QLabel() ok_label.setPixmap(ok_q_indicator) good_q_indicator = QPixmap(24, 24) good_q_indicator.fill(QColor(100, 200, 0)) good_label = QLabel() good_label.setPixmap(good_q_indicator) status_box = QHBoxLayout() indicator_box = QHBoxLayout() self.statusBar().hide() indicator_box.addWidget(QLabel("Stick visibility:\t")) indicator_box.addWidget(skipped_label) indicator_box.addWidget(QLabel(" Skipped ")) indicator_box.addWidget(bad_label) indicator_box.addWidget(QLabel(" Bad ")) indicator_box.addWidget(ok_label) indicator_box.addWidget(QLabel(" OK ")) indicator_box.addWidget(good_label) indicator_box.addWidget(QLabel(" Good ")) status_box.addItem(indicator_box) status_box.addWidget(self.progress_bar) status_box.setAlignment(indicator_box, Qt.AlignLeft) status_box.setSpacing(100) status_widget = QWidget() status_widget.setLayout(status_box) self.statusBar().addPermanentWidget(status_widget, 1) self.progress_bar.setFormat("%v / %m") self.sys_tray = QSystemTrayIcon(QIcon(':icons/snowflake.svg')) self.sys_tray.show() self.thread_pool = QThreadPool() def handle_save_dataset_triggered(self, checked: bool): if self.dataset.path == Path("."): file_dialog = QFileDialog(self) file_dialog.setWindowTitle("Save dataset") file_dialog.setFileMode(QFileDialog.AnyFile) file_dialog.setNameFilter("*.json") file_dialog.setAcceptMode(QFileDialog.AcceptSave) if file_dialog.exec_(): file_path = Path(file_dialog.selectedFiles()[0]) self.dataset.save_as(file_path) self.add_dataset_entry_to_recent(file_path) self.save_state() else: self.dataset.save() def handle_add_camera_triggered(self, checked: bool): file_dialog = QFileDialog(self) file_dialog.setFileMode(QFileDialog.Directory) if file_dialog.exec_(): if self.dataset.add_camera(Path(file_dialog.selectedFiles()[0])): self.add_camera_entry_to_recent(Path(file_dialog.selectedFiles()[0])) def handle_open_dataset_triggered(self, checked: bool): file_dialog = QFileDialog(self) file_dialog.setNameFilter("*.json") file_dialog.setFileMode(QFileDialog.AnyFile) file_dialog.setWindowTitle("Open dataset file") if file_dialog.exec_(): file_path = Path(file_dialog.selectedFiles()[0]) if not self.open_dataset(file_path): return self.setWindowTitle(str(self.dataset.path)) self.add_dataset_entry_to_recent(file_path) self.ui.actionOpen_dataset.setMenu(self.recent_datasets_menu) self.save_state() def handle_add_recent_camera_triggered(self, action: QAction): if self.dataset.add_camera(Path(action.toolTip())): self.add_camera_entry_to_recent(Path(action.toolTip())) else: QMessageBox.critical(self, "Failed to open camera folder", f'Could not open {action.toolTip()}') def connect_dataset_signals(self): self.dataset.camera_added.connect(self.handle_camera_added) def save_state(self): try: with open(Path(sys.argv[0]).parent / 'state.json', 'w') as f: self.state['recent_datasets'] = list(reversed(list(map(lambda p: str(p), self.recent_datasets)))) self.state['recent_cameras'] = list(reversed(list(map(lambda p: str(p), self.recent_cameras)))) json.dump(self.state, f) except PermissionError: pass def add_camera_entry_to_recent(self, p: Path): if not self.startup_page_ui.label_noRecentCameras.isHidden(): self.startup_page_ui.label_noRecentCameras.hide() self.startup_page_ui.verticalLayout_recentCameras.removeWidget(self.startup_page_ui.label_noRecentCameras) if p not in self.recent_cameras: self.recent_cameras.insert(0, p) action = QAction(str(p.name)) action.setToolTip(str(p)) self.recent_cameras_qactions[str(p)] = action if len(self.recent_cameras_menu.actions()) > 0: self.recent_cameras_menu.insertAction(self.recent_cameras_menu.actions()[0], action) else: self.recent_cameras_menu.addAction(action) btn = QToolButton() btn.setDefaultAction(action) btn.setStyleSheet('font-size: 12pt') btn.setText(str(p.name)) btn.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed) self.startup_camera_buttons.insert(0, btn) self.startup_page_ui.label_noRecentCameras.hide() self.startup_page_ui.verticalLayout_recentCameras.insertWidget(0, btn) else: action_idx = self.recent_cameras_menu.actions().index(self.recent_cameras_qactions[str(p)]) btn = self.startup_page_ui.verticalLayout_recentCameras.itemAt(action_idx).widget() #self.startup_page_ui.verticalLayout_recentCameras.removeItem(btn) self.startup_page_ui.verticalLayout_recentCameras.removeWidget(btn) self.startup_page_ui.verticalLayout_recentCameras.insertWidget(0, btn) self.recent_cameras.remove(p) self.recent_cameras.insert(0, p) self.recent_cameras_menu.clear() for i in range(self.startup_page_ui.verticalLayout_recentCameras.count()): btn: QToolButton = self.startup_page_ui.verticalLayout_recentCameras.itemAt(i).widget() self.recent_cameras_menu.addAction(btn.defaultAction()) if len(self.recent_cameras) > 10: self.remove_camera_entry_from_recent(self.recent_cameras[-1]) def add_dataset_entry_to_recent(self, p: Path): if not self.startup_page_ui.label_noRecentDatasets.isHidden(): self.startup_page_ui.label_noRecentDatasets.hide() self.startup_page_ui.verticalLayout_recentDatasets.removeWidget(self.startup_page_ui.label_noRecentDatasets) if p not in self.recent_datasets: self.recent_datasets.insert(0, p) action = QAction(str(p.name)) action.setToolTip(str(p)) self.recent_dataset_qactions.insert(0, action) if len(self.recent_datasets_menu.actions()) > 0: self.recent_datasets_menu.insertAction(self.recent_datasets_menu.actions()[0], action) else: self.recent_datasets_menu.addAction(action) btn = QToolButton() btn.setDefaultAction(action) btn.setStyleSheet('font-size: 12pt') btn.setText(str(p)) btn.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed) self.startup_page_ui.verticalLayout_recentDatasets.insertWidget(0, btn) self.startup_dataset_buttons.append(btn) if len(self.recent_datasets) > 10: self.remove_dataset_entry_from_recent(self.recent_datasets[-1]) def remove_dataset_entry_from_recent(self, path: Path): actions = list(filter(lambda action: action.toolTip() == str(path), self.recent_dataset_qactions)) if len(actions) > 0: action = actions[0] btn = list(filter(lambda btn_: btn_.defaultAction() == action, self.startup_dataset_buttons))[0] self.startup_dataset_buttons.remove(btn) self.startup_page_ui.verticalLayout_recentDatasets.removeWidget(btn) self.recent_dataset_qactions.remove(action) self.recent_datasets_menu.removeAction(action) self.recent_datasets.remove(path) btn.setVisible(False) btn.deleteLater() if len(self.recent_datasets) == 0: self.startup_page_ui.verticalLayout_recentDatasets.addWidget(self.startup_page_ui.label_noRecentDatasets) self.startup_page_ui.label_noRecentDatasets.setAlignment(Qt.AlignHCenter) self.startup_page_ui.label_noRecentDatasets.show() def remove_camera_entry_from_recent(self, path: Path): action = self.recent_cameras_qactions[str(path)] btn = list(filter(lambda btn_: btn_.defaultAction() == action, self.startup_camera_buttons))[0] self.startup_camera_buttons.remove(btn) self.startup_page_ui.verticalLayout_recentCameras.removeWidget(btn) del self.recent_cameras_qactions[str(path)] self.recent_cameras_menu.removeAction(action) self.recent_cameras.remove(path) btn.setVisible(False) btn.deleteLater() if len(self.recent_cameras) == 0: self.startup_page_ui.verticalLayout_recentCameras.addWidget(self.startup_page_ui.label_noRecentCameras) self.startup_page_ui.label_noRecentCameras.setAlignment(Qt.AlignHCenter) self.startup_page_ui.label_noRecentCameras.show() def open_dataset(self, path: Path) -> bool: if len(self.dataset.cameras) > 0: self.dataset.save() if not os.access(path, mode=os.F_OK): self.remove_dataset_entry_from_recent(path) msg_box = QMessageBox(QMessageBox.Warning, 'File not found', f'The file {str(path)} does not exist.', QMessageBox.Open | QMessageBox.Close) if msg_box.exec_() == QMessageBox.Open: self.ui.actionOpen_dataset.trigger() else: msg_box.close() return False elif not os.access(path, mode=os.W_OK): self.remove_dataset_entry_from_recent(path) msg_box = QMessageBox(QMessageBox.Warning, 'Permission denied', f'The application can\'t modify the ' f'file {str(path)}', QMessageBox.Close) msg_box.exec_() return False self.dataset = Dataset() self.processing_widget.set_dataset(self.dataset) if not self.dataset.load_from(path): self.processing_widget.cleanup() self.ui.stackedWidget.setCurrentIndex(0) return False return True @staticmethod def get_config_state(): try: with open(Path(sys.argv[0]).parent / 'state.json', 'r') as f: state = json.load(f) if sorted(list(state.keys())) != ['first_time_startup', 'recent_cameras', 'recent_datasets']: raise AttributeError return state except (FileNotFoundError, AttributeError) as _: return { 'first_time_startup': True, 'recent_cameras': [], 'recent_datasets': [], } def closeEvent(self, event: QCloseEvent) -> None: if self.processing_widget is not None: self.processing_widget.cleanup() self.save_state() super().closeEvent(event) def handle_close_dataset_triggered(self): self.dataset.save() self.processing_widget.cleanup() self.dataset = Dataset() self.processing_widget.set_dataset(self.dataset) self.ui.stackedWidget.setCurrentIndex(0) self.ui.toolBar.hide() self.statusBar().hide() def handle_camera_added(self): self.ui.stackedWidget.setCurrentIndex(1) self.ui.toolBar.show() self.statusBar().show() def show_progress_bar(self, cam_name: str, photo_count: int): self.statusBar().showMessage(f'Processing camera {cam_name}', 0) self.progress_bar.setMinimum(0) self.progress_bar.setMaximum(photo_count) self.progress_bar.setValue(0) self.progress_bar.show() def update_progress_bar(self, value: int): self.progress_bar.setValue(value) def hide_progress_bar(self, val: int): self.statusBar().clearMessage() self.progress_bar.hide() def notify_user(self, camera_name: str): self.sys_tray.showMessage("Verification needed", f'Camera {camera_name} requires verification of stick positions', QSystemTrayIcon.NoIcon, 5000) def handle_no_cameras_open(self): self.ui.stackedWidget.setCurrentIndex(0) self.ui.toolBar.hide() self.statusBar().hide() def handle_export_to_json_triggered(self): file_dialog = QFileDialog(self) file_dialog.setWindowTitle("Save measurements") file_dialog.setFileMode(QFileDialog.AnyFile) file_dialog.setNameFilter("*.json") file_dialog.setAcceptMode(QFileDialog.AcceptSave) if file_dialog.exec_(): file_path = Path(file_dialog.selectedFiles()[0]) dat = self.processing_widget.dataset.get_json_data() with open(file_path, "w") as f: f.write(dat)
class PVPNApplet(QMainWindow): """Main applet body """ tray_icon = None polling = True previous_status = None #auth = 'pkexec' auth = 'sudo' # Override the class constructor def __init__(self): super(PVPNApplet, self).__init__() self.country_codes = country_codes # Keep a list of country codes # Init QSystemTrayIcon self.tray_icon = QSystemTrayIcon(self) self.tray_icon.setIcon(QIcon('icons/16x16/protonvpn-disconnected.png')) # Init libnotify Notify.init('ProtonVPN') # Refresh server list, store the resulting servers so we can populate the menu self.servers = self.update_available_servers() # Menu actions connect_fastest_action = QAction('Connect fastest', self) reconnect_action = QAction('Reconnect', self) disconnect_action = QAction('Disconnect', self) status_action = QAction('Status', self) connect_fastest_sc_action = QAction('Secure Core', self) connect_fastest_p2p_action = QAction('P2P', self) connect_fastest_tor_action = QAction('Tor', self) connect_random_action = QAction('Random', self) show_protonvpn_applet_version_action = QAction('About ProtonVPN-Applet', self) show_protonvpn_version_action = QAction('About ProtonVPN', self) quit_action = QAction('Exit', self) self.show_notifications_action = QAction('Show Notifications') self.show_notifications_action.setCheckable(True) self.show_notifications_action.setChecked(False) # Triggers quit_action.triggered.connect(qApp.quit) connect_fastest_action.triggered.connect(self.connect_fastest) disconnect_action.triggered.connect(self.disconnect_vpn) status_action.triggered.connect(self.status_vpn) show_protonvpn_applet_version_action.triggered.connect(self.show_protonvpn_applet_version) show_protonvpn_version_action.triggered.connect(self.get_protonvpn_version) connect_fastest_sc_action.triggered.connect(self.connect_fastest_sc) connect_fastest_p2p_action.triggered.connect(self.connect_fastest_p2p) connect_fastest_tor_action.triggered.connect(self.connect_fastest_tor) connect_random_action.triggered.connect(self.connect_random) reconnect_action.triggered.connect(self.reconnect_vpn) # Generate connection menu for specific countries connect_country_actions = [] for country_name in self.get_available_countries(self.servers): # Get the ISO-3166 Alpha-2 country code country_name_to_code = {v: k for k, v in country_codes.country_codes.items()} country_code = country_name_to_code[country_name] # Dynamically create functions for connecting to each country; each function just passes its respective # country code to `self.connect_fastest_cc()` setattr(self, f'connect_fastest_{country_code}', functools.partial(self.connect_fastest_cc, country_code)) # Generate an action for each country; set up the trigger; append to actions list country_action = QAction(f'{country_name}', self) country_action.triggered.connect(getattr(self, f'connect_fastest_{country_code}')) connect_country_actions.append(country_action) # Create a scrollable country connection menu connect_country_menu = QMenu("Country...", self) connect_country_menu.setStyleSheet('QMenu { menu-scrollable: 1; }') connect_country_menu.addActions(connect_country_actions) # Generate connection menu connection_menu = QMenu("Other connections...", self) connection_menu.addMenu(connect_country_menu) connection_menu.addAction(connect_fastest_sc_action) connection_menu.addAction(connect_fastest_p2p_action) connection_menu.addAction(connect_fastest_tor_action) connection_menu.addAction(connect_random_action) # Draw menu tray_menu = QMenu() tray_menu.addAction(connect_fastest_action) tray_menu.addAction(reconnect_action) tray_menu.addMenu(connection_menu) tray_menu.addAction(disconnect_action) tray_menu.addAction(status_action) tray_menu.addSeparator() tray_menu.addAction(self.show_notifications_action) tray_menu.addAction(show_protonvpn_applet_version_action) tray_menu.addAction(show_protonvpn_version_action) tray_menu.addAction(quit_action) self.tray_icon.setContextMenu(tray_menu) self.tray_icon.show() # Polling thread self.start_polling() def is_polling(self): return self.polling def kill_polling(self): self.polling = False def start_polling(self): self.polling = True self.polling_thread = Polling(self) self.polling_thread.start() def _connect_vpn(self, command): self.kill_polling() connect_thread = ConnectVPN(self, command) connect_thread.finished.connect(self.start_polling) connect_thread.start() def connect_fastest(self): self._connect_vpn(VPNCommand.connect_fastest.value) def connect_fastest_p2p(self): self._connect_vpn(VPNCommand.connect_fastest_p2p.value) def connect_fastest_sc(self): self._connect_vpn(VPNCommand.connect_fastest_sc.value) def connect_fastest_cc(self, cc): command = VPNCommand.connect_fastest_cc.value + f' {cc}' self._connect_vpn(command) def connect_fastest_tor(self): self._connect_vpn(VPNCommand.connect_fastest_tor.value) def connect_random(self): self._connect_vpn(VPNCommand.connect_random.value) def disconnect_vpn(self): disconnect_thread = DisconnectVPN(self) disconnect_thread.start() def status_vpn(self): status_thread = CheckStatus(self) status_thread.start() def reconnect_vpn(self): reconnect_thread = ReconnectVPN(self) reconnect_thread.start() # Override closeEvent to intercept the window closing event def closeEvent(self, event): event.ignore() self.hide() def show_notifications(self): return self.show_notifications_action.isChecked() def show_protonvpn_applet_version(self): """Show the protonvpn-applet version. """ name = '© 2020 Dónal Murray' email = '*****@*****.**' github = 'https://github.com/seadanda/protonvpn-applet' info = [f'<center>Version: {PROTONVPN_APPLET_VERSION}', f'{name}', f"<a href='{email}'>{email}</a>", f"<a href='{github}'>{github}</a></center>"] centered_text = f'<center>{"<br>".join(info)}</center>' QMessageBox.information(self, 'protonvpn-applet', centered_text) def get_protonvpn_version(self): """Start the CheckProtonVPNVersion thread; when it gets the version, it will call `self.show_protonvpn_version` """ print('called get_protonvpn_version') check_protonvpn_version_thread = CheckProtonVPNVersion(self) check_protonvpn_version_thread.protonvpn_version_ready.connect(self.show_protonvpn_version) check_protonvpn_version_thread.start() def show_protonvpn_version(self, version): """ Show the ProtonVPN version in a QMessageBox. Parameters ---------- version : str Version number to be shown. """ print('called show_protonvpn_version') QMessageBox.information(self, 'ProtonVPN Version', f'Version: {version}') def update_available_servers(self): utils.pull_server_data() return utils.get_servers() @staticmethod def get_available_countries(servers): return sorted(list({utils.get_country_name(server['ExitCountry']) for server in servers}))
def __init__(self, parent=None): super().__init__() self.threads = [] self.ignoreQuit = True self.columnWidth = 100 self.numRow = 20 self.numColumn = 2 self.validDate = ["mon", "tues", "wed", "thurs", "fri", "sat", "sun"] self.AM = "am" self.PM = "pm" self.data_path = os.path.abspath("UserData/GalaData.json") self.icon_path = os.path.abspath("Icon/orange.png") self.trayMenu = QMenu(self) self.trayMenu.addAction("Open", self.open_) self.trayMenu.addAction("Hide", self.hide) self.trayMenu.addAction("Quit", self.quit) self.tray = QSystemTrayIcon(QIcon(self.icon_path), self) self.tray.setContextMenu(self.trayMenu) self.tray.activated.connect(self.onClickEvent) self.tray.show() self.firstHeader = "Time" self.secondHeader = "Description" self.table = QTableWidget(self) self.table.setRowCount(self.numRow) self.table.setColumnCount(self.numColumn) self.table.setHorizontalHeaderLabels([self.firstHeader, self.secondHeader]) # self.table.setColumnWidth(0, self.columnWidth) # self.table.setColumnWidth(1, self.columnWidth) self.tableScrollW = self.table.verticalScrollBar().sizeHint().width() self.tableHeaderW = self.table.horizontalHeader().length() self.tableVertHeaderW = self.table.verticalHeader().width() self.tableFrameW = self.table.frameWidth() * 2 self.tableWidth = (self.tableScrollW + self.tableHeaderW + self.tableFrameW) self.table.setFixedWidth(self.tableWidth) self.table.verticalHeader().hide() self.header = self.table.horizontalHeader() self.header.setSectionResizeMode(0, QHeaderView.Interactive) self.header.setSectionResizeMode(1, QHeaderView.Stretch) self.headerMidPoint = self.header.length() / 2 self.header.setMinimumSectionSize(self.headerMidPoint * 0.10) self.header.setMaximumSectionSize(self.headerMidPoint * 1.90) self.saveButton = self.createButton("Save", self.saveButtonClick) self.galaButton = self.createButton("Gala", self.galaButtonClick) self.loadButton = self.createButton("Load", self.loadButtonClick) self.infoButton = self.createButton("Info", self.infoButtonClick) # self.checkButton = self.createButton("Check", self.checkButtonClick) self.clearButton = self.createButton("Clear", self.clearButtonClick) layout = QGridLayout(self) layout.addWidget(self.table, 0, 0, 1, 6) layout.addWidget(self.loadButton, 1, 0) layout.addWidget(self.saveButton, 1, 1) layout.addWidget(self.clearButton, 1, 2) # layout.addWidget(self.checkButton, 1, 3) layout.addWidget(self.infoButton, 1, 4) layout.addWidget(self.galaButton, 1, 5) # only vertical resize allowed # layout.setSizeConstraint(QLayout.SetFixedSize) self.setLayout(layout) self.autoLoad() # load user data height = self.table.verticalHeader().width() * 20 width = self.sizeHint().width() self.resize(width, height) self.setWindowIcon(QIcon(self.icon_path)) self.setFixedWidth(width) self.setWindowTitle("Gala")
def __init__(self, parent=None): super(MainWindow, self).__init__() self.statusBar().showMessage("Move Dial to Deform Microphone Voice !.") self.setWindowTitle(__doc__) self.setMinimumSize(240, 240) self.setMaximumSize(480, 480) self.resize(self.minimumSize()) self.setWindowIcon(QIcon.fromTheme("audio-input-microphone")) self.tray = QSystemTrayIcon(self) self.center() QShortcut("Ctrl+q", self, activated=lambda: self.close()) self.menuBar().addMenu("&File").addAction("Quit", lambda: exit()) self.menuBar().addMenu("Sound").addAction( "STOP !", lambda: call("killall rec", shell=True) ) windowMenu = self.menuBar().addMenu("&Window") windowMenu.addAction("Hide", lambda: self.hide()) windowMenu.addAction("Minimize", lambda: self.showMinimized()) windowMenu.addAction("Maximize", lambda: self.showMaximized()) windowMenu.addAction("Restore", lambda: self.showNormal()) windowMenu.addAction("FullScreen", lambda: self.showFullScreen()) windowMenu.addAction("Center", lambda: self.center()) windowMenu.addAction("Top-Left", lambda: self.move(0, 0)) windowMenu.addAction("To Mouse", lambda: self.move_to_mouse_position()) # widgets group0 = QGroupBox("Voice Deformation") self.setCentralWidget(group0) self.process = QProcess(self) self.process.error.connect( lambda: self.statusBar().showMessage("Info: Process Killed", 5000) ) self.control = QDial() self.control.setRange(-10, 20) self.control.setSingleStep(5) self.control.setValue(0) self.control.setCursor(QCursor(Qt.OpenHandCursor)) self.control.sliderPressed.connect( lambda: self.control.setCursor(QCursor(Qt.ClosedHandCursor)) ) self.control.sliderReleased.connect( lambda: self.control.setCursor(QCursor(Qt.OpenHandCursor)) ) self.control.valueChanged.connect( lambda: self.control.setToolTip(f"<b>{self.control.value()}") ) self.control.valueChanged.connect( lambda: self.statusBar().showMessage( f"Voice deformation: {self.control.value()}", 5000 ) ) self.control.valueChanged.connect(self.run) self.control.valueChanged.connect(lambda: self.process.kill()) # Graphic effect self.glow = QGraphicsDropShadowEffect(self) self.glow.setOffset(0) self.glow.setBlurRadius(99) self.glow.setColor(QColor(99, 255, 255)) self.control.setGraphicsEffect(self.glow) self.glow.setEnabled(False) # Timer to start self.slider_timer = QTimer(self) self.slider_timer.setSingleShot(True) self.slider_timer.timeout.connect(self.on_slider_timer_timeout) # an icon and set focus QLabel(self.control).setPixmap( QIcon.fromTheme("audio-input-microphone").pixmap(32) ) self.control.setFocus() QVBoxLayout(group0).addWidget(self.control) self.menu = QMenu(__doc__) self.menu.addAction(__doc__).setDisabled(True) self.menu.setIcon(self.windowIcon()) self.menu.addSeparator() self.menu.addAction( "Show / Hide", lambda: self.hide() if self.isVisible() else self.showNormal(), ) self.menu.addAction("STOP !", lambda: call("killall rec", shell=True)) self.menu.addSeparator() self.menu.addAction("Quit", lambda: exit()) self.tray.setContextMenu(self.menu) self.make_trayicon()
class Gala(QWidget): """ Main window that holds the main layout """ MAX_TIME = 604800 # self.maxTime == Monday 12:00 am or 0 seconds def __init__(self, parent=None): super().__init__() self.threads = [] self.ignoreQuit = True self.columnWidth = 100 self.numRow = 20 self.numColumn = 2 self.validDate = ["mon", "tues", "wed", "thurs", "fri", "sat", "sun"] self.AM = "am" self.PM = "pm" self.data_path = os.path.abspath("UserData/GalaData.json") self.icon_path = os.path.abspath("Icon/orange.png") self.trayMenu = QMenu(self) self.trayMenu.addAction("Open", self.open_) self.trayMenu.addAction("Hide", self.hide) self.trayMenu.addAction("Quit", self.quit) self.tray = QSystemTrayIcon(QIcon(self.icon_path), self) self.tray.setContextMenu(self.trayMenu) self.tray.activated.connect(self.onClickEvent) self.tray.show() self.firstHeader = "Time" self.secondHeader = "Description" self.table = QTableWidget(self) self.table.setRowCount(self.numRow) self.table.setColumnCount(self.numColumn) self.table.setHorizontalHeaderLabels([self.firstHeader, self.secondHeader]) # self.table.setColumnWidth(0, self.columnWidth) # self.table.setColumnWidth(1, self.columnWidth) self.tableScrollW = self.table.verticalScrollBar().sizeHint().width() self.tableHeaderW = self.table.horizontalHeader().length() self.tableVertHeaderW = self.table.verticalHeader().width() self.tableFrameW = self.table.frameWidth() * 2 self.tableWidth = (self.tableScrollW + self.tableHeaderW + self.tableFrameW) self.table.setFixedWidth(self.tableWidth) self.table.verticalHeader().hide() self.header = self.table.horizontalHeader() self.header.setSectionResizeMode(0, QHeaderView.Interactive) self.header.setSectionResizeMode(1, QHeaderView.Stretch) self.headerMidPoint = self.header.length() / 2 self.header.setMinimumSectionSize(self.headerMidPoint * 0.10) self.header.setMaximumSectionSize(self.headerMidPoint * 1.90) self.saveButton = self.createButton("Save", self.saveButtonClick) self.galaButton = self.createButton("Gala", self.galaButtonClick) self.loadButton = self.createButton("Load", self.loadButtonClick) self.infoButton = self.createButton("Info", self.infoButtonClick) # self.checkButton = self.createButton("Check", self.checkButtonClick) self.clearButton = self.createButton("Clear", self.clearButtonClick) layout = QGridLayout(self) layout.addWidget(self.table, 0, 0, 1, 6) layout.addWidget(self.loadButton, 1, 0) layout.addWidget(self.saveButton, 1, 1) layout.addWidget(self.clearButton, 1, 2) # layout.addWidget(self.checkButton, 1, 3) layout.addWidget(self.infoButton, 1, 4) layout.addWidget(self.galaButton, 1, 5) # only vertical resize allowed # layout.setSizeConstraint(QLayout.SetFixedSize) self.setLayout(layout) self.autoLoad() # load user data height = self.table.verticalHeader().width() * 20 width = self.sizeHint().width() self.resize(width, height) self.setWindowIcon(QIcon(self.icon_path)) self.setFixedWidth(width) self.setWindowTitle("Gala") def autoLoad(self): self.load() def createButton(self, text, func): btn = QToolButton() btn.setText(text) btn.clicked.connect(func) return btn def onClickEvent(self, event): self.open_() def closeEvent(self, closeEvent): if self.ignoreQuit: closeEvent.ignore() self.hide() else: QCoreApplication.exit() def hideEvent(self, hideEvent): self.hide() def galaButtonClick(self): if self.validTimes(msgHint="Failed to start.") == True: for i in range(0, len(self.threads)): self.threads[i].stop() self.hide() now = Gala.timeNow() minTime = None num = None for row in range(0, self.numRow): txt = self.table.item(row, 0).text() if txt == "": continue end = Gala.parseTime(txt) end = Gala.normalizeTime(end[0], end[1], end[2]) if end < now: deltaTime = Gala.MAX_TIME - abs(end - now) else: deltaTime = end - now if minTime is None or deltaTime < minTime: minTime = deltaTime num = row galaThread = GalaThread(self.table.item(num, 0).text(), self.table.item(num, 1).text()) galaThread.signal.connect(self.delivMsg) galaThread.start() self.threads.append(galaThread) def saveButtonClick(self): self.setFocus() if self.validTimes(msgHint="Failed to save.") == True: os.makedirs("UserData", exist_ok=True) with open(self.data_path, 'w') as f: data = self.convertTableToJson() f.write(data) f.close() def loadButtonClick(self): self.load() def infoButtonClick(self): ex = GalaPopup("Examples", "Tues 1:00 pm | Fri 3:00 pm | Sat 8:30 am\n\n" "Valid days\n" "Mon | Tues | Wed | Thurs | Fri | Sat | Sun\n\n" "Valid times\n" "12:00 am ... 11:59 pm") ex.setWindowTitle("Info") ex.exec_() def checkButtonClick(self): pass def clearButtonClick(self): self.clearTable() def load(self): self.loadJsonToTable(self.data_path) def open_(self): self.setVisible(True) self.raise_() def quit(self): self.ignoreQuit = False self.close() def hide(self): self.setVisible(False) @staticmethod def timeNow(): now = datetime.datetime.now() nowTime = Gala.normalizeTime(now.weekday(), now.hour, now.minute) return nowTime @staticmethod def parseTime(text): text = text.split() weekday = text[0].lower() weekday = Weekday[weekday].value time = text[1].split(':') hour = int(time[0]) min_ = int(time[1]) amPM = text[2].lower() if amPM == "pm" and hour in range(1, 12): hour += 12 elif hour is 12 and amPM is "am": hour -= 12 return [weekday, hour, min_] @staticmethod def normalizeTime(day, hour, min_): """ days = [Mon...Sun] = [0...6] hours = [0...24] minutes = [0...60] Normalize to/with Monday 00:00 (12:00 am) as 0 or self.maxNorm in seconds. Example: Thursday 14:00 (2:00 pm) = 396,000 seconds from 0 """ day = day * 24 * 60 * 60 hour = hour * 60 * 60 min_ = min_ * 60 normTime = day + hour + min_ return normTime def delivMsg(self, timeMsg, msg): msgBox = GalaPopup("Gala delivery", timeMsg, msg) msgBox.exec_() def errTimeMsg(self, row, msgHint=""): errMsg = self.table.item(row, 0).text() err = GalaPopup(msgHint, "Invalid time at row " + str(row + 1) + ":\n\n" + errMsg) err.setWindowTitle("Invalid time") err.exec_() def validTimes(self, msgHint=""): """ Validate time Assume (or enforce) time format as "DATE TIME AM/PM". Example (from string to an array): ["Tues", "11:00", "am"] """ # TODO More strict time check. i.e right now Tues 0:00 pm is okay... # maybe more checks or simplify some steps? for row in range(0, self.numRow): galaTime = self.table.item(row, 0) if galaTime is None or galaTime.text() is "": continue galaTime = galaTime.text().split() if len(galaTime) != 3: self.errTimeMsg(row, msgHint) return False date = galaTime[0] time = galaTime[1] am_pm = galaTime[2] if self.isDate(date) and self.isTime(time) and self.isAmPm(am_pm): continue else: self.errTimeMsg(row, msgHint) return False return True def isDate(self, date): date = date.lower() if date in self.validDate: return True return False def isTime(self, time): time = time.split(':') if len(time) != 2: return False hour = int(time[0]) minute = int(time[1]) hourRange = lambda: range(1, 13) minuteRange = lambda: range(0, 61) if hour in hourRange() and minute in minuteRange(): return True else: return False def isAmPm(self, am_pm): if am_pm.lower() == self.AM or am_pm.lower() == self.PM: return True else: return False def clearTable(self): for row in range(0, self.numRow): for col in range(0, self.numColumn): g = QTableWidgetItem("") self.table.setItem(row, col, g) def convertTableToJson(self): items = [] for row in range(0, self.numRow): item = {} item["row"] = row for col in range(0, self.numColumn): tableItem = self.table.item(row, col) if tableItem is None: text = None else: text = tableItem.text() if col == 0: item["time"] = text elif col == 1: item["description"] = text items.append(item) galaItems = {"gala_items": items} jsonString = json.dumps(galaItems, indent=4) return jsonString def loadJsonToTable(self, path): if not os.path.isfile(path): return 0 galaData = open(path).read() galaData = json.loads(galaData) for i in range(0, len(galaData["gala_items"])): row = galaData["gala_items"][i]["row"] time = galaData["gala_items"][i]["time"] info = galaData["gala_items"][i]["description"] self.table.setItem(row, 0, QTableWidgetItem(time)) self.table.setItem(row, 1, QTableWidgetItem(info)) def convertTableToDict(self): jobArr = [] for row in range(0, self.numRow): newJob = {} for col in range(0, self.numColumn): if col == 1: newJob["time"] = self.table.item(row, col) elif col == 2: newJob["description"] = self.table.item(row, col) jobArr.append(newJob) return jobArr
queue = Queue() image_queue = ImageQueue() # creates the interactions object interactions = Interactions(sp, token_info, sp_oauth, exit_app, queue) # UI ui = Ui(interactions) # Create icon icon = QIcon(f"{ASSETS_DIR}img{sep}logo_small.png") # Create tray tray = QSystemTrayIcon() tray.setIcon(icon) tray.setVisible(True) tray.setToolTip("Spotlightify") # Create menu menu = QMenu() open_ui = QAction("Open") open_ui.triggered.connect(show_ui) menu.addAction(open_ui) exit_ = QAction("Exit") exit_.triggered.connect(exit_app) menu.addAction(exit_) listener_thread = Thread(target=listener, daemon=True, args=(open_ui,))
class ElectrumGui(Logger): @profiler def __init__(self, config, daemon, plugins): set_language(config.get('language', get_default_language())) Logger.__init__(self) # Uncomment this call to verify objects are being properly # GC-ed when windows are closed #network.add_jobs([DebugMem([Abstract_Wallet, SPV, Synchronizer, # ElectrumWindow], interval=5)]) QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads) if hasattr(QtCore.Qt, "AA_ShareOpenGLContexts"): QtCore.QCoreApplication.setAttribute( QtCore.Qt.AA_ShareOpenGLContexts) if hasattr(QGuiApplication, 'setDesktopFileName'): QGuiApplication.setDesktopFileName('electrum.desktop') self.gui_thread = threading.current_thread() self.config = config self.daemon = daemon self.plugins = plugins self.windows = [] self.efilter = OpenFileEventFilter(self.windows) self.app = QElectrumApplication(sys.argv) self.app.installEventFilter(self.efilter) self.app.setWindowIcon(read_QIcon("electrum.png")) # timer self.timer = QTimer(self.app) self.timer.setSingleShot(False) self.timer.setInterval(500) # msec self.nd = None self.network_updated_signal_obj = QNetworkUpdatedSignalObject() self._num_wizards_in_progress = 0 self._num_wizards_lock = threading.Lock() # init tray self.dark_icon = self.config.get("dark_icon", False) self.tray = QSystemTrayIcon(self.tray_icon(), None) self.tray.setToolTip('Electrum') self.tray.activated.connect(self.tray_activated) self.build_tray_menu() self.tray.show() self.app.new_window_signal.connect(self.start_new_window) self.set_dark_theme_if_needed() run_hook('init_qt', self) def set_dark_theme_if_needed(self): use_dark_theme = self.config.get('qt_gui_color_theme', 'default') == 'dark' if use_dark_theme: try: import qdarkstyle self.app.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5()) except BaseException as e: use_dark_theme = False self.logger.warning(f'Error setting dark theme: {repr(e)}') # Apply any necessary stylesheet patches patch_qt_stylesheet(use_dark_theme=use_dark_theme) # Even if we ourselves don't set the dark theme, # the OS/window manager/etc might set *a dark theme*. # Hence, try to choose colors accordingly: ColorScheme.update_from_widget(QWidget(), force_dark=use_dark_theme) def build_tray_menu(self): # Avoid immediate GC of old menu when window closed via its action if self.tray.contextMenu() is None: m = QMenu() self.tray.setContextMenu(m) else: m = self.tray.contextMenu() m.clear() for window in self.windows: submenu = m.addMenu(window.wallet.basename()) submenu.addAction(_("Show/Hide"), window.show_or_hide) submenu.addAction(_("Close"), window.close) m.addAction(_("Dark/Light"), self.toggle_tray_icon) m.addSeparator() m.addAction(_("Exit Electrum"), self.close) def tray_icon(self): if self.dark_icon: return read_QIcon('electrum_dark_icon.png') else: return read_QIcon('electrum_light_icon.png') def toggle_tray_icon(self): self.dark_icon = not self.dark_icon self.config.set_key("dark_icon", self.dark_icon, True) self.tray.setIcon(self.tray_icon()) def tray_activated(self, reason): if reason == QSystemTrayIcon.DoubleClick: if all([w.is_hidden() for w in self.windows]): for w in self.windows: w.bring_to_top() else: for w in self.windows: w.hide() def close(self): for window in self.windows: window.close() def new_window(self, path, uri=None): # Use a signal as can be called from daemon thread self.app.new_window_signal.emit(path, uri) def show_network_dialog(self, parent): if not self.daemon.network: parent.show_warning(_( 'You are using Electrum in offline mode; restart Electrum if you want to get connected' ), title=_('Offline')) return if self.nd: self.nd.on_update() self.nd.show() self.nd.raise_() return self.nd = NetworkDialog(self.daemon.network, self.config, self.network_updated_signal_obj) self.nd.show() def _create_window_for_wallet(self, wallet): w = ElectrumWindow(self, wallet) self.windows.append(w) self.build_tray_menu() # FIXME: Remove in favour of the load_wallet hook run_hook('on_new_window', w) w.warn_if_testnet() w.warn_if_watching_only() return w def count_wizards_in_progress(func): def wrapper(self: 'ElectrumGui', *args, **kwargs): with self._num_wizards_lock: self._num_wizards_in_progress += 1 try: return func(self, *args, **kwargs) finally: with self._num_wizards_lock: self._num_wizards_in_progress -= 1 return wrapper @count_wizards_in_progress def start_new_window(self, path, uri, *, app_is_starting=False): '''Raises the window for the wallet if it is open. Otherwise opens the wallet and creates a new window for it''' wallet = None try: wallet = self.daemon.load_wallet(path, None) except BaseException as e: self.logger.exception('') custom_message_box(icon=QMessageBox.Warning, parent=None, title=_('Error'), text=_('Cannot load wallet') + ' (1):\n' + str(e)) # if app is starting, still let wizard to appear if not app_is_starting: return if not wallet: try: wallet = self._start_wizard_to_select_or_create_wallet(path) except (WalletFileException, BitcoinException) as e: self.logger.exception('') custom_message_box(icon=QMessageBox.Warning, parent=None, title=_('Error'), text=_('Cannot load wallet') + ' (2):\n' + str(e)) if not wallet: return # create or raise window try: for window in self.windows: if window.wallet.storage.path == wallet.storage.path: break else: window = self._create_window_for_wallet(wallet) except BaseException as e: self.logger.exception('') custom_message_box(icon=QMessageBox.Warning, parent=None, title=_('Error'), text=_('Cannot create window for wallet') + ':\n' + str(e)) if app_is_starting: wallet_dir = os.path.dirname(path) path = os.path.join(wallet_dir, get_new_wallet_name(wallet_dir)) self.start_new_window(path, uri) return if uri: window.pay_to_URI(uri) window.bring_to_top() window.setWindowState(window.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive) window.activateWindow() return window def _start_wizard_to_select_or_create_wallet( self, path) -> Optional[Abstract_Wallet]: wizard = InstallWizard(self.config, self.app, self.plugins) try: path, storage = wizard.select_storage(path, self.daemon.get_wallet) # storage is None if file does not exist if storage is None: wizard.path = path # needed by trustedcoin plugin wizard.run('new') storage = wizard.create_storage(path) else: wizard.run_upgrades(storage) except (UserCancelled, GoBack): return except WalletAlreadyOpenInMemory as e: return e.wallet finally: wizard.terminate() # return if wallet creation is not complete if storage is None or storage.get_action(): return wallet = Wallet(storage) wallet.start_network(self.daemon.network) self.daemon.add_wallet(wallet) return wallet def close_window(self, window: ElectrumWindow): if window in self.windows: self.windows.remove(window) self.build_tray_menu() # save wallet path of last open window if not self.windows: self.config.save_last_wallet(window.wallet) run_hook('on_close_window', window) self.daemon.stop_wallet(window.wallet.storage.path) def init_network(self): # Show network dialog if config does not exist if self.daemon.network: if self.config.get('auto_connect') is None: wizard = InstallWizard(self.config, self.app, self.plugins) wizard.init_network(self.daemon.network) wizard.terminate() def main(self): try: self.init_network() except UserCancelled: return except GoBack: return except BaseException as e: self.logger.exception('') return self.timer.start() self.config.open_last_wallet() path = self.config.get_wallet_path() if not self.start_new_window( path, self.config.get('url'), app_is_starting=True): return signal.signal(signal.SIGINT, lambda *args: self.app.quit()) def quit_after_last_window(): # keep daemon running after close if self.config.get('daemon'): return # check if a wizard is in progress with self._num_wizards_lock: if self._num_wizards_in_progress > 0 or len(self.windows) > 0: return self.app.quit() self.app.setQuitOnLastWindowClosed( False) # so _we_ can decide whether to quit self.app.lastWindowClosed.connect(quit_after_last_window) def clean_up(): # Shut down the timer cleanly self.timer.stop() # clipboard persistence. see http://www.mail-archive.com/[email protected]/msg17328.html event = QtCore.QEvent(QtCore.QEvent.Clipboard) self.app.sendEvent(self.app.clipboard(), event) self.tray.hide() self.app.aboutToQuit.connect(clean_up) # main loop self.app.exec_() # on some platforms the exec_ call may not return, so use clean_up() def stop(self): self.logger.info('closing GUI') self.app.quit()
class TriblerWindow(QMainWindow): resize_event = pyqtSignal() escape_pressed = pyqtSignal() received_search_completions = pyqtSignal(object) def on_exception(self, *exc_info): if self.exception_handler_called: # We only show one feedback dialog, even when there are two consecutive exceptions. return self.exception_handler_called = True if self.tray_icon: try: self.tray_icon.deleteLater() except RuntimeError: # The tray icon might have already been removed when unloading Qt. # This is due to the C code actually being asynchronous. logging.debug( "Tray icon already removed, no further deletion necessary." ) self.tray_icon = None # Stop the download loop self.downloads_page.stop_loading_downloads() # Add info about whether we are stopping Tribler or not os.environ['TRIBLER_SHUTTING_DOWN'] = str( self.core_manager.shutting_down) if not self.core_manager.shutting_down: self.core_manager.stop(stop_app_on_shutdown=False) self.setHidden(True) if self.debug_window: self.debug_window.setHidden(True) exception_text = "".join(traceback.format_exception(*exc_info)) logging.error(exception_text) dialog = FeedbackDialog( self, exception_text, self.core_manager.events_manager.tribler_version, self.start_time) dialog.show() def __init__(self, core_args=None, core_env=None): QMainWindow.__init__(self) QCoreApplication.setOrganizationDomain("nl") QCoreApplication.setOrganizationName("TUDelft") QCoreApplication.setApplicationName("Tribler") QCoreApplication.setAttribute(Qt.AA_UseHighDpiPixmaps) self.gui_settings = QSettings() api_port = int( get_gui_setting(self.gui_settings, "api_port", DEFAULT_API_PORT)) dispatcher.update_worker_settings(port=api_port) self.navigation_stack = [] self.tribler_started = False self.tribler_settings = None self.debug_window = None self.core_manager = CoreManager(api_port) self.pending_requests = {} self.pending_uri_requests = [] self.download_uri = None self.dialog = None self.new_version_dialog = None self.start_download_dialog_active = False self.request_mgr = None self.search_request_mgr = None self.search_suggestion_mgr = None self.selected_torrent_files = [] self.vlc_available = True self.has_search_results = False self.last_search_query = None self.last_search_time = None self.start_time = time.time() self.exception_handler_called = False self.token_refresh_timer = None sys.excepthook = self.on_exception uic.loadUi(get_ui_file_path('mainwindow.ui'), self) TriblerRequestManager.window = self self.tribler_status_bar.hide() # Load dynamic widgets uic.loadUi(get_ui_file_path('torrent_channel_list_container.ui'), self.channel_page_container) self.channel_torrents_list = self.channel_page_container.items_list self.channel_torrents_detail_widget = self.channel_page_container.details_tab_widget self.channel_torrents_detail_widget.initialize_details_widget() self.channel_torrents_list.itemClicked.connect( self.channel_page.clicked_item) uic.loadUi(get_ui_file_path('torrent_channel_list_container.ui'), self.search_page_container) self.search_results_list = self.search_page_container.items_list self.search_torrents_detail_widget = self.search_page_container.details_tab_widget self.search_torrents_detail_widget.initialize_details_widget() self.search_results_list.itemClicked.connect( self.on_channel_item_click) self.search_results_list.itemClicked.connect( self.search_results_page.clicked_item) self.token_balance_widget.mouseReleaseEvent = self.on_token_balance_click def on_state_update(new_state): self.loading_text_label.setText(new_state) self.core_manager.core_state_update.connect(on_state_update) self.magnet_handler = MagnetHandler(self.window) QDesktopServices.setUrlHandler("magnet", self.magnet_handler, "on_open_magnet_link") self.debug_pane_shortcut = QShortcut(QKeySequence("Ctrl+d"), self) self.debug_pane_shortcut.activated.connect( self.clicked_menu_button_debug) # Remove the focus rect on OS X for widget in self.findChildren(QLineEdit) + self.findChildren( QListWidget) + self.findChildren(QTreeWidget): widget.setAttribute(Qt.WA_MacShowFocusRect, 0) self.menu_buttons = [ self.left_menu_button_home, self.left_menu_button_search, self.left_menu_button_my_channel, self.left_menu_button_subscriptions, self.left_menu_button_video_player, self.left_menu_button_downloads, self.left_menu_button_discovered ] self.video_player_page.initialize_player() self.search_results_page.initialize_search_results_page() self.settings_page.initialize_settings_page() self.subscribed_channels_page.initialize() self.edit_channel_page.initialize_edit_channel_page() self.downloads_page.initialize_downloads_page() self.home_page.initialize_home_page() self.loading_page.initialize_loading_page() self.discovering_page.initialize_discovering_page() self.discovered_page.initialize_discovered_page() self.trust_page.initialize_trust_page() self.stackedWidget.setCurrentIndex(PAGE_LOADING) # Create the system tray icon if QSystemTrayIcon.isSystemTrayAvailable(): self.tray_icon = QSystemTrayIcon() use_monochrome_icon = get_gui_setting(self.gui_settings, "use_monochrome_icon", False, is_bool=True) self.update_tray_icon(use_monochrome_icon) # Create the tray icon menu menu = self.create_add_torrent_menu() show_downloads_action = QAction('Show downloads', self) show_downloads_action.triggered.connect( self.clicked_menu_button_downloads) token_balance_action = QAction('Show token balance', self) token_balance_action.triggered.connect( lambda: self.on_token_balance_click(None)) quit_action = QAction('Quit Tribler', self) quit_action.triggered.connect(self.close_tribler) menu.addSeparator() menu.addAction(show_downloads_action) menu.addAction(token_balance_action) menu.addSeparator() menu.addAction(quit_action) self.tray_icon.setContextMenu(menu) else: self.tray_icon = None self.hide_left_menu_playlist() self.left_menu_button_debug.setHidden(True) self.top_menu_button.setHidden(True) self.left_menu.setHidden(True) self.token_balance_widget.setHidden(True) self.settings_button.setHidden(True) self.add_torrent_button.setHidden(True) self.top_search_bar.setHidden(True) # Set various icons self.top_menu_button.setIcon(QIcon(get_image_path('menu.png'))) self.search_completion_model = QStringListModel() completer = QCompleter() completer.setModel(self.search_completion_model) completer.setCompletionMode(QCompleter.UnfilteredPopupCompletion) self.item_delegate = QStyledItemDelegate() completer.popup().setItemDelegate(self.item_delegate) completer.popup().setStyleSheet(""" QListView { background-color: #404040; } QListView::item { color: #D0D0D0; padding-top: 5px; padding-bottom: 5px; } QListView::item:hover { background-color: #707070; } """) self.top_search_bar.setCompleter(completer) # Toggle debug if developer mode is enabled self.window().left_menu_button_debug.setHidden(not get_gui_setting( self.gui_settings, "debug", False, is_bool=True)) # Start Tribler self.core_manager.start(core_args=core_args, core_env=core_env) self.core_manager.events_manager.received_search_result_channel.connect( self.search_results_page.received_search_result_channel) self.core_manager.events_manager.received_search_result_torrent.connect( self.search_results_page.received_search_result_torrent) self.core_manager.events_manager.torrent_finished.connect( self.on_torrent_finished) self.core_manager.events_manager.new_version_available.connect( self.on_new_version_available) self.core_manager.events_manager.tribler_started.connect( self.on_tribler_started) self.core_manager.events_manager.events_started.connect( self.on_events_started) self.core_manager.events_manager.low_storage_signal.connect( self.on_low_storage) # Install signal handler for ctrl+c events def sigint_handler(*_): self.close_tribler() signal.signal(signal.SIGINT, sigint_handler) self.installEventFilter(self.video_player_page) # Resize the window according to the settings center = QApplication.desktop().availableGeometry(self).center() pos = self.gui_settings.value( "pos", QPoint(center.x() - self.width() * 0.5, center.y() - self.height() * 0.5)) size = self.gui_settings.value("size", self.size()) self.move(pos) self.resize(size) self.show() def update_tray_icon(self, use_monochrome_icon): if not QSystemTrayIcon.isSystemTrayAvailable(): return if use_monochrome_icon: self.tray_icon.setIcon( QIcon(QPixmap(get_image_path('monochrome_tribler.png')))) else: self.tray_icon.setIcon( QIcon(QPixmap(get_image_path('tribler.png')))) self.tray_icon.show() def on_low_storage(self): """ Dealing with low storage space available. First stop the downloads and the core manager and ask user to user to make free space. :return: """ self.downloads_page.stop_loading_downloads() self.core_manager.stop(False) close_dialog = ConfirmationDialog( self.window(), "<b>CRITICAL ERROR</b>", "You are running low on disk space (<100MB). Please make sure to have " "sufficient free space available and restart Tribler again.", [("Close Tribler", BUTTON_TYPE_NORMAL)]) close_dialog.button_clicked.connect(lambda _: self.close_tribler()) close_dialog.show() def on_torrent_finished(self, torrent_info): if self.tray_icon: self.window().tray_icon.showMessage( "Download finished", "Download of %s has finished." % torrent_info["name"]) def show_loading_screen(self): self.top_menu_button.setHidden(True) self.left_menu.setHidden(True) self.token_balance_widget.setHidden(True) self.settings_button.setHidden(True) self.add_torrent_button.setHidden(True) self.top_search_bar.setHidden(True) self.stackedWidget.setCurrentIndex(PAGE_LOADING) def on_tribler_started(self): self.tribler_started = True self.top_menu_button.setHidden(False) self.left_menu.setHidden(False) self.token_balance_widget.setHidden(False) self.settings_button.setHidden(False) self.add_torrent_button.setHidden(False) self.top_search_bar.setHidden(False) # fetch the settings, needed for the video player port self.request_mgr = TriblerRequestManager() self.fetch_settings() self.downloads_page.start_loading_downloads() self.home_page.load_popular_torrents() if not self.gui_settings.value( "first_discover", False) and not self.core_manager.use_existing_core: self.window().gui_settings.setValue("first_discover", True) self.discovering_page.is_discovering = True self.stackedWidget.setCurrentIndex(PAGE_DISCOVERING) else: self.clicked_menu_button_home() def on_events_started(self, json_dict): self.setWindowTitle("Tribler %s" % json_dict["version"]) def show_status_bar(self, message): self.tribler_status_bar_label.setText(message) self.tribler_status_bar.show() def hide_status_bar(self): self.tribler_status_bar.hide() def process_uri_request(self): """ Process a URI request if we have one in the queue. """ if len(self.pending_uri_requests) == 0: return uri = self.pending_uri_requests.pop() if uri.startswith('file') or uri.startswith('magnet'): self.start_download_from_uri(uri) def perform_start_download_request(self, uri, anon_download, safe_seeding, destination, selected_files, total_files=0, callback=None): # Check if destination directory is writable if not is_dir_writable(destination): ConfirmationDialog.show_message( self.window(), "Download error <i>%s</i>" % uri, "Insufficient write permissions to <i>%s</i> directory. " "Please add proper write permissions on the directory and " "add the torrent again." % destination, "OK") return selected_files_uri = "" if len(selected_files) != total_files: # Not all files included selected_files_uri = u'&' + u''.join( u"selected_files[]=%s&" % quote_plus_unicode(filename) for filename in selected_files)[:-1] anon_hops = int(self.tribler_settings['download_defaults'] ['number_hops']) if anon_download else 0 safe_seeding = 1 if safe_seeding else 0 post_data = "uri=%s&anon_hops=%d&safe_seeding=%d&destination=%s%s" % ( quote_plus_unicode(uri), anon_hops, safe_seeding, destination, selected_files_uri) post_data = post_data.encode( 'utf-8') # We need to send bytes in the request, not unicode request_mgr = TriblerRequestManager() request_mgr.perform_request( "downloads", callback if callback else self.on_download_added, method='PUT', data=post_data) # Save the download location to the GUI settings current_settings = get_gui_setting(self.gui_settings, "recent_download_locations", "") recent_locations = current_settings.split( ",") if len(current_settings) > 0 else [] if isinstance(destination, unicode): destination = destination.encode('utf-8') encoded_destination = destination.encode('hex') if encoded_destination in recent_locations: recent_locations.remove(encoded_destination) recent_locations.insert(0, encoded_destination) if len(recent_locations) > 5: recent_locations = recent_locations[:5] self.gui_settings.setValue("recent_download_locations", ','.join(recent_locations)) def on_new_version_available(self, version): if version == str(self.gui_settings.value('last_reported_version')): return self.new_version_dialog = ConfirmationDialog( self, "New version available", "Version %s of Tribler is available.Do you want to visit the " "website to download the newest version?" % version, [('IGNORE', BUTTON_TYPE_NORMAL), ('LATER', BUTTON_TYPE_NORMAL), ('OK', BUTTON_TYPE_NORMAL)]) self.new_version_dialog.button_clicked.connect( lambda action: self.on_new_version_dialog_done(version, action)) self.new_version_dialog.show() def on_new_version_dialog_done(self, version, action): if action == 0: # ignore self.gui_settings.setValue("last_reported_version", version) elif action == 2: # ok import webbrowser webbrowser.open("https://tribler.org") self.new_version_dialog.setParent(None) self.new_version_dialog = None def on_search_text_change(self, text): self.search_suggestion_mgr = TriblerRequestManager() self.search_suggestion_mgr.perform_request( "search/completions?q=%s" % text, self.on_received_search_completions) def on_received_search_completions(self, completions): if completions is None: return self.received_search_completions.emit(completions) self.search_completion_model.setStringList(completions["completions"]) def fetch_settings(self): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("settings", self.received_settings, capture_errors=False) def received_settings(self, settings): # If we cannot receive the settings, stop Tribler with an option to send the crash report. if 'error' in settings: raise RuntimeError( TriblerRequestManager.get_message_from_error(settings)) self.tribler_settings = settings['settings'] # Set the video server port self.video_player_page.video_player_port = settings["ports"][ "video_server~port"] # Disable various components based on the settings if not self.tribler_settings['search_community']['enabled']: self.window().top_search_bar.setHidden(True) if not self.tribler_settings['video_server']['enabled']: self.left_menu_button_video_player.setHidden(True) self.downloads_creditmining_button.setHidden( not self.tribler_settings["credit_mining"]["enabled"]) self.downloads_all_button.click() # process pending file requests (i.e. someone clicked a torrent file when Tribler was closed) # We do this after receiving the settings so we have the default download location. self.process_uri_request() # Set token balance refresh timer and load the token balance self.token_refresh_timer = QTimer() self.token_refresh_timer.timeout.connect(self.load_token_balance) self.token_refresh_timer.start(60000) self.load_token_balance() def on_top_search_button_click(self): current_ts = time.time() current_search_query = self.top_search_bar.text() if self.last_search_query and self.last_search_time \ and self.last_search_query == self.top_search_bar.text() \ and current_ts - self.last_search_time < 1: logging.info( "Same search query already sent within 500ms so dropping this one" ) return self.left_menu_button_search.setChecked(True) self.has_search_results = True self.clicked_menu_button_search() self.search_results_page.perform_search(current_search_query) self.search_request_mgr = TriblerRequestManager() self.search_request_mgr.perform_request( "search?q=%s" % current_search_query, None) self.last_search_query = current_search_query self.last_search_time = current_ts def on_settings_button_click(self): self.deselect_all_menu_buttons() self.stackedWidget.setCurrentIndex(PAGE_SETTINGS) self.settings_page.load_settings() self.navigation_stack = [] self.hide_left_menu_playlist() def on_token_balance_click(self, _): self.raise_window() self.deselect_all_menu_buttons() self.stackedWidget.setCurrentIndex(PAGE_TRUST) self.trust_page.load_trust_statistics() self.navigation_stack = [] self.hide_left_menu_playlist() def load_token_balance(self): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("trustchain/statistics", self.received_token_balance, capture_errors=False) def received_token_balance(self, statistics): if not statistics or "statistics" not in statistics: return statistics = statistics["statistics"] if 'latest_block' in statistics: balance = (statistics["latest_block"]["transaction"]["total_up"] - \ statistics["latest_block"]["transaction"]["total_down"]) / 1024 / 1024 self.token_balance_label.setText("%d" % balance) else: self.token_balance_label.setText("0") def raise_window(self): self.setWindowState(self.windowState() & ~Qt.WindowMinimized | Qt.WindowActive) self.raise_() self.activateWindow() def create_add_torrent_menu(self): """ Create a menu to add new torrents. Shows when users click on the tray icon or the big plus button. """ menu = TriblerActionMenu(self) browse_files_action = QAction('Import torrent from file', self) browse_directory_action = QAction('Import torrent(s) from directory', self) add_url_action = QAction('Import torrent from magnet/URL', self) browse_files_action.triggered.connect(self.on_add_torrent_browse_file) browse_directory_action.triggered.connect( self.on_add_torrent_browse_dir) add_url_action.triggered.connect(self.on_add_torrent_from_url) menu.addAction(browse_files_action) menu.addAction(browse_directory_action) menu.addAction(add_url_action) return menu def on_add_torrent_button_click(self, pos): self.create_add_torrent_menu().exec_( self.mapToGlobal(self.add_torrent_button.pos())) def on_add_torrent_browse_file(self): filenames = QFileDialog.getOpenFileNames( self, "Please select the .torrent file", QDir.homePath(), "Torrent files (*.torrent)") if len(filenames[0]) > 0: [ self.pending_uri_requests.append(u"file:%s" % filename) for filename in filenames[0] ] self.process_uri_request() def start_download_from_uri(self, uri): self.download_uri = uri if get_gui_setting(self.gui_settings, "ask_download_settings", True, is_bool=True): # Clear any previous dialog if exists if self.dialog: self.dialog.button_clicked.disconnect() self.dialog.setParent(None) self.dialog = None self.dialog = StartDownloadDialog(self, self.download_uri) self.dialog.button_clicked.connect(self.on_start_download_action) self.dialog.show() self.start_download_dialog_active = True else: # In the unlikely scenario that tribler settings are not available yet, try to fetch settings again and # add the download uri back to self.pending_uri_requests to process again. if not self.tribler_settings: self.fetch_settings() if self.download_uri not in self.pending_uri_requests: self.pending_uri_requests.append(self.download_uri) return self.window().perform_start_download_request( self.download_uri, self.window().tribler_settings['download_defaults'] ['anonymity_enabled'], self.window().tribler_settings['download_defaults'] ['safeseeding_enabled'], self.tribler_settings['download_defaults']['saveas'], [], 0) self.process_uri_request() def on_start_download_action(self, action): if action == 1: if self.dialog and self.dialog.dialog_widget: self.window().perform_start_download_request( self.download_uri, self.dialog.dialog_widget.anon_download_checkbox.isChecked( ), self.dialog.dialog_widget.safe_seed_checkbox.isChecked(), self.dialog.dialog_widget.destination_input.currentText(), self.dialog.get_selected_files(), self.dialog.dialog_widget.files_list_view. topLevelItemCount()) else: ConfirmationDialog.show_error( self, "Tribler UI Error", "Something went wrong. Please try again.") logging.exception( "Error while trying to download. Either dialog or dialog.dialog_widget is None" ) self.dialog.request_mgr.cancel_request( ) # To abort the torrent info request self.dialog.setParent(None) self.dialog = None self.start_download_dialog_active = False if action == 0: # We do this after removing the dialog since process_uri_request is blocking self.process_uri_request() def on_add_torrent_browse_dir(self): chosen_dir = QFileDialog.getExistingDirectory( self, "Please select the directory containing the .torrent files", QDir.homePath(), QFileDialog.ShowDirsOnly) if len(chosen_dir) != 0: self.selected_torrent_files = [ torrent_file for torrent_file in glob.glob(chosen_dir + "/*.torrent") ] self.dialog = ConfirmationDialog( self, "Add torrents from directory", "Are you sure you want to add %d torrents to Tribler?" % len(self.selected_torrent_files), [('ADD', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)]) self.dialog.button_clicked.connect( self.on_confirm_add_directory_dialog) self.dialog.show() def on_confirm_add_directory_dialog(self, action): if action == 0: for torrent_file in self.selected_torrent_files: escaped_uri = u"file:%s" % pathname2url(torrent_file) self.perform_start_download_request( escaped_uri, self.window().tribler_settings['download_defaults'] ['anonymity_enabled'], self.window().tribler_settings['download_defaults'] ['safeseeding_enabled'], self.tribler_settings['download_defaults']['saveas'], [], 0) if self.dialog: self.dialog.setParent(None) self.dialog = None def on_add_torrent_from_url(self): # Make sure that the window is visible (this action might be triggered from the tray icon) self.raise_window() self.dialog = ConfirmationDialog( self, "Add torrent from URL/magnet link", "Please enter the URL/magnet link in the field below:", [('ADD', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)], show_input=True) self.dialog.dialog_widget.dialog_input.setPlaceholderText( 'URL/magnet link') self.dialog.dialog_widget.dialog_input.setFocus() self.dialog.button_clicked.connect( self.on_torrent_from_url_dialog_done) self.dialog.show() def on_torrent_from_url_dialog_done(self, action): if self.dialog and self.dialog.dialog_widget: uri = self.dialog.dialog_widget.dialog_input.text() # Remove first dialog self.dialog.setParent(None) self.dialog = None if action == 0: self.start_download_from_uri(uri) def on_download_added(self, result): if len(self.pending_uri_requests ) == 0: # Otherwise, we first process the remaining requests. self.window().left_menu_button_downloads.click() else: self.process_uri_request() def on_top_menu_button_click(self): if self.left_menu.isHidden(): self.left_menu.show() else: self.left_menu.hide() def deselect_all_menu_buttons(self, except_select=None): for button in self.menu_buttons: if button == except_select: button.setEnabled(False) continue button.setEnabled(True) if button == self.left_menu_button_search and not self.has_search_results: button.setEnabled(False) button.setChecked(False) def clicked_menu_button_home(self): self.deselect_all_menu_buttons(self.left_menu_button_home) self.stackedWidget.setCurrentIndex(PAGE_HOME) self.navigation_stack = [] self.hide_left_menu_playlist() def clicked_menu_button_search(self): self.deselect_all_menu_buttons(self.left_menu_button_search) self.stackedWidget.setCurrentIndex(PAGE_SEARCH_RESULTS) self.navigation_stack = [] self.hide_left_menu_playlist() def clicked_menu_button_discovered(self): self.deselect_all_menu_buttons(self.left_menu_button_discovered) self.stackedWidget.setCurrentIndex(PAGE_DISCOVERED) self.discovered_page.load_discovered_channels() self.navigation_stack = [] self.hide_left_menu_playlist() def clicked_menu_button_my_channel(self): self.deselect_all_menu_buttons(self.left_menu_button_my_channel) self.stackedWidget.setCurrentIndex(PAGE_EDIT_CHANNEL) self.edit_channel_page.load_my_channel_overview() self.navigation_stack = [] self.hide_left_menu_playlist() def clicked_menu_button_video_player(self): self.deselect_all_menu_buttons(self.left_menu_button_video_player) self.stackedWidget.setCurrentIndex(PAGE_VIDEO_PLAYER) self.navigation_stack = [] self.show_left_menu_playlist() def clicked_menu_button_downloads(self): self.raise_window() self.left_menu_button_downloads.setChecked(True) self.deselect_all_menu_buttons(self.left_menu_button_downloads) self.stackedWidget.setCurrentIndex(PAGE_DOWNLOADS) self.navigation_stack = [] self.hide_left_menu_playlist() def clicked_menu_button_debug(self): if not self.debug_window: self.debug_window = DebugWindow( self.tribler_settings, self.core_manager.events_manager.tribler_version) self.debug_window.show() def clicked_menu_button_subscriptions(self): self.deselect_all_menu_buttons(self.left_menu_button_subscriptions) self.subscribed_channels_page.load_subscribed_channels() self.stackedWidget.setCurrentIndex(PAGE_SUBSCRIBED_CHANNELS) self.navigation_stack = [] self.hide_left_menu_playlist() def hide_left_menu_playlist(self): self.left_menu_seperator.setHidden(True) self.left_menu_playlist_label.setHidden(True) self.left_menu_playlist.setHidden(True) def show_left_menu_playlist(self): self.left_menu_seperator.setHidden(False) self.left_menu_playlist_label.setHidden(False) self.left_menu_playlist.setHidden(False) def on_channel_item_click(self, channel_list_item): list_widget = channel_list_item.listWidget() from TriblerGUI.widgets.channel_list_item import ChannelListItem if isinstance(list_widget.itemWidget(channel_list_item), ChannelListItem): channel_info = channel_list_item.data(Qt.UserRole) self.channel_page.initialize_with_channel(channel_info) self.navigation_stack.append(self.stackedWidget.currentIndex()) self.stackedWidget.setCurrentIndex(PAGE_CHANNEL_DETAILS) def on_playlist_item_click(self, playlist_list_item): list_widget = playlist_list_item.listWidget() from TriblerGUI.widgets.playlist_list_item import PlaylistListItem if isinstance(list_widget.itemWidget(playlist_list_item), PlaylistListItem): playlist_info = playlist_list_item.data(Qt.UserRole) self.playlist_page.initialize_with_playlist(playlist_info) self.navigation_stack.append(self.stackedWidget.currentIndex()) self.stackedWidget.setCurrentIndex(PAGE_PLAYLIST_DETAILS) def on_page_back_clicked(self): try: prev_page = self.navigation_stack.pop() self.stackedWidget.setCurrentIndex(prev_page) if prev_page == PAGE_SEARCH_RESULTS: self.stackedWidget.widget( prev_page).load_search_results_in_list() if prev_page == PAGE_SUBSCRIBED_CHANNELS: self.stackedWidget.widget(prev_page).load_subscribed_channels() if prev_page == PAGE_DISCOVERED: self.stackedWidget.widget(prev_page).load_discovered_channels() except IndexError: logging.exception("Unknown page found in stack") def on_edit_channel_clicked(self): self.stackedWidget.setCurrentIndex(PAGE_EDIT_CHANNEL) self.navigation_stack = [] self.channel_page.on_edit_channel_clicked() def resizeEvent(self, _): # Resize home page cells cell_width = self.home_page_table_view.width( ) / 3 - 3 # We have some padding to the right cell_height = cell_width / 2 + 60 for i in range(0, 3): self.home_page_table_view.setColumnWidth(i, cell_width) self.home_page_table_view.setRowHeight(i, cell_height) self.resize_event.emit() def exit_full_screen(self): self.top_bar.show() self.left_menu.show() self.video_player_page.is_full_screen = False self.showNormal() def close_tribler(self): if not self.core_manager.shutting_down: def show_force_shutdown(): self.loading_text_label.setText( "Tribler is taking longer than expected to shut down. You can force " "Tribler to shutdown by pressing the button below. This might lead " "to data loss.") self.window().force_shutdown_btn.show() if self.tray_icon: self.tray_icon.deleteLater() self.show_loading_screen() self.hide_status_bar() self.loading_text_label.setText("Shutting down...") self.shutdown_timer = QTimer() self.shutdown_timer.timeout.connect(show_force_shutdown) self.shutdown_timer.start(SHUTDOWN_WAITING_PERIOD) self.gui_settings.setValue("pos", self.pos()) self.gui_settings.setValue("size", self.size()) if self.core_manager.use_existing_core: # Don't close the core that we are using QApplication.quit() self.core_manager.stop() self.core_manager.shutting_down = True self.downloads_page.stop_loading_downloads() request_queue.clear() def closeEvent(self, close_event): self.close_tribler() close_event.ignore() def keyReleaseEvent(self, event): if event.key() == Qt.Key_Escape: self.escape_pressed.emit() if self.isFullScreen(): self.exit_full_screen() def clicked_force_shutdown(self): process_checker = ProcessChecker() if process_checker.already_running: core_pid = process_checker.get_pid_from_lock_file() os.kill(int(core_pid), 9) # Stop the Qt application QApplication.quit()
def startSplashWindowPhase(self) -> None: super().startSplashWindowPhase() i18n_catalog = i18nCatalog("uranium") self.showSplashMessage(i18n_catalog.i18nc("@info:progress", "Initializing package manager...")) self._package_manager.initialize() signal.signal(signal.SIGINT, signal.SIG_DFL) # This is done here as a lot of plugins require a correct gl context. If you want to change the framework, # these checks need to be done in your <framework>Application.py class __init__(). self._configuration_error_message = ConfigurationErrorMessage(self, i18n_catalog.i18nc("@info:status", "Your configuration seems to be corrupt."), lifetime = 0, title = i18n_catalog.i18nc("@info:title", "Configuration errors") ) # Remove, install, and then loading plugins self.showSplashMessage(i18n_catalog.i18nc("@info:progress", "Loading plugins...")) # Remove and install the plugins that have been scheduled self._plugin_registry.initializeBeforePluginsAreLoaded() self._plugin_registry.pluginLoadStarted.connect(self._displayLoadingPluginSplashMessage) self._loadPlugins() self._plugin_registry.pluginLoadStarted.disconnect(self._displayLoadingPluginSplashMessage) self._plugin_registry.checkRequiredPlugins(self.getRequiredPlugins()) self.pluginsLoaded.emit() self.showSplashMessage(i18n_catalog.i18nc("@info:progress", "Updating configuration...")) with self._container_registry.lockFile(): VersionUpgradeManager.getInstance().upgrade() # Load preferences again because before we have loaded the plugins, we don't have the upgrade routine for # the preferences file. Now that we have, load the preferences file again so it can be upgraded and loaded. self.showSplashMessage(i18n_catalog.i18nc("@info:progress", "Loading preferences...")) try: preferences_filename = Resources.getPath(Resources.Preferences, self._app_name + ".cfg") with open(preferences_filename, "r", encoding = "utf-8") as f: serialized = f.read() # This performs the upgrade for Preferences self._preferences.deserialize(serialized) self._preferences.setValue("general/plugins_to_remove", "") self._preferences.writeToFile(preferences_filename) except (EnvironmentError, UnicodeDecodeError): Logger.log("i", "The preferences file cannot be opened or it is corrupted, so we will use default values") self.processEvents() # Force the configuration file to be written again since the list of plugins to remove maybe changed try: self.readPreferencesFromConfiguration() except FileNotFoundError: Logger.log("i", "The preferences file '%s' cannot be found, will use default values", self._preferences_filename) self._preferences_filename = Resources.getStoragePath(Resources.Preferences, self._app_name + ".cfg") Logger.info("Completed loading preferences.") # FIXME: This is done here because we now use "plugins.json" to manage plugins instead of the Preferences file, # but the PluginRegistry will still import data from the Preferences files if present, such as disabled plugins, # so we need to reset those values AFTER the Preferences file is loaded. self._plugin_registry.initializeAfterPluginsAreLoaded() # Check if we have just updated from an older version self._preferences.addPreference("general/last_run_version", "") last_run_version_str = self._preferences.getValue("general/last_run_version") if not last_run_version_str: last_run_version_str = self._version last_run_version = Version(last_run_version_str) current_version = Version(self._version) if last_run_version < current_version: self._just_updated_from_old_version = True self._preferences.setValue("general/last_run_version", str(current_version)) self._preferences.writeToFile(self._preferences_filename) # Preferences: recent files self._preferences.addPreference("%s/recent_files" % self._app_name, "") file_names = self._preferences.getValue("%s/recent_files" % self._app_name).split(";") for file_name in file_names: if not os.path.isfile(file_name): continue self._recent_files.append(QUrl.fromLocalFile(file_name)) if not self.getIsHeadLess(): # Initialize System tray icon and make it invisible because it is used only to show pop up messages self._tray_icon = None if self._tray_icon_name: try: self._tray_icon = QIcon(Resources.getPath(Resources.Images, self._tray_icon_name)) self._tray_icon_widget = QSystemTrayIcon(self._tray_icon) self._tray_icon_widget.setVisible(False) Logger.info("Created system tray icon.") except FileNotFoundError: Logger.log("w", "Could not find the icon %s", self._tray_icon_name)
class NomnsParse(QApplication): """Application Control.""" def __init__(self, *args): self.setAttribute(Qt.AA_EnableHighDpiScaling) super().__init__(*args) # Updates self._toggled = False self._log_reader = None # Load Parsers self._load_parsers() self._settings = SettingsWindow() # Tray Icon self._system_tray = QSystemTrayIcon() self._system_tray.setIcon(QIcon(resource_path('data/ui/icon.png'))) self._system_tray.setToolTip("nParse") # self._system_tray.setContextMenu(self._create_menu()) self._system_tray.activated.connect(self._menu) self._system_tray.show() # Turn On self._toggle() if self.new_version_available(): self._system_tray.showMessage( "nParse Update".format(ONLINE_VERSION), "New version available!\ncurrent: {}\nonline: {}".format( CURRENT_VERSION, ONLINE_VERSION ), msecs=3000 ) def _load_parsers(self): self._parsers = [ parsers.Maps(), parsers.Spells() ] for parser in self._parsers: if parser.name in config.data.keys() and 'geometry' in config.data[parser.name].keys(): g = config.data[parser.name]['geometry'] parser.setGeometry(g[0], g[1], g[2], g[3]) if config.data[parser.name]['toggled']: parser.toggle() def _toggle(self): if not self._toggled: try: config.verify_paths() except ValueError as error: self._system_tray.showMessage( error.args[0], error.args[1], msecs=3000) else: self._log_reader = logreader.LogReader( config.data['general']['eq_log_dir']) self._log_reader.new_line.connect(self._parse) self._toggled = True else: if self._log_reader: self._log_reader.deleteLater() self._log_reader = None self._toggled = False def _parse(self, new_line): if new_line: timestamp, text = new_line # (datetime, text) # don't send parse to non toggled items, except maps. always parse maps for parser in [parser for parser in self._parsers if config.data[parser.name]['toggled'] or parser.name == 'maps']: parser.parse(timestamp, text) def _menu(self, event): """Returns a new QMenu for system tray.""" menu = QMenu() menu.setAttribute(Qt.WA_DeleteOnClose) # check online for new version new_version_text = "" if self.new_version_available(): new_version_text = "Update Available {}".format(ONLINE_VERSION) else: new_version_text = "Version {}".format(CURRENT_VERSION) check_version_action = menu.addAction(new_version_text) menu.addSeparator() get_eq_dir_action = menu.addAction('Select EQ Logs Directory') menu.addSeparator() parser_toggles = set() for parser in self._parsers: toggle = menu.addAction(parser.name.title()) toggle.setCheckable(True) toggle.setChecked(config.data[parser.name]['toggled']) parser_toggles.add(toggle) menu.addSeparator() settings_action = menu.addAction('Settings') menu.addSeparator() quit_action = menu.addAction('Quit') action = menu.exec_(QCursor.pos()) if action == check_version_action: webbrowser.open('https://github.com/nomns/nparse/releases') elif action == get_eq_dir_action: dir_path = str(QFileDialog.getExistingDirectory( None, 'Select Everquest Logs Directory')) if dir_path: config.data['general']['eq_log_dir'] = dir_path config.save() self._toggle() elif action == settings_action: if self._settings.exec_(): # Update required settings for parser in self._parsers: if parser.windowOpacity() != config.data['general']['parser_opacity']: parser.setWindowOpacity( config.data['general']['parser_opacity'] / 100) parser.settings_updated() # some settings are saved within other settings automatically # force update for parser in self._parsers: if parser.name == "spells": parser.load_custom_timers() elif action == quit_action: if self._toggled: self._toggle() # save parser geometry for parser in self._parsers: g = parser.geometry() config.data[parser.name]['geometry'] = [ g.x(), g.y(), g.width(), g.height() ] config.save() self._system_tray.setVisible(False) self.quit() elif action in parser_toggles: parser = [ parser for parser in self._parsers if parser.name == action.text().lower()][0] parser.toggle() def new_version_available(self): # this will only work if numbers go up try: for (o, c) in zip(ONLINE_VERSION.split('.'), CURRENT_VERSION.split('.')): if int(o) > int(c): return True except: return False
class MainWindow(QMainWindow): """ Сheckbox and system tray icons. Will initialize in the constructor. """ check_box = None tray_icon = None # Override the class constructor def __init__(self, canvas): # Be sure to call the super class method super().__init__(None, Qt.WindowStaysOnTopHint) self.canvas = canvas self.setVisible(False) self.setMinimumSize(QSize(480, 80)) self.setWindowTitle("Settings - Coming Soon") # Init QSystemTrayIcon self.tray_icon = QSystemTrayIcon(self) icon = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'mdpi.png') self.tray_icon.setIcon(QIcon(icon)) ''' Define and add steps to work with the system tray icon show - show window hide - hide window exit - exit from application ''' activate = QAction("Activate", self) show_action = QAction("Show", self) quit_action = QAction("Exit", self) hide_action = QAction("Hide", self) activate.triggered.connect(self.canvas.show) show_action.triggered.connect(self.show) hide_action.triggered.connect(self.hide) quit_action.triggered.connect(qApp.quit) tray_menu = QMenu() tray_menu.addAction(activate) tray_menu.addAction(show_action) tray_menu.addAction(hide_action) tray_menu.addAction(quit_action) self.tray_icon.setContextMenu(tray_menu) self.tray_icon.show() ui = QMainWindow() #ui.setWindowFlags(Qt.FramelessWindowHint) ui.setWindowModality(Qt.NonModal) ui.move(0, 0) ui.setMinimumSize(QSize(480, 80)) ui.show() # Override closeEvent, to intercept the window closing event # The window will be closed only if there is no check mark in the check box def closeEvent(self, event): event.ignore() self.hide() self.tray_icon.showMessage("Tray Program", "Application was minimized to Tray", QSystemTrayIcon.Information, 2000)
def setup_icon(self): self.icon_normal = QIcon(ALARM_PATH) self.icon = QSystemTrayIcon(self.icon_normal) self.icon_urgent = QIcon(ALARM_URGENT_PATH) self.icon_active = QIcon(ALARM_ACTIVE_PATH) self.icon.activated.connect(self.activate_menu)
class MyApp(QMainWindow): def __init__(self): super().__init__() print("ready MainWindow") self.initUI() self.is_pypresence_client_set = False self.p = Help() self.loadFile() #pyqt 종료 이벤트 override 됨 def closeEvent(self, event): if self.is_pypresence_client_set: self.RPC.close() print("RPC setup done, close event done") else: print("RPC setup not done, close event done") print("check line empty:" + str(self.checkEmptyLine())) self.saveFile() self.p.close() def run_pypresence(self, *args): print("run pypresence : " + str(args)) #pypresence 첫 실행 시 #pypresence 주어진 client_id로 연결하고 상태를 업데이트 if not self.is_pypresence_client_set: self.client_id = args[0] #get Discord Developer Portal self.RPC = Presence(self.client_id, pipe=0) self.RPC.connect() self.is_pypresence_client_set = True startTime = datetime.datetime.today().timestamp() self.RPC.update(details=args[1], state=args[2], large_image=args[3], start=startTime) #pypresence 첫 실행이 아닐 시 연결에 변화 없이 내용 업데이트 elif self.is_pypresence_client_set: startTime = datetime.datetime.today().timestamp() self.RPC.update(details=args[1], state=args[2], large_image=args[3], start=startTime) def initUI(self): #Get Foreground Window process name w = win32gui w.GetWindowText(w.GetForegroundWindow()) pid = win32process.GetWindowThreadProcessId(w.GetForegroundWindow()) print(psutil.Process(pid[-1]).name()) #foreground 프로세스 변경 시 자동으로 사용자 상태가 현재 활성화 된 창을 표시할 수 있도록 #SetWinEventHook 를 사용하여 foreground프로세스가 변경될 때 이벤트를 받을 수 있어야야함 #메뉴바 액션 exitAction = QAction('종료', self) exitAction.setShortcut('Ctrl+Q') exitAction.setStatusTip('프로그램 종료') exitAction.triggered.connect(qApp.quit) aboutAction = QAction('제작자', self) aboutAction.setStatusTip('제작자의 정보 : iro_bound') #exitAction.triggered.connect() #스테이터스 바 #첫번째 호출 시 statusbar 생성, 이후 호출시 상태바 객체 반환 #showMessage(str) 로 상태 메세지 변경 self.statusBar().showMessage('DCGA 준비됨') #메뉴 바 #--self.tray icon-- self.tray = QSystemTrayIcon(QIcon('icon.png'), parent=self) self.tray.setToolTip("check out this app on self.tray icon") self.tray.setVisible(True) menubar = self.menuBar() menubar.setNativeMenuBar(False) #Mac OS sync menu = QMenu('&도움말') filemenu = menu self.tray.setContextMenu(menu) filemenu.addAction(exitAction) filemenu.addAction(aboutAction) menubar.addMenu(menu) #센트럴 위젯 central = QWidget() #central.setStyleSheet("background-color:#333333; border-style:solid; border-width: 1px; border-color: #555555; border-radius: 4px;") self.idLine = QLineEdit() self.idLine.setPlaceholderText("Client ID를 입력") self.contentLine = QLineEdit() self.contentLine.setPlaceholderText("원하는 내용을 입력") self.statusLine = QLineEdit() self.statusLine.setPlaceholderText("원하는 상태를 입력") self.imageLine = QLineEdit() self.imageLine.setPlaceholderText("이미지 이름") self.okButton = QPushButton("적용") self.okButton.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.doingButton = QPushButton("설정하기") labelID = QLabel("Client 설정 :") labelID.setAlignment(Qt.AlignCenter) label0 = QLabel("~하는 중 :") label0.setAlignment(Qt.AlignCenter) label1 = QLabel("내용 : ") label1.setAlignment(Qt.AlignCenter) label2 = QLabel("상태 : ") label2.setAlignment(Qt.AlignCenter) label3 = QLabel("이미지 : ") label3.setAlignment(Qt.AlignCenter) grid = QGridLayout() grid.setContentsMargins(59, 50, 50, 50) grid.addWidget(labelID, 0, 0) grid.addWidget(self.idLine, 0, 1) grid.addWidget(label0, 1, 0) grid.addWidget(self.doingButton, 1, 1) grid.addWidget(label1, 2, 0) grid.addWidget(self.contentLine, 2, 1) grid.addWidget(label2, 3, 0) grid.addWidget(self.statusLine, 3, 1) grid.addWidget(label3, 4, 0) grid.addWidget(self.imageLine, 4, 1) grid.addWidget(self.okButton, 5, 0, 1, 2) temp = QPushButton("temp") grid.addWidget(temp, 6, 0, 1, 2) temp.clicked.connect(self.temms) #grid.setColumnStretch(0, 2) #grid.setColumnStretch(1, 2) central.setLayout(grid) self.setCentralWidget(central) #윈도우 기본 셋 self.setWindowTitle('Discord Custom GameActivity') self.setWindowIcon(QIcon('icon.png')) self.resize(400, 300) #self.setWindowFlags(Qt.FramelessWindowHint) #window frame hide #self.setAttribute(Qt.WA_TranslucentBackground) #remove border top side grabage self.center() self.show() #self.setStyleSheet("background-color: #333333;") #self -> style css type #이벤트 self.doingButton.clicked.connect(self.onDoingButton) self.okButton.clicked.connect(self.onOkButton) #self.tray.activated(self.self.trayActiviy(self.tray)) #화면 창을 가운데로 정렬 def center(self): qr = self.frameGeometry() #get 창의 위치, 크기 정보를 cp = QDesktopWidget().availableGeometry().center( ) #get 현재 모니터 화면의 가운데 위치 qr.moveCenter(cp) #qr에 담긴 프로그램 창의 중심정보를 화면의 중심으로 이동 self.move(qr.topLeft() ) #현재 창을 qr의 위치로 실제로 이동시킴, 의미 : topLeft => 모니터의 좌상단을 기준으로 #액션 def onDoingButton(self): webbrowser.open("https://discord.com/developers/applications") self.p.show() def onOkButton(self): id = self.idLine.text() content = self.contentLine.text() status = self.statusLine.text() image = self.imageLine.text() print(id, content, status, image) if self.checkEmptyLine(): print("Enter onOkButton event -> checkEmptyLine False Enter here") x = QMessageBox.question(self, '경고', '입력 항목을 다시 확인해주세요', QMessageBox.Yes | QMessageBox.No, QMessageBox.No) else: self.run_pypresence(id, content, status, image) def checkEmptyLine(self): check = True if self.idLine.text() == "" or self.contentLine.text( ) == "" or self.statusLine.text() == "" or self.imageLine.text() == "": check = True else: check = False return check def loadFile(self): loadContent = './config.json' if os.path.isfile(loadContent): with open("config.json", "r") as f: readfile = json.load(f) self.idLine.setText(readfile[0]) self.contentLine.setText(readfile[1]) self.statusLine.setText(readfile[2]) self.imageLine.setText(readfile[3]) print("file load success") print(readfile) else: print("file load fail") def saveFile(self): if self.checkEmptyLine(): print("file save file. check line edit is empty line.") else: writeContent = [ self.idLine.text(), self.contentLine.text(), self.statusLine.text(), self.imageLine.text() ] with open("config.json", "w") as json_file: json.dump(writeContent, json_file) print("file save success") print(writeContent) def temms(self): if self.tray.isVisible(): self.tray.setVisible(False) elif not self.tray.isVisible(): self.tray.setVisible(True) else: print("error")
def initUI(self): #Get Foreground Window process name w = win32gui w.GetWindowText(w.GetForegroundWindow()) pid = win32process.GetWindowThreadProcessId(w.GetForegroundWindow()) print(psutil.Process(pid[-1]).name()) #foreground 프로세스 변경 시 자동으로 사용자 상태가 현재 활성화 된 창을 표시할 수 있도록 #SetWinEventHook 를 사용하여 foreground프로세스가 변경될 때 이벤트를 받을 수 있어야야함 #메뉴바 액션 exitAction = QAction('종료', self) exitAction.setShortcut('Ctrl+Q') exitAction.setStatusTip('프로그램 종료') exitAction.triggered.connect(qApp.quit) aboutAction = QAction('제작자', self) aboutAction.setStatusTip('제작자의 정보 : iro_bound') #exitAction.triggered.connect() #스테이터스 바 #첫번째 호출 시 statusbar 생성, 이후 호출시 상태바 객체 반환 #showMessage(str) 로 상태 메세지 변경 self.statusBar().showMessage('DCGA 준비됨') #메뉴 바 #--self.tray icon-- self.tray = QSystemTrayIcon(QIcon('icon.png'), parent=self) self.tray.setToolTip("check out this app on self.tray icon") self.tray.setVisible(True) menubar = self.menuBar() menubar.setNativeMenuBar(False) #Mac OS sync menu = QMenu('&도움말') filemenu = menu self.tray.setContextMenu(menu) filemenu.addAction(exitAction) filemenu.addAction(aboutAction) menubar.addMenu(menu) #센트럴 위젯 central = QWidget() #central.setStyleSheet("background-color:#333333; border-style:solid; border-width: 1px; border-color: #555555; border-radius: 4px;") self.idLine = QLineEdit() self.idLine.setPlaceholderText("Client ID를 입력") self.contentLine = QLineEdit() self.contentLine.setPlaceholderText("원하는 내용을 입력") self.statusLine = QLineEdit() self.statusLine.setPlaceholderText("원하는 상태를 입력") self.imageLine = QLineEdit() self.imageLine.setPlaceholderText("이미지 이름") self.okButton = QPushButton("적용") self.okButton.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.doingButton = QPushButton("설정하기") labelID = QLabel("Client 설정 :") labelID.setAlignment(Qt.AlignCenter) label0 = QLabel("~하는 중 :") label0.setAlignment(Qt.AlignCenter) label1 = QLabel("내용 : ") label1.setAlignment(Qt.AlignCenter) label2 = QLabel("상태 : ") label2.setAlignment(Qt.AlignCenter) label3 = QLabel("이미지 : ") label3.setAlignment(Qt.AlignCenter) grid = QGridLayout() grid.setContentsMargins(59, 50, 50, 50) grid.addWidget(labelID, 0, 0) grid.addWidget(self.idLine, 0, 1) grid.addWidget(label0, 1, 0) grid.addWidget(self.doingButton, 1, 1) grid.addWidget(label1, 2, 0) grid.addWidget(self.contentLine, 2, 1) grid.addWidget(label2, 3, 0) grid.addWidget(self.statusLine, 3, 1) grid.addWidget(label3, 4, 0) grid.addWidget(self.imageLine, 4, 1) grid.addWidget(self.okButton, 5, 0, 1, 2) temp = QPushButton("temp") grid.addWidget(temp, 6, 0, 1, 2) temp.clicked.connect(self.temms) #grid.setColumnStretch(0, 2) #grid.setColumnStretch(1, 2) central.setLayout(grid) self.setCentralWidget(central) #윈도우 기본 셋 self.setWindowTitle('Discord Custom GameActivity') self.setWindowIcon(QIcon('icon.png')) self.resize(400, 300) #self.setWindowFlags(Qt.FramelessWindowHint) #window frame hide #self.setAttribute(Qt.WA_TranslucentBackground) #remove border top side grabage self.center() self.show() #self.setStyleSheet("background-color: #333333;") #self -> style css type #이벤트 self.doingButton.clicked.connect(self.onDoingButton) self.okButton.clicked.connect(self.onOkButton)
def __init__(self, parent=None): QSystemTrayIcon.__init__(self, self.get_trayicon(), parent) self.activated.connect(self.on_activated) self.menu_is_visible = False
def __init__(self): QMainWindow.__init__(self) self.ui = Ui_MainWindow() self.ui.setupUi(self) self.ui.actionAdd_camera.triggered.connect(self.handle_add_camera_triggered) self.ui.actionOpen_dataset.triggered.connect(self.handle_open_dataset_triggered) self.ui.actionSave_dataset.triggered.connect(self.handle_save_dataset_triggered) self.ui.actionClose_dataset.triggered.connect(self.handle_close_dataset_triggered) self.ui.actionExport_to_JSON.triggered.connect(self.handle_export_to_json_triggered) self.startup_page = QWidget() self.startup_page_ui = Ui_StartupPage() self.startup_page_ui.setupUi(self.startup_page) self.startup_page_ui.addCamera_button.setDefaultAction(self.ui.actionAdd_camera) self.startup_page_ui.openDataset_button.setDefaultAction(self.ui.actionOpen_dataset) self.startup_page_ui.openDataset_button.setMenu(None) self.startup_page_ui.openDataset_button.setArrowType(Qt.NoArrow) self.startup_page_ui.label_recent_datasets_image_placeholder.setPixmap( QPixmap(':/icons/document-open-recent.svg')) self.startup_page_ui.label_recent_cameras_image_placeholder.setPixmap( QPixmap(':/icons/document-open-recent.svg')) self.startup_dataset_buttons: List[QToolButton] = [] self.startup_camera_buttons: List[QToolButton] = [] self.state = self.get_config_state() self.recent_datasets: List[Path] = [] self.recent_datasets_menu = QMenu() self.recent_datasets_menu.setToolTipsVisible(True) self.recent_dataset_qactions = [] for ds in self.state['recent_datasets']: self.add_dataset_entry_to_recent(Path(ds)) if len(self.recent_datasets) == 0: self.startup_page_ui.verticalLayout_recentDatasets.addWidget(self.startup_page_ui.label_noRecentDatasets) self.startup_page_ui.label_noRecentDatasets.setAlignment(Qt.AlignHCenter) self.startup_page_ui.label_noRecentDatasets.show() self.recent_datasets_menu.triggered.connect(lambda action: self.open_dataset(Path(action.toolTip()))) self.ui.actionOpen_dataset.setMenu(self.recent_datasets_menu) self.recent_cameras: List[Path] = [] self.recent_cameras_menu = QMenu() self.recent_cameras_menu.setToolTipsVisible(True) self.recent_cameras_qactions: Dict[str, QAction] = {} for cam in self.state['recent_cameras']: self.add_camera_entry_to_recent(Path(cam)) if len(self.recent_cameras) == 0: self.startup_page_ui.verticalLayout_recentCameras.addWidget(self.startup_page_ui.label_noRecentCameras) self.startup_page_ui.label_noRecentCameras.setAlignment(Qt.AlignHCenter) self.startup_page_ui.label_noRecentCameras.show() self.recent_cameras_menu.triggered.connect(self.handle_add_recent_camera_triggered) self.ui.actionAdd_camera.setMenu(self.recent_cameras_menu) self.dataset = Dataset() self.processing_widget = CameraProcessingWidget() self.processing_widget.set_dataset(self.dataset) self.processing_widget.camera_loaded.connect(self.handle_camera_added) self.processing_widget.processing_started.connect(self.show_progress_bar) self.processing_widget.processing_updated.connect(self.update_progress_bar) self.processing_widget.processing_stopped.connect(self.hide_progress_bar) self.processing_widget.stick_verification_needed.connect(self.notify_user) self.processing_widget.no_cameras_open.connect(self.handle_no_cameras_open) self.ui.stackedWidget.removeWidget(self.ui.page) self.ui.stackedWidget.removeWidget(self.ui.page_2) self.ui.stackedWidget.addWidget(self.startup_page) self.ui.stackedWidget.addWidget(self.processing_widget) self.ui.stackedWidget.setCurrentIndex(0) self.setCentralWidget(self.ui.stackedWidget) self.ui.toolBar.hide() self.progress_bar = QProgressBar() self.progress_bar.hide() skipped_q_indicator = QPixmap(24, 24) skipped_q_indicator.fill(QColor(150, 150, 150)) skipped_label = QLabel() skipped_label.setPixmap(skipped_q_indicator) bad_q_indicator = QPixmap(24, 24) bad_q_indicator.fill(QColor(200, 0, 0)) bad_label = QLabel() bad_label.setPixmap(bad_q_indicator) ok_q_indicator = QPixmap(24, 24) ok_q_indicator.fill(QColor(200, 100, 0)) ok_label = QLabel() ok_label.setPixmap(ok_q_indicator) good_q_indicator = QPixmap(24, 24) good_q_indicator.fill(QColor(100, 200, 0)) good_label = QLabel() good_label.setPixmap(good_q_indicator) status_box = QHBoxLayout() indicator_box = QHBoxLayout() self.statusBar().hide() indicator_box.addWidget(QLabel("Stick visibility:\t")) indicator_box.addWidget(skipped_label) indicator_box.addWidget(QLabel(" Skipped ")) indicator_box.addWidget(bad_label) indicator_box.addWidget(QLabel(" Bad ")) indicator_box.addWidget(ok_label) indicator_box.addWidget(QLabel(" OK ")) indicator_box.addWidget(good_label) indicator_box.addWidget(QLabel(" Good ")) status_box.addItem(indicator_box) status_box.addWidget(self.progress_bar) status_box.setAlignment(indicator_box, Qt.AlignLeft) status_box.setSpacing(100) status_widget = QWidget() status_widget.setLayout(status_box) self.statusBar().addPermanentWidget(status_widget, 1) self.progress_bar.setFormat("%v / %m") self.sys_tray = QSystemTrayIcon(QIcon(':icons/snowflake.svg')) self.sys_tray.show() self.thread_pool = QThreadPool()
class MainWindow(QMainWindow): def __init__(self): super(MainWindow, self).__init__() settings = QSettings() self.setup_trayicon() self.setup_ui() self.update_work_end_time() self.update_rest_end_time() self.setup_connections() self.timeFormat = "hh:mm:ss" self.time = QTime(0, 0, 0, 0) self.workTime = QTime(0, 0, 0, 0) self.restTime = QTime(0, 0, 0, 0) self.totalTime = QTime(0, 0, 0, 0) self.currentMode = Mode.work self.maxRepetitions = -1 self.currentRepetitions = 0 self.show() def leaveEvent(self, event): super(MainWindow, self).leaveEvent(event) self.tasksTableWidget.clearSelection() def closeEvent(self, event): super(MainWindow, self).closeEvent(event) settings = QSettings() settings.setValue("timer/work/hours", self.workHoursSpinBox.value()) settings.setValue("timer/work/minutes", self.workMinutesSpinBox.value()) settings.setValue("timer/work/seconds", self.workSecondsSpinBox.value()) settings.setValue("timer/rest/hours", self.restHoursSpinBox.value()) settings.setValue("timer/rest/minutes", self.restMinutesSpinBox.value()) settings.setValue("timer/rest/seconds", self.restSecondsSpinBox.value()) tasks = [] for i in range(self.tasksTableWidget.rowCount()): item = self.tasksTableWidget.item(i, 0) if not item.font().strikeOut(): tasks.append(item.text()) settings.setValue("tasks/tasks", tasks) def start_timer(self): try: if not self.timer.isActive(): self.create_timer() except: self.create_timer() def create_timer(self): self.timer = QTimer() self.timer.timeout.connect(self.update_time) self.timer.timeout.connect(self.maybe_change_mode) self.timer.setInterval(1000) self.timer.setSingleShot(False) self.timer.start() def pause_timer(self): try: self.timer.stop() self.timer.disconnect() except: pass def reset_timer(self): try: self.pause_timer() self.time = QTime(0, 0, 0, 0) self.display_time() except: pass def maybe_start_timer(self): if self.currentRepetitions != self.maxRepetitions: self.start_timer() started = True else: self.currentRepetitions = 0 started = False return started def update_work_end_time(self): self.workEndTime = QTime(self.workHoursSpinBox.value(), self.workMinutesSpinBox.value(), self.workSecondsSpinBox.value()) def update_rest_end_time(self): self.restEndTime = QTime(self.restHoursSpinBox.value(), self.restMinutesSpinBox.value(), self.restSecondsSpinBox.value()) def update_current_mode(self, mode: str): self.currentMode = Mode.work if mode == "work" else Mode.rest def update_time(self): self.time = self.time.addSecs(1) self.totalTime = self.totalTime.addSecs(1) if self.modeComboBox.currentText() == "work": self.workTime = self.workTime.addSecs(1) else: self.restTime = self.restTime.addSecs(1) self.display_time() def update_max_repetitions(self, value): if value == 0: self.currentRepetitions = 0 self.maxRepetitions = -1 else: self.maxRepetitions = 2 * value def maybe_change_mode(self): if self.currentMode is Mode.work and self.time >= self.workEndTime: self.reset_timer() self.modeComboBox.setCurrentIndex(1) self.increment_current_repetitions() started = self.maybe_start_timer() self.show_window_message( Status.workFinished if started else Status.repetitionsReached) elif self.currentMode is Mode.rest and self.time >= self.restEndTime: self.reset_timer() self.modeComboBox.setCurrentIndex(0) self.increment_current_repetitions() started = self.maybe_start_timer() self.show_window_message( Status.restFinished if started else Status.repetitionsReached) def increment_current_repetitions(self): if self.maxRepetitions > 0: self.currentRepetitions += 1 def insert_task(self): task = self.taskTextEdit.toPlainText() self.insert_tasks(task) def insert_tasks(self, *tasks): for task in tasks: if task: rowCount = self.tasksTableWidget.rowCount() self.tasksTableWidget.setRowCount(rowCount + 1) self.tasksTableWidget.setItem(rowCount, 0, QTableWidgetItem(task)) self.tasksTableWidget.resizeRowsToContents() self.taskTextEdit.clear() def delete_task(self): selectedIndexes = self.tasksTableWidget.selectedIndexes() if selectedIndexes: self.tasksTableWidget.removeRow(selectedIndexes[0].row()) def mark_task_as_finished(self, row, col): item = self.tasksTableWidget.item(row, col) font = self.tasksTableWidget.item(row, col).font() font.setStrikeOut(False if item.font().strikeOut() else True) item.setFont(font) def display_time(self): self.timeDisplay.display(self.time.toString(self.timeFormat)) self.statisticsRestTimeDisplay.display( self.restTime.toString(self.timeFormat)) self.statisticsWorkTimeDisplay.display( self.workTime.toString(self.timeFormat)) self.statisticsTotalTimeDisplay.display( self.totalTime.toString(self.timeFormat)) def show_window_message(self, status): if status is Status.workFinished: self.trayIcon.showMessage("Break", choice(work_finished_phrases), QIcon("icons/tomato.png")) elif status is Status.restFinished: self.trayIcon.showMessage("Work", choice(rest_finished_phrases), QIcon("icons/tomato.png")) else: self.trayIcon.showMessage("Finished", choice(pomodoro_finished_phrases), QIcon("icons/tomato.png")) self.resetButton.click() def setup_connections(self): self.playButton.clicked.connect(self.start_timer) self.playButton.clicked.connect( lambda: self.playButton.setDisabled(True)) self.playButton.clicked.connect( lambda: self.pauseButton.setDisabled(False)) self.playButton.clicked.connect( lambda: self.resetButton.setDisabled(False)) self.pauseButton.clicked.connect(self.pause_timer) self.pauseButton.clicked.connect( lambda: self.playButton.setDisabled(False)) self.pauseButton.clicked.connect( lambda: self.pauseButton.setDisabled(True)) self.pauseButton.clicked.connect( lambda: self.resetButton.setDisabled(False)) self.resetButton.clicked.connect(self.reset_timer) self.resetButton.clicked.connect( lambda: self.playButton.setDisabled(False)) self.resetButton.clicked.connect( lambda: self.pauseButton.setDisabled(True)) self.resetButton.clicked.connect( lambda: self.resetButton.setDisabled(True)) self.workHoursSpinBox.valueChanged.connect(self.update_work_end_time) self.workMinutesSpinBox.valueChanged.connect(self.update_work_end_time) self.workSecondsSpinBox.valueChanged.connect(self.update_work_end_time) self.restHoursSpinBox.valueChanged.connect(self.update_rest_end_time) self.restMinutesSpinBox.valueChanged.connect(self.update_rest_end_time) self.restSecondsSpinBox.valueChanged.connect(self.update_rest_end_time) self.modeComboBox.currentTextChanged.connect(self.update_current_mode) self.repetitionsSpinBox.valueChanged.connect( self.update_max_repetitions) self.acceptTaskButton.pressed.connect(self.insert_task) self.deleteTaskButton.pressed.connect(self.delete_task) self.tasksTableWidget.cellDoubleClicked.connect( self.mark_task_as_finished) def setup_ui(self): settings = QSettings() self.size_policy = sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) #TABWIDGET self.tabWidget = QTabWidget() self.pomodoroWidget = QWidget(self) self.pomodoroWidgetLayout = QVBoxLayout(self.pomodoroWidget) self.pomodoroWidget.setLayout(self.pomodoroWidgetLayout) # work self.workGroupBox = QGroupBox("Work") self.workGroupBoxLayout = QHBoxLayout(self.workGroupBox) self.workGroupBox.setLayout(self.workGroupBoxLayout) self.workHoursSpinBox = QSpinBox(minimum=0, maximum=24, value=settings.value( "timer/work/hours", 0), suffix="h", sizePolicy=self.size_policy) self.workMinutesSpinBox = QSpinBox(minimum=0, maximum=60, value=settings.value( "timer/work/minutes", 25), suffix="m", sizePolicy=self.size_policy) self.workSecondsSpinBox = QSpinBox(minimum=0, maximum=60, value=settings.value( "timer/work/seconds", 0), suffix="s", sizePolicy=self.size_policy) self.workGroupBoxLayout.addWidget(self.workHoursSpinBox) self.workGroupBoxLayout.addWidget(self.workMinutesSpinBox) self.workGroupBoxLayout.addWidget(self.workSecondsSpinBox) # rest self.restGroupBox = QGroupBox("Rest") self.restGroupBoxLayout = QHBoxLayout(self.restGroupBox) self.restGroupBox.setLayout(self.restGroupBoxLayout) self.restHoursSpinBox = QSpinBox(minimum=0, maximum=24, value=settings.value( "timer/rest/hours", 0), suffix="h", sizePolicy=self.size_policy) self.restMinutesSpinBox = QSpinBox(minimum=0, maximum=60, value=settings.value( "timer/rest/minutes", 5), suffix="m", sizePolicy=self.size_policy) self.restSecondsSpinBox = QSpinBox(minimum=0, maximum=60, value=settings.value( "timer/rest/seconds", 0), suffix="s", sizePolicy=self.size_policy) self.restGroupBoxLayout.addWidget(self.restHoursSpinBox) self.restGroupBoxLayout.addWidget(self.restMinutesSpinBox) self.restGroupBoxLayout.addWidget(self.restSecondsSpinBox) #OTHER self.otherGroupBox = QGroupBox("Other") self.otherGroupBoxLayout = QHBoxLayout(self.otherGroupBox) self.otherGroupBox.setLayout(self.otherGroupBoxLayout) self.repetitionsLabel = QLabel("Repetitions", sizePolicy=self.size_policy) self.repetitionsSpinBox = QSpinBox(minimum=0, maximum=10000, value=0, sizePolicy=self.size_policy, specialValueText="∞") self.modeLabel = QLabel("Mode", sizePolicy=self.size_policy) self.modeComboBox = QComboBox() self.modeComboBox.addItems(["work", "rest"]) self.otherGroupBoxLayout.addWidget(self.repetitionsLabel) self.otherGroupBoxLayout.addWidget(self.repetitionsSpinBox) self.otherGroupBoxLayout.addWidget(self.modeLabel) self.otherGroupBoxLayout.addWidget(self.modeComboBox) #LCDDISPLAY self.lcdDisplayGroupBox = QGroupBox("Time") self.lcdDisplayGroupBoxLayout = QHBoxLayout(self.lcdDisplayGroupBox) self.lcdDisplayGroupBox.setLayout(self.lcdDisplayGroupBoxLayout) self.timeDisplay = QLCDNumber(8, sizePolicy=self.size_policy) self.timeDisplay.setFixedHeight(100) self.timeDisplay.display("00:00:00") self.lcdDisplayGroupBoxLayout.addWidget(self.timeDisplay) #BUTTONS self.buttonWidget = QWidget() self.buttonWidgetLayout = QHBoxLayout(self.buttonWidget) self.buttonWidget.setLayout(self.buttonWidgetLayout) self.playButton = self.make_button("start", disabled=False) self.resetButton = self.make_button("reset") self.pauseButton = self.make_button("pause") self.buttonWidgetLayout.addWidget(self.pauseButton) self.buttonWidgetLayout.addWidget(self.playButton) self.buttonWidgetLayout.addWidget(self.resetButton) #CENTRALWIDGET self.pomodoroWidgetLayout.addWidget(self.workGroupBox) self.pomodoroWidgetLayout.addWidget(self.restGroupBox) self.pomodoroWidgetLayout.addWidget(self.otherGroupBox) self.pomodoroWidgetLayout.addWidget(self.lcdDisplayGroupBox) self.pomodoroWidgetLayout.addWidget(self.buttonWidget) #CREATE TASKS TAB self.tasksWidget = QWidget(self.tabWidget) self.tasksWidgetLayout = QVBoxLayout(self.tasksWidget) self.tasksWidget.setLayout(self.tasksWidgetLayout) self.inputWidget = QWidget() self.inputWidget.setFixedHeight(50) self.inputWidgetLayout = QHBoxLayout(self.inputWidget) self.inputWidgetLayout.setContentsMargins(0, 0, 0, 0) self.inputWidget.setLayout(self.inputWidgetLayout) self.taskTextEdit = QTextEdit( placeholderText="Describe your task briefly.", undoRedoEnabled=True) self.inputButtonContainer = QWidget() self.inputButtonContainerLayout = QVBoxLayout( self.inputButtonContainer) self.inputButtonContainerLayout.setContentsMargins(0, 0, 0, 0) self.inputButtonContainer.setLayout(self.inputButtonContainerLayout) self.acceptTaskButton = QToolButton(icon=QIcon("icons/check.png")) self.deleteTaskButton = QToolButton(icon=QIcon("icons/trash.png")) self.inputButtonContainerLayout.addWidget(self.acceptTaskButton) self.inputButtonContainerLayout.addWidget(self.deleteTaskButton) self.inputWidgetLayout.addWidget(self.taskTextEdit) self.inputWidgetLayout.addWidget(self.inputButtonContainer) self.tasksTableWidget = QTableWidget(0, 1) self.tasksTableWidget.setHorizontalHeaderLabels(["Tasks"]) self.tasksTableWidget.horizontalHeader().setStretchLastSection(True) self.tasksTableWidget.verticalHeader().setVisible(False) self.tasksTableWidget.setWordWrap(True) self.tasksTableWidget.setTextElideMode(Qt.ElideNone) self.tasksTableWidget.setEditTriggers(QAbstractItemView.NoEditTriggers) self.tasksTableWidget.setSelectionMode( QAbstractItemView.SingleSelection) self.insert_tasks(*settings.value("tasks/tasks", [])) self.tasksWidgetLayout.addWidget(self.inputWidget) self.tasksWidgetLayout.addWidget(self.tasksTableWidget) #CREATE STATISTICS TAB self.statisticsWidget = QWidget() self.statisticsWidgetLayout = QVBoxLayout(self.statisticsWidget) self.statisticsWidget.setLayout(self.statisticsWidgetLayout) self.statisticsWorkTimeGroupBox = QGroupBox("Work Time") self.statisticsWorkTimeGroupBoxLayout = QHBoxLayout() self.statisticsWorkTimeGroupBox.setLayout( self.statisticsWorkTimeGroupBoxLayout) self.statisticsWorkTimeDisplay = QLCDNumber(8) self.statisticsWorkTimeDisplay.display("00:00:00") self.statisticsWorkTimeGroupBoxLayout.addWidget( self.statisticsWorkTimeDisplay) self.statisticsRestTimeGroupBox = QGroupBox("Rest Time") self.statisticsRestTimeGroupBoxLayout = QHBoxLayout() self.statisticsRestTimeGroupBox.setLayout( self.statisticsRestTimeGroupBoxLayout) self.statisticsRestTimeDisplay = QLCDNumber(8) self.statisticsRestTimeDisplay.display("00:00:00") self.statisticsRestTimeGroupBoxLayout.addWidget( self.statisticsRestTimeDisplay) self.statisticsTotalTimeGroupBox = QGroupBox("Total Time") self.statisticsTotalTimeGroupBoxLayout = QHBoxLayout() self.statisticsTotalTimeGroupBox.setLayout( self.statisticsTotalTimeGroupBoxLayout) self.statisticsTotalTimeDisplay = QLCDNumber(8) self.statisticsTotalTimeDisplay.display("00:00:00") self.statisticsTotalTimeGroupBoxLayout.addWidget( self.statisticsTotalTimeDisplay) self.statisticsWidgetLayout.addWidget(self.statisticsTotalTimeGroupBox) self.statisticsWidgetLayout.addWidget(self.statisticsWorkTimeGroupBox) self.statisticsWidgetLayout.addWidget(self.statisticsRestTimeGroupBox) #ADD TABS self.timerTab = self.tabWidget.addTab(self.pomodoroWidget, QIcon("icons/timer.png"), "Timer") self.tasksTab = self.tabWidget.addTab(self.tasksWidget, QIcon("icons/tasks.png"), "Tasks") self.statisticsTab = self.tabWidget.addTab( self.statisticsWidget, QIcon("icons/statistics.png"), "Statistics") self.setCentralWidget(self.tabWidget) def make_button(self, text, iconPath=None, disabled=True): button = QPushButton(text, sizePolicy=self.size_policy) if iconPath: button.setIcon(QIcon(iconPath)) button.setDisabled(disabled) return button def setup_trayicon(self): self.trayIcon = QSystemTrayIcon(QIcon("icons/tomato.png")) self.trayIcon.setContextMenu(QMenu()) self.quitAction = self.trayIcon.contextMenu().addAction( QIcon("icons/exit.png"), "Quit", self.exit) self.quitAction.triggered.connect(self.exit) self.trayIcon.activated.connect(self.onActivate) self.trayIcon.show() def exit(self): self.close() app = QApplication.instance() if app: app.quit() def onActivate(self, reason): if reason == QSystemTrayIcon.Trigger: self.show()
#!/usr/bin/python # coding: UTF-8 import sys from PyQt5.QtGui import QIcon from PyQt5.QtWidgets import QApplication, QSystemTrayIcon from cracker.cracker import Cracker if __name__ == "__main__": app = QApplication(sys.argv) app.setApplicationName('Cracker') cracker = Cracker(app) cracker.run() icon = QIcon('icon.png') tray = QSystemTrayIcon(icon) tray.setIcon(icon) tray.setVisible(True) sys.exit(app.exec_())
class MyWindow(QtWidgets.QWidget, Ui_MainWindow): def __init__(self): super(MyWindow, self).__init__() self.setupUi(self) self.setWindowTitle('天气小工具') # 界面标题 self.setWindowOpacity(0.9) # 设置窗口透明度 self.setAttribute(QtCore.Qt.WA_TranslucentBackground) # 设置窗口背景透明 self.setWindowFlag(QtCore.Qt.FramelessWindowHint) # 隐藏边框 ''' 给无边框移动用的三个变量 ''' self._startPos = None self._endPos = None self._isTracking = False self.init_info() # 读取csv文件,初始化城市数据 self.cityComboBox.addItems(["{0}".format(x) for x in config.city_list]) # 初始化界面下拉框 # self.pushButton_refresh.clicked.connect(lambda: self.start_create("customer", 1)) # 可以传多个参数入线程 self.pushButton_refresh.clicked.connect( lambda: self.start_create()) # 界面的按钮事件需要提前注册 self.closeButton.clicked.connect(self.close) # 界面退出程序 self.miniButton.clicked.connect(self.init_tuopan) # 界面最小化 # self.miniButton.clicked.connect(self.showMinimized) # 界面最小化 self.trayIcon = QSystemTrayIcon(self) # 托盘 self.trayIcon.setIcon(QIcon(config.path_icon)) # 托盘图标 self.time_thread = Runthread_t(self) self.time_thread.nonglisignal.connect(self.displaynongli) # 刷新界面农历 self.time_thread.timesignal.connect(self.displaytime) # 刷新界面公历 self.time_thread.start() def init_tuopan(self): self.hide() restoreAction = QAction("&显示主窗口", self, triggered=self.showNormal) quitAction = QAction("&退出", self, triggered=self.close) self.trayIconMenu = QMenu(self) self.trayIconMenu.addAction(restoreAction) # self.trayIconMenu.addSeparator() # 分割线 self.trayIconMenu.addAction(quitAction) self.setWindowIcon(QIcon(config.path_icon)) self.trayIcon.setContextMenu(self.trayIconMenu) self.trayIcon.show() def init_info(self): File = open(config.path_csv, 'r') cityReader = csv.reader(File) cityData = list(cityReader) for i in cityData: if i[0] != '城市': config.city_Info[i[0]] = i[1] config.city_list.append(i[0]) print(config.city_list, config.city_Info) File.close() def mouseMoveEvent(self, e: QMouseEvent): # 重写移动事件 self._endPos = e.pos() - self._startPos self.move(self.pos() + self._endPos) def mousePressEvent(self, e: QMouseEvent): # 重写鼠标事件 if e.button() == Qt.LeftButton: self._isTracking = True self._startPos = QPoint(e.x(), e.y()) def mouseReleaseEvent(self, e: QMouseEvent): # 重写鼠标事件 if e.button() == Qt.LeftButton: self._isTracking = False self._startPos = None self._endPos = None def displaytime(self, s): # 界面刷新当前时间 self.timeLabel.setText(s) def displaynongli(self, s): # 界面刷新农历 self.yinliLabel.setText(s) # 可以建多个线程 # def start_create(self, type, Nums): # 可以传多个参数入线程 def start_create(self): self.pushButton_refresh.setDisabled(True) # self.thread = Runthread(self, type, Nums) # 可以传多个参数入线程 self.thread = Runthread(self) self.thread.cmysignal.connect(self.add_info) # 通过槽输出信号到前台界面 self.thread.weasignal.connect(self.refreshweather) # 刷新界面表格 self.thread.start() def refreshweather(self, weather): for i in range(2): for j in range(5): self.weaTable.setItem(j + 1, i + 1, QTableWidgetItem(weather[i][j])) def add_info(self, message): # 界面打印信息 if message == "结束!": self.pushButton_refresh.setDisabled(False)