def init_application(): app = QApplication(sys.argv) app.setQuitOnLastWindowClosed(False) mywindow = MainWindow() icon = QIcon('icon.png') # Create the tray tray = QSystemTrayIcon() tray.setIcon(icon) tray.setVisible(True) # Create the menu menu = QMenu() color_menu = QMenu('Player color') color_menu.addAction(mywindow.white_checkbox) color_menu.addAction(mywindow.black_checkbox) show_action = QAction('Show move') nothing_action = QAction('Nothing') exit_action = QAction('Exit') menu.addAction(show_action) menu.addMenu(color_menu) menu.addAction(nothing_action) menu.addAction(exit_action) show_action.triggered.connect(mywindow.show_move) exit_action.triggered.connect(QtWidgets.qApp.quit) # Add the menu to the tray tray.setContextMenu(menu) mywindow.show() app.exec_()
class Systray(QObject): trayIconMenu = None def __init__(self, parent): super().__init__(parent) self.trayIconMenu = ContextMenu(None) icon = QIcon.fromTheme("xware-desktop") 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 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()
def main(): app = QtWidgets.QApplication(sys.argv) app.setQuitOnLastWindowClosed(False) # Create the icon if darkdetect.isDark(): icon_image = "icon-light.png" else: icon_image = "icon-dark.png" icon = QIcon(icon_image) # Create the tray tray = QSystemTrayIcon() tray.setIcon(icon) tray.setVisible(True) # Create the menu menu = QMenu() action = QAction("A menu item") menu.addAction(action) # Add a Quit option to the menu. quit = QAction("Quit") quit.triggered.connect(app.quit) menu.addAction(quit) # Add the menu to the tray tray.setContextMenu(menu) sys.exit(app.exec_())
def main(): # Create the icon if sys.platform.startswith("win32"): icon = QIcon("icon.ico") elif sys.platform.startswith("darwin"): icon = QIcon("icon.icns") # Create the tray tray = QSystemTrayIcon() tray.setIcon(icon) tray.setVisible(True) # Create the background thread thread = TimerThread(stopFlag) # Create the menu menu = QMenu() action_next = QAction("Next background") action_next.triggered.connect(next) menu.addAction(action_next) action_quit = QAction("Quit") action_quit.triggered.connect(quit) menu.addAction(action_quit) # Add the menu to the tray tray.setContextMenu(menu) thread.start() app.exec_()
class SystemTray(object): # 程序托盘类 def __init__(self, w): self.app = app self.w = w QApplication.setQuitOnLastWindowClosed( False) # 禁止默认的closed方法,只能使用qapp.quit()的方法退出程序 self.w.show() # 不设置显示则为启动最小化到托盘 self.tp = QSystemTrayIcon(self.w) self.initUI() self.run() def initUI(self): # 设置托盘图标 self.tp.setIcon(QIcon('./d.ico')) def quitApp(self): # 退出程序 self.w.show() # w.hide() #设置退出时是否显示主窗口 re = QMessageBox.question(self.w, "提示", "退出系统", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if re == QMessageBox.Yes: self.tp.setVisible(False) # 隐藏托盘控件,托盘图标刷新不及时,提前隐藏 qApp.quit() # 退出程序 def message(self): # 提示信息被点击方法 print("弹出的信息被点击了") def act(self, reason): # 主界面显示方法 # 鼠标点击icon传递的信号会带有一个整形的值,1是表示单击右键,2是双击,3是单击左键,4是用鼠标中键点击 if reason == 2 or reason == 3: self.w.show() def run(self): a1 = QAction('&显示(Show)', triggered=self.w.show) a2 = QAction('&退出(Exit)', triggered=self.quitApp) tpMenu = QMenu() tpMenu.addAction(a1) tpMenu.addAction(a2) self.tp.setContextMenu(tpMenu) self.tp.show() # 不调用show不会显示系统托盘消息,图标隐藏无法调用 # 信息提示 # 参数1:标题 # 参数2:内容 # 参数3:图标(0没有图标 1信息图标 2警告图标 3错误图标),0还是有一个小图标 self.tp.showMessage('Hello', '我藏好了', icon=0) # 绑定提醒信息点击事件 self.tp.messageClicked.connect(self.message) # 绑定托盘菜单点击事件 self.tp.activated.connect(self.act) sys.exit(self.app.exec_()) # 持续对app的连接
class Tray(QWidget): # noinspection PyArgumentList def __init__(self): super().__init__() self.menu = QMenu() self.icon = QIcon("assets/icon.png") self.tray = QSystemTrayIcon() self.obj = worker.Worker() self.thread = QThread() self.obj.update_config.connect(self.update_config) self.obj.moveToThread(self.thread) self.obj.finished.connect(self.thread.quit) self.thread.started.connect(self.obj.config_watcher) # self.thread.finished.connect(app.exit) self.thread.start() self.init_ui() def init_ui(self): self.tray.setIcon(self.icon) self.tray.setVisible(True) self.rebuild_menu() def update_config(self): if self.rebuild_menu(): rumps.notification(title='Better LES', message='Configuration Reloaded', subtitle='') def rebuild_menu(self): config = None success = True try: with open('config.json') as f: config = json.load(f) except JSONDecodeError as e: print(e) error, location = str(e).split(':') rumps.notification( title='An error occurred while loading the configuration', message=location, subtitle=error) success = False self.menu.clear() iterate(self.menu, config) self.menu.addSeparator() self.menu.addAction('Quit Better LES', partial(exit)) # Add the menu to the tray self.tray.setContextMenu(self.menu) return success
def tray_icon(app, win): tray = QSystemTrayIcon(QIcon("icon.png"), win) menu = QMenu() action = menu.addAction("Quit") action.triggered.connect(app.quit) menu.addAction(action) tray.setContextMenu(menu) tray.setVisible(True) tray.setToolTip("Cute Ninja") tray.show()
def main(): d = getDateTime() # Global app app = QApplication(sys.argv) QApplication.setQuitOnLastWindowClosed(False) qIcon = QIcon('icons/shotty.png') app.setWindowIcon(qIcon) shotty = ShottyFullscreen() showNotification('Shotty', 'Running in the background') tray = QSystemTrayIcon() if tray.isSystemTrayAvailable(): tray.setIcon(QIcon('icons/shotty.png')) tray.setVisible(True) tray.show() # Add a menu trayMenu = QMenu() region_screenshot_action = QAction(QIcon("icons/screenshot.png"), 'Take region screenshot') full_screenshot_action = QAction(QIcon("icons/screenshot.png"), 'Take screenshot') settings_action = QAction(QIcon("icons/settings.png"), 'Settings') about_action = QAction(QIcon("icons/info.png"), 'About') exit_action = QAction(QIcon("icons/exit.png"), 'Exit Shoty') exit_action.triggered.connect(app.exit) about_action.triggered.connect(shotty.showShottyAboutWindow) region_screenshot_action.triggered.connect(shotty.initUI) # We need to pass checked because connect passes # a bool arg as first param full_screenshot_action.triggered.connect( lambda checked, date=getDateTime( ), x1=-1, y1=-1, x2=-1, y2=-1, im=screenshot( ): shotty.saveScreenShot(date, x1, y1, x2, y2, im=im[:, :, :3])) trayMenu.addAction(region_screenshot_action) trayMenu.addAction(full_screenshot_action) trayMenu.addAction(settings_action) trayMenu.addAction(about_action) trayMenu.addAction(exit_action) tray.setContextMenu(trayMenu) else: print("[ERROR] Can't instantiate tray icon") sys.exit(app.exec_())
class Tray: # create tray icon menu def __init__(self, ): # create systemtray UI self.icon = QIcon(ico) self.tray = QSystemTrayIcon() self.tray.setIcon(self.icon) self.tray.setToolTip("Now Playing ▶") self.tray.setVisible(True) self.menu = QMenu() # create systemtray options and actions self.actTitle = QAction("Now Playing v1.4") self.menu.addAction(self.actTitle) self.actTitle.setEnabled(False) self.actConfig = QAction("Settings") self.actConfig.triggered.connect(win.show) self.menu.addAction(self.actConfig) self.menu.addSeparator() self.actPause = QAction() self.actPause.triggered.connect(self.pause) self.menu.addAction(self.actPause) self.actPause.setEnabled(False) self.actExit = QAction("Exit") self.actExit.triggered.connect(self.cleanquit) self.menu.addAction(self.actExit) # add menu to the systemtray UI self.tray.setContextMenu(self.menu) def unpause(self): # unpause polling global paused paused = 0 self.actPause.setText('Pause') self.actPause.triggered.connect(self.pause) def pause(self): # pause polling global paused paused = 1 self.actPause.setText('Resume') self.actPause.triggered.connect(self.unpause) def cleanquit(self): # quit app and cleanup self.tray.setVisible(False) file = ConfigFile(config, config_file).file if file: writetrack(file) sys.exit()
class systemTray(QObject): cs = check_service def start_tray(self, ns, ip): # Initialize QApplication self.app = QApplication([]) self.app.setQuitOnLastWindowClosed(False) # Check service status ipc, state = self.cs(ns, ip) # Set icon icon = QIcon(srcdir + ipc) self.tray = QSystemTrayIcon() self.tray.setIcon(icon) self.tray.setVisible(True) self.tray.setToolTip(ns + " is " + state) # Set menu self.menu = QMenu() self.quit = QAction("Exit") self.quit.triggered.connect(self.app.quit) self.menu.addAction(self.quit) # Add menu to tray self.tray.setContextMenu(self.menu) # Create thread self.thread = QThread() # Create object which will be moved from main thread to worker thread self.worker = worker(ns, ip) # Move object to worker thread self.worker.moveToThread(self.thread) # Connect object to signal self.worker.newIcon.connect(self.updateIcon) self.worker.newToolTip.connect(self.updateToolTip) # Connect started signal to run method of object in worker thread self.thread.started.connect(self.worker.run) # Start thread self.thread.start() # Run tray self.app.exec_() def updateIcon(self, icon): self.tray.setIcon(icon) def updateToolTip(self, tooltip): self.tray.setToolTip(tooltip)
class SystemTray(object): # 程序托盘类 def __init__(self, w): self.app = app self.w = w QApplication.setQuitOnLastWindowClosed(False) # 禁止默认的closed方法, self.w.show() # 不设置显示则为启动最小化到托盘 self.tp = QSystemTrayIcon(self.w) self.initUI() self.run() def initUI(self): # 设置托盘图标 self.tp.setIcon(QIcon('/etc/v2rayL/images/logo.ico')) def quitApp(self): # 退出程序 self.w.show() # w.hide() #设置退出时是否显示主窗口 re = QMessageBox.question(self.w, "提示", "确认退出?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if re == QMessageBox.Yes: self.tp.setVisible(False) # 隐藏托盘控件 qApp.quit() # 退出程序 self.w.v2rayL.disconnect() def act(self, reason): # 主界面显示方法 if reason == 2 or reason == 3: self.w.show() def run(self): a1 = QAction('恢复(Show)', triggered=self.w.show) a3 = QAction('退出(Exit)', triggered=self.quitApp) tpMenu = QMenu() tpMenu.addAction(a1) tpMenu.addAction(a3) self.tp.setContextMenu(tpMenu) self.tp.show() # 不调用show不会显示系统托盘消息,图标隐藏无法调用 # 绑定提醒信息点击事件 # self.tp.messageClicked.connect(self.message) # 绑定托盘菜单点击事件 self.tp.activated.connect(self.act) sys.exit(self.app.exec_()) # 持续对app的连接
def _create_systemtray(self): ''' Create & add systemtray icon with controls. ''' if self._systemtray: return systemtray = QSystemTrayIcon(self) systemtray.setToolTip(app_data.app_name()) systemtray.setIcon(QIcon(app_data.app_logo_path())) systemtray.setVisible(True) menu = QMenu(self) about_action = menu.addAction(QIcon(app_data.about_logo_path()), "About") about_action.triggered.connect(self._about_clicked) exclusions_action = menu.addAction(QIcon(app_data.exclusions_logo_path()), "Exclusions") exclusions_action.triggered.connect(self._exclusions_clicked) quit_action = menu.addAction(QIcon(app_data.quit_logo_path()), "Quit") quit_action.triggered.connect(self._quit_clicked) systemtray.setContextMenu(menu) self._systemtray = systemtray
def run(self): app = self.app app.setQuitOnLastWindowClosed(False) # Create the icon icon = QIcon(self.img_icon) # Create the tray tray = QSystemTrayIcon() tray.setIcon(icon) tray.setVisible(True) # Create the menu menu = QMenu() self.open_site_link = open_site = QAction("Open") open_site.triggered.connect(self.open_site) menu.addAction(open_site) # Add a Quit option to the menu. quit = QAction("Quit") quit.triggered.connect(app.quit) menu.addAction(quit) # Add the menu to the tray tray.setContextMenu(menu) loop = QEventLoop(self.app) asyncio.set_event_loop(loop) web_app = web.Application() cors = aiohttp_cors.setup(web_app, defaults={ "*": aiohttp_cors.ResourceOptions( allow_credentials=True, expose_headers="*", allow_headers="*", ) }) cors.add(web_app.router.add_get('/', self.web_get_root)) cors.add(web_app.router.add_get('/inc-link/', self.web_get_inc_link)) return loop.run_until_complete(web._run_app(web_app, port=8766, host='127.0.0.1'))
class SystemTray(object): # 程序托盘类 def __init__(self, w): self.app = app self.w = w QApplication.setQuitOnLastWindowClosed( False) # 禁止默认的closed方法,只能使用qapp.quit()的方法退出程序 self.tray = QSystemTrayIcon(self.w) self.initUI() self.run() def initUI(self): # 设置托盘图标 self.tray.setIcon(QIcon('icon/tray.ico')) def quitApp(self): # 退出程序 re = QMessageBox.question(self.w, "提示", "退出系统", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if re == QMessageBox.Yes: self.tray.setVisible(False) # 隐藏托盘控件,托盘图标刷新不及时,提前隐藏 app.quit() # 退出程序 def capture(self): print('capture') pass def run(self): a1 = QAction('&截图 Ctrl 1', triggered=self.capture) a2 = QAction('&退出', triggered=self.quitApp) _translate = QtCore.QCoreApplication.translate trayMenu = QMenu() trayMenu.addAction(a1) trayMenu.addAction(a2) self.tray.setContextMenu(trayMenu) self.tray.show() # 不调用show不会显示系统托盘消息,图标隐藏无法调用 # 信息提示 sys.exit(self.app.exec_()) # 持续对app的连接
class Systray(QObject): app = None trayIconMenu = None def __init__(self, app): super().__init__() self.app = app self.mainWin = self.app.mainWin self.trayIconMenu = QMenu(None) icon = QIcon(":/image/thunder.ico") self.trayIcon = QSystemTrayIcon(None) self.trayIcon.setIcon(icon) self.trayIcon.setContextMenu(self.trayIconMenu) self.trayIcon.setVisible(True) self.trayIcon.activated.connect(self.slotSystrayActivated) self.app.lastWindowClosed.connect(self.slotTeardown) self.trayIconMenu.addAction(self.mainWin.action_exit) @pyqtSlot() def slotTeardown(self): print("teardown Systray") # On Ubuntu 13.10, systrayicon won't destroy itself gracefully, stops the whole program from exiting. del self.trayIcon @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 self.mainWin.isHidden() or self.mainWin.isMinimized(): self.mainWin.restore() else: self.mainWin.minimize()
def start(self): icon = QIcon(self.data_repository.get_res_file_path("clock.svg")) tray = QSystemTrayIcon() tray.setIcon(icon) tray.setVisible(True) menu = QMenu() self.action_quit.triggered.connect(lambda x: self._on_quit()) self.action_pause_continue.triggered.connect( lambda x: self._on_pause_continue()) menu.addAction(self.action_pause_continue) self.add_statistics_to_menu(menu) self.setup_settings(menu) menu.addAction(self.action_quit) tray.setContextMenu(menu) self.app.exec_()
class IconoSistema: def __init__(self, parent=None): self.iniciar(parent=parent) def iniciar(self, parent): self.icon = QIcon(Const.ICONO) self.tray = QSystemTrayIcon(parent=parent) self.tray.setIcon(self.icon) self.iconoSalir = QIcon.fromTheme("exit") self.agregarMenu() self.tray.show() def agregarMenu(self): menu = QMenu() #ultimaAlertaAction = menu.addAction(self.icon, 'Ver &Ultima alerta') #verAlertasAction = menu.addAction(self.iconoLista, "&Ver Alertas") menu.addSeparator() exitAction = menu.addAction(self.iconoSalir, "&Salir") exitAction.triggered.connect(QCoreApplication.instance().quit) #verAlertasAction.triggered.connect(lambda: self.verVentanaAlertas()) #ultimaAlertaAction.triggered.connect(lambda: self.verUltimaAlerta()) self.tray.activated.connect(self.onTrayIconActivated) self.tray.setVisible(True) self.tray.setContextMenu(menu) #self.showMessage('Aplicacion iniciada') Const.SYSTRAY = self def onTrayIconActivated(self, reason): if reason == QSystemTrayIcon.Trigger: # ignorar pass elif reason == QSystemTrayIcon.DoubleClick: #self.verVentanaAlertas() pass
class QTrayIcon(object): __options = [] def __init__(self, title=None, icon=None, parent=None): self.__trayIcon = QSystemTrayIcon(parent=parent) self.__context_menu = QMenu() self.__trayIcon.setContextMenu(self.__context_menu) self.set_icon(icon) self.set_title(title) def __add_option(self, text, function): option = QAction(text) option.triggered.connect(function) self.__context_menu.addAction(option) self.__options.append(option) def add_options(self, options): for option, function in options.items(): self.__add_option(option, function) def disable_option(self, index): self.__options[index].setEnabled(False) def enable_option(self, index): self.__options[index].setEnabled(True) def set_icon(self, icon): self.__icon = QIcon(icon) self.__trayIcon.setIcon(self.__icon) self.__trayIcon.setVisible(True) def set_title(self, title): self.__trayIcon.setToolTip(title) def show_message(self, title, message, duration=5000, icon=None): icon = QIcon(icon) if icon else self.__icon self.__trayIcon.showMessage(title, message, self.__icon, duration)
def runcatCPU(): app = QApplication(sys.argv) # 最后一个可视的窗口退出时程序不退出 app.setQuitOnLastWindowClosed(False) icon = QSystemTrayIcon() icon.setIcon(QIcon('icons/0.png')) icon.setVisible(True) cpu_percent = psutil.cpu_percent(interval=1) / 100 cpu_percent_update_fps = 20 fps_count = 0 while True: fps_count += 1 if fps_count > cpu_percent_update_fps: cpu_percent = psutil.cpu_percent(interval=1) / 100 fps_count = 0 # 开口向上的抛物线, 左边递减 time_interval = (cpu_percent * cpu_percent - 2 * cpu_percent + 2) / 20 for i in range(5): icon.setIcon(QIcon('icons/%d.png' % i)) icon.setToolTip('cpu: %.2f' % cpu_percent) time.sleep(time_interval) app.exec_()
def setTrayIcon(app, mainWindow): app.setQuitOnLastWindowClosed(False) icon = QIcon(APP_ICON) trayIcon = QSystemTrayIcon(parent=mainWindow, icon=icon) menu = QMenu(parent=mainWindow) settingsAction = QAction(text='Settings', parent=mainWindow) settingsAction.triggered.connect(mainWindow.showSettingsWindow) menu.addAction(settingsAction) aboutAction = QAction(text='About', parent=mainWindow) aboutAction.triggered.connect(mainWindow.showAboutWindow) menu.addAction(aboutAction) quitAction = QAction(text='Quit', parent=mainWindow) quitAction.triggered.connect(app.quit) menu.addAction(quitAction) trayIcon.activated.connect(mainWindow.setFocusOnWindow) trayIcon.setContextMenu(menu) trayIcon.setVisible(True)
def runcatMemory(): app = QApplication(sys.argv) # 最后一个可视的窗口退出时程序不退出 app.setQuitOnLastWindowClosed(False) icon = QSystemTrayIcon() icon.setIcon(QIcon('icons/0.png')) icon.setVisible(True) memory_percent = psutil.virtual_memory().percent / 100 memory_percent_update_fps = 20 fps_count = 0 while True: fps_count += 1 if fps_count > memory_percent_update_fps: memory_percent = psutil.virtual_memory().percent / 100 fps_count = 0 # 开口向上的抛物线, 左边递减 time_interval = (memory_percent * memory_percent - 2 * memory_percent + 2) / 20 for i in range(5): icon.setIcon(QIcon('icons/%d.png' % i)) icon.setToolTip('memory: %.2f' % memory_percent) time.sleep(time_interval) app.exec_()
class Main(QMainWindow, form_class): def __init__(self): super().__init__() self.setupUi(self) self.trayIcon = QSystemTrayIcon(self) self.trayIcon.setIcon(QIcon('ui/icon.png')) self.trayIcon.activated.connect(self.restore_window) self.WM = WindowManager() self.pre_window = self.WM.get_fore_window() self.rnn = RNN() self.runState = False self.startButton.clicked.connect(self.btn_clk_start) self.startState = True self.trainButton.clicked.connect(self.btn_clk_train) self.helpButton.clicked.connect(self.btn_clk_help) self.helpState = True self.timer = QTimer(self) self.timer.start(200) self.timer.timeout.connect(self.run) def restore_window(self, reason): if reason == QSystemTrayIcon.DoubleClick: self.trayIcon.hide() self.showNormal() def btn_clk_start(self): if self.startState: self.startState = False self.startButton.setText('Stop') self.runState = True self.back = Background() self.back.show() self.trayIcon.setVisible(True) self.hide() else: self.startState = True self.startButton.setText('Start') self.runState = False self.back.close() self.timer.stop() def btn_clk_train(self): if os.path.isfile('params.pkl'): os.remove('params.pkl') time.sleep(2) self.rnn = RNN() self.rnn.train() QMessageBox.information(self, "RNN", "train finished") def btn_clk_help(self): if self.helpState: self.helpState = False self.helpButton.setText('go to tray icon') QMessageBox.information(self, "Help", "*****@*****.**") else: self.helpState = True self.helpButton.setText('Help') self.trayIcon.setVisible(True) self.hide() def get_hash(self, text): hash = hashlib.md5() hash.update(text.encode()) return hash.hexdigest() def run(self): try: with open("data.txt", 'a') as f: cur_window = self.WM.get_fore_window().split()[0] if cur_window != self.pre_window: self.pre_window = cur_window cur_window_hash = self.get_hash(cur_window) f.write(" " + cur_window_hash) if self.runState: target_windows = list( self.rnn.test([cur_window_hash], 3)) target_windows.append(cur_window_hash) print(target_windows) self.WM.set_window(self.WM.find_window('dimmer')) all_windows = list(self.WM.get_windows()) for t_window in target_windows: for i, window in enumerate(all_windows): if t_window == self.get_hash( window.split()[0]): self.WM.set_window( self.WM.find_window(all_windows[i])) except: pass
return os.path.join(base_path, relative_path) icon_start = QIcon(resource_path("icon/icon_start.png")) icon_stop = QIcon(resource_path("icon/icon_stop.png")) app.setWindowIcon(icon_stop) def tray_icon_clicked(): window.activateWindow() window.showNormal() tray = QSystemTrayIcon() tray.setIcon(icon_stop) tray.setVisible(True) # noinspection PyUnresolvedReferences tray.activated.connect(tray_icon_clicked) database = Database() active_job: Union[Job, None] = None def start_timer(): timer.start() clock.setStyleSheet('font-size: 18px; color: green; font-weight: bold') app.setWindowIcon(icon_start) tray.setIcon(icon_start) def stop_timer():
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) 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) ## 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 splash = None def createSplash(self): if not self.getCommandLineOption("headless"): try: QtApplication.splash = self._createSplashScreen() except FileNotFoundError: QtApplication.splash = None else: if QtApplication.splash: QtApplication.splash.show() self.processEvents() ## Display text on the splash screen. def showSplashMessage(self, message): if not QtApplication.splash: self.createSplash() if QtApplication.splash: QtApplication.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 QtApplication.splash: QtApplication.splash.close() QtApplication.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 SystemTray(object): # 程序托盘类 def __init__(self, w): self.app = app self.thread = Thread() self.w = w QApplication.setQuitOnLastWindowClosed( False) # 禁止默认的closed方法,只能使用qapp.quit()的方法退出程序 self.w.hide() # 不设置显示则为启动最小化到托盘 self.tp = QSystemTrayIcon(self.w) self.initUI() self.run() def kill_v2ray_process(self, name="v2ray"): child = subprocess.Popen(["pgrep", "-f", name], stdout=subprocess.PIPE, shell=False) pid = int(child.communicate()[0]) os.kill(pid, signal.SIGKILL) def initUI(self): # 设置托盘图标 self.tp.setIcon(QIcon('icons.ico')) def quitApp(self): # 退出程序 # self.w.show() # w.hide() #设置退出时是否显示主窗口 # re = QMessageBox.question(self.w, "Notification", "Quit ?", # QMessageBox.Yes | QMessageBox.No, # QMessageBox.No) # if re == QMessageBox.Yes: self.tp.setVisible(False) # 隐藏托盘控件,托盘图标刷新不及时,提前隐藏 # self.thread.quit() self.kill_v2ray_process() qApp.quit() # 退出程序 def message(self): # 提示信息被点击方法 print("弹出的信息被点击了") def act(self, reason): # 主界面显示方法 # 鼠标点击icon传递的信号会带有一个整形的值,1是表示单击右键,2是双击,3是单击左键,4是用鼠标中键点击 if reason == 2 or reason == 3: self.w.show() def run(self): a1 = QAction('&显示(Show)', triggered=self.w.show) a2 = QAction('&退出(Exit)', triggered=self.quitApp) self.thread.start() # self.thread.trigger.connect("v2ray") #self.thread.quit() # self.thread.started() tpMenu = QMenu() tpMenu.addAction(a1) tpMenu.addAction(a2) self.tp.setContextMenu(tpMenu) self.tp.show() # 不调用show不会显示系统托盘消息,图标隐藏无法调用 # 信息提示 # 参数1:标题 # 参数2:内容 # 参数3:图标(0没有图标 1信息图标 2警告图标 3错误图标),0还是有一个小图标 # self.tp.showMessage('Hello', '我藏好了', icon=0) # 绑定提醒信息点击事件 # self.tp.messageClicked.connect(self.message) # 绑定托盘菜单点击事件 self.tp.activated.connect(self.act) sys.exit(self.app.exec_()) # 持续对app的连接
class GoTrayUI(QDialog): def __init__(self): super().__init__() self.GotoTrayUI() def GotoTrayUI(self): ### Font configure font = QtGui.QFont() font.setBold(True) ### remove title bar self.setWindowFlags(QtCore.Qt.FramelessWindowHint|QtCore.Qt.WindowStaysOnBottomHint) ### background image and Fixed Size back_label = QLabel(self) back_label.setGeometry(QtCore.QRect(2,2,400,250)) back = QPixmap('.\\img\\33_back.png') back_label.setPixmap(back) ### close Button ( check box checked = go tray ) Close_btn = QPushButton('',self) Close_btn.setIcon(QIcon('.\\img\\Close_btn.png')) Close_btn.setFixedSize(20,20) Close_btn.setIconSize(QtCore.QSize(30,30)) Close_btn.move(360,20) Close_btn.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) ### check box ( checked = go tray | non_checked = exit ) self.check_box = QCheckBox("Tray Icon으로 보내시겠습니까?",self) self.check_box.setFont(font) self.check_box.setStyleSheet("color : white") self.check_box.move(80,80) self.check_box.resize(300,50) ### Exit Button ( check box checked = go tray ) Exit_btn = QPushButton('',self) Exit_btn.setText('종료') Exit_btn.setFixedSize(80,40) Exit_btn.move(100,160) Exit_btn.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) ### Cancel Button Cancel_btn = QPushButton('',self) Cancel_btn.setText('취소') Cancel_btn.setFixedSize(80,40) Cancel_btn.move(240,160) Cancel_btn.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) # TrayIcon Setting self.tray_icon = QSystemTrayIcon(self) self.tray_icon.setIcon(QIcon('.\\img\\Icon.png')) show_action = QAction("실행", self) quit_action = QAction("종료", self) ### Tray Icon menu - 실행 - show process_UI show_action.triggered.connect(self.active_tray) ### Tray Icon menu - 종료 - exit quit_action.triggered.connect(QApplication.quit) tray_menu = QMenu() tray_menu.addAction(show_action) tray_menu.addAction(quit_action) ### Tray_menu set self.tray_icon.setContextMenu(tray_menu) Cancel_btn.clicked.connect(self.hide) Close_btn.clicked.connect(self.closeEvent) Exit_btn.clicked.connect(self.closeEvent) ### tray visible False self.tray_icon.setVisible(True) self.tray_icon.setVisible(False) ### Chkbox clicked & Close def closeEvent(self): if self.check_box.isChecked(): self.hide() self.tray_icon.setVisible(True) ### show Message - Tray self.tray_icon.showMessage("Kx","Tray Icon has been set") ### processing UI hide set self = proc_self self.hide() else: exit() ### traymenu - execute def active_tray(self): self.tray_icon.setVisible(False) self = proc_self self.show() ### MousePressEvent & MouseMoveEvent = drag window def mousePressEvent(self, event): self.offset = event.pos() def mouseMoveEvent(self, event): x=event.globalX() y=event.globalY() x_w = self.offset.x() y_w = self.offset.y() self.move(x-x_w, y-y_w)
class QtApplication(QApplication, Application): 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] 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() # 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") self.setAttribute(Qt.AA_UseDesktopOpenGL) major_version, minor_version, profile = OpenGLContext.detectBestOpenGLVersion( ) 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() # Read preferences here (upgrade won't work) to get the language in use, so the splash window can be shown in # the correct language. 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) 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") # 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: 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) 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() self.showSplashMessage( self._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 def _onJobFinished(self, job: Job) -> None: if isinstance(job, WriteFileJob): if not job.getResult() or not job.getAddToRecentFiles(): # For a write file job, if it failed or it doesn't need to be added to the recent files list, we do not # add it. return elif (not isinstance(job, ReadMeshJob) and not isinstance(job, ReadFileJob)) or not job.getResult(): return if isinstance(job, (ReadMeshJob, ReadFileJob, WriteFileJob)): 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 ## 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. @pyqtSlot(result="QObject*") def getBackend(self) -> Backend: 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) -> Backend: return self.getBackend() ## 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 splash = None # type: Optional[QSplashScreen] 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() ## Display text on the splash screen. def showSplashMessage(self, message: str) -> None: 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) ## Close the splash screen after the application has started. def closeSplash(self) -> None: if QtApplication.splash: QtApplication.splash.close() QtApplication.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"]: 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 ## Delete all nodes containing mesh data in the scene. # \param only_selectable. Set this to False to delete objects from all build plates @pyqtSlot() def deleteAll(self, only_selectable=True) -> None: self.getController().deleteAllNodesWithMeshData(only_selectable) ## Get the MeshFileHandler of this application. def getMeshFileHandler(self) -> MeshFileHandler: 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 ## Gets the instance of this application. # # This is just to further specify the type of Application.getInstance(). # \return The instance of this application. @classmethod def getInstance(cls, *args, **kwargs) -> "QtApplication": 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 Parse99(QApplication): 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 _settings_valid(self): valid = True if self.settings.get_value('general', 'first_run') is None: self._system_tray.showMessage( "Parse99", """It looks like this is the first time the program is being run. Please setup the application using the Settings option once you right click the system tray icon.""" ) self.edit_settings() self.settings.set_value('general', 'first_run', True) valid = False elif self.settings.get_value('general', 'eq_directory') is None: self._system_tray.showMessage( "Parse99", "Please enter the General settings and \ choose the location of your Everquest Installation." ) self.edit_settings() valid = False elif self.settings.get_value('characters', None) is None: self._system_tray.showMessage( "Parse99", "No characters have been made. \ Please create at least one character using settings." ) self.edit_settings(tab="characters") valid = False elif self.settings.get_value('general', 'current_character') is None: self._system_tray.showMessage( "Parse99", "No character has been selected. \ Please choose a character from the Character menu." ) valid = False return valid def toggle(self, switch): if switch == 'off': if self._thread is not None: self._timer.stop() self._thread.stop() self._thread.join() elif switch == 'on': if self._settings_valid(): characters = self.settings.get_value('characters', None) log_file = characters[ self.settings.get_value('general', 'current_character') ]['log_file'] self._thread = FileReader( log_file, int(self.settings.get_value('general', 'parse_interval')) ) self._thread.start() self._timer.start( 1000 * int(self.settings.get_value('general', 'parse_interval')) ) def _parse(self): for line in self._thread.get_new_lines(): for plugin in self._plugins.keys(): if self._plugins[plugin].is_active(): self._plugins[plugin].parse(line) def _get_menu(self): # main menu menu = QMenu() main_menu_action_group = QActionGroup(menu) main_menu_action_group.setObjectName("main") # character menu map_action = QAction(menu) map_action.setText("Toggle Map") main_menu_action_group.addAction(map_action) separator = QAction(menu) separator.setSeparator(True) main_menu_action_group.addAction(separator) characters_action = QAction(menu) characters_action.setText("Switch Characters") main_menu_action_group.addAction(characters_action) separator = QAction(menu) separator.setSeparator(True) main_menu_action_group.addAction(separator) settings_action = QAction(menu) settings_action.setText("Settings") main_menu_action_group.addAction(settings_action) quit_action = QAction(menu) quit_action.setText("Quit") main_menu_action_group.addAction(quit_action) menu.addActions(main_menu_action_group.actions()) menu.triggered[QAction].connect(self._menu_actions) return menu def update_menu(self): self._system_tray.contextMenu().disconnect() self._system_tray.setContextMenu(self._get_menu()) def _menu_actions(self, action): # ag = action group, at = action text ag = action.actionGroup().objectName() at = action.text().lower() if ag == "main": if at == "quit": try: self.toggle('off') self._system_tray.setVisible(False) for plugin in self._plugins.keys(): self._plugins[plugin].close() self.quit() except Exception as e: print("menu actions, quit", e) elif at == "settings": self.edit_settings(tab="general") elif at == "switch characters": print("switch characters") self.edit_settings(tab="characters") elif at == "toggle map": self._plugins['maps'].toggle() def edit_settings(self, **kwargs): try: if not self.settings.gui.isVisible(): self.toggle('off') self.settings.gui.set_show_tab(kwargs.get('tab', None)) self.settings.gui.exec() self.toggle('on') except Exception as e: print("parse99.edit_settings():", e)
class QtApplication(QApplication, Application): 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] 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() # 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") 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 and not self.getIsHeadLess(): 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: 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() self._package_manager.initialize() # Read preferences here (upgrade won't work) to get the language in use, so the splash window can be shown in # the correct language. 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) 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._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. 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: Logger.log("i", "The preferences file cannot be found, will use default values") # Force the configuration file to be written again since the list of plugins to remove maybe changed self.showSplashMessage(i18n_catalog.i18nc("@info:progress", "Loading preferences...")) 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") # 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: 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) def initializeEngine(self) -> None: # TODO: Document native/qml import trickery self._qml_engine = QQmlApplicationEngine(self) 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._qml_engine.rootContext().setContextProperty("screenScaleFactor", self._screenScaleFactor()) self.registerObjects(self._qml_engine) Bindings.register() 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 def _onJobFinished(self, job: Job) -> None: if isinstance(job, WriteFileJob): if not job.getResult() or not job.getAddToRecentFiles(): # For a write file job, if it failed or it doesn't need to be added to the recent files list, we do not # add it. return elif (not isinstance(job, ReadMeshJob) and not isinstance(job, ReadFileJob)) or not job.getResult(): return if isinstance(job, (ReadMeshJob, ReadFileJob, WriteFileJob)): 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 ## 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. @pyqtSlot(result = "QObject*") def getBackend(self) -> Backend: 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) -> Backend: return self.getBackend() ## 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 splash = None # type: Optional[QSplashScreen] 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() ## Display text on the splash screen. def showSplashMessage(self, message: str) -> None: if not QtApplication.splash: self.createSplash() if QtApplication.splash: QtApplication.splash.showMessage(message, Qt.AlignHCenter | Qt.AlignVCenter) self.processEvents() elif self.getIsHeadLess(): Logger.log("d", message) ## Close the splash screen after the application has started. def closeSplash(self) -> None: if QtApplication.splash: QtApplication.splash.close() QtApplication.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"]: 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 ## Delete all nodes containing mesh data in the scene. # \param only_selectable. Set this to False to delete objects from all build plates @pyqtSlot() def deleteAll(self, only_selectable = True) -> None: Logger.log("i", "Clearing scene") if not self.getController().getToolsEnabled(): return nodes = [] for node in DepthFirstIterator(self.getController().getScene().getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. if not isinstance(node, SceneNode): continue if (not node.getMeshData() and not node.callDecoration("getLayerData")) and not node.callDecoration("isGroup"): continue # Node that doesnt have a mesh and is not a group. if only_selectable and not node.isSelectable(): continue if not node.callDecoration("isSliceable") and not node.callDecoration("getLayerData") and not node.callDecoration("isGroup"): continue # Only remove nodes that are selectable. if node.getParent() and cast(SceneNode, node.getParent()).callDecoration("isGroup"): continue # Grouped nodes don't need resetting as their parent (the group) is resetted) nodes.append(node) if nodes: op = GroupedOperation() for node in nodes: op.addOperation(RemoveSceneNodeOperation(node)) # Reset the print information self.getController().getScene().sceneChanged.emit(node) op.push() Selection.clear() ## Get the MeshFileHandler of this application. def getMeshFileHandler(self) -> MeshFileHandler: 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 ## Gets the instance of this application. # # This is just to further specify the type of Application.getInstance(). # \return The instance of this application. @classmethod def getInstance(cls, *args, **kwargs) -> "QtApplication": 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
def main(): exitcode = 0 try: menu = QMenu() # Proxy m_proxy = QAction("Proxy: Disabled") m_proxy.setShortcut('Ctrl+P') m_proxy.setCheckable(True) m_proxy.setDisabled(True) m_proxy.triggered.connect(lambda: current['proxy'].disable(m_proxy)) menu.addAction(m_proxy) proxy_dict = {} proxy_group = QActionGroup(menu) for proxy in profile.get_items('Proxy'): proxyname = proxy[0] proxy_dict[proxyname] = Proxy(proxy, m_proxy) proxy_group.addAction(proxy_dict[proxyname].QAction) menu.addAction(proxy_dict[proxyname].QAction) # Bypass menu.addSeparator() m_bypass = QAction("Bypass: Disabled") m_bypass.setShortcut('Ctrl+B') m_bypass.setCheckable(True) m_bypass.setDisabled(True) m_bypass.triggered.connect(lambda: current['bypass'].disable(m_bypass)) menu.addAction(m_bypass) bypass_dict = {} bypass_group = QActionGroup(menu) for bypass in profile.get_items('Bypass'): bypassname = bypass[0] bypass_dict[bypassname] = Bypass(bypass, m_bypass) bypass_group.addAction(bypass_dict[bypassname].QAction) menu.addAction(bypass_dict[bypassname].QAction) # Capture menu.addSeparator() m_capture = QAction("Capture: Disabled") m_capture.setShortcut('Ctrl+C') m_capture.setCheckable(True) m_capture.setDisabled(True) m_dashboard = QAction("Open Dashboard...") m_dashboard.setShortcut('Ctrl+D') m_dashboard.setDisabled(True) m_capture.triggered.connect(lambda: current['capture'].disable(m_capture, m_dashboard)) menu.addAction(m_capture) capture_dict = {} capture_group = QActionGroup(menu) for capture in profile.get_items('Capture'): capturename = capture[0] capture_dict[capturename] = Capture(capture, m_capture, m_dashboard) capture_group.addAction(capture_dict[capturename].QAction) menu.addAction(capture_dict[capturename].QAction) menu.addAction(m_dashboard) # Common m_log = QAction("Log Folder") m_log.setShortcut('Ctrl+L') m_log.triggered.connect(lambda: subprocess.call(["open", log_path])) m_profile = QAction("Profile Folder") m_profile.setShortcut('Ctrl+F') m_profile.triggered.connect(lambda: subprocess.call(["open", profile_path])) m_extension = QAction("Extension Folder") m_extension.setShortcut('Ctrl+E') m_extension.triggered.connect(lambda: subprocess.call(["open", ext_path])) m_copy_shell = QAction("Copy Shell Command") m_copy_shell.setShortcut('Ctrl+S') m_set_system = QAction("As System Proxy: " + user_port) m_set_system.setShortcut('Ctrl+A') m_set_system.triggered.connect(lambda: setproxy_menu(m_set_system)) m_copy_shell.triggered.connect(copy_shell) m_set_system.setCheckable(True) if system: m_set_system.setChecked(True) setproxy() menu.addSeparator() menu.addAction(m_set_system) menu.addSeparator() menu.addAction(m_log) menu.addAction(m_profile) menu.addAction(m_extension) menu.addAction(m_copy_shell) menu.addSeparator() m_quit = QAction('Quit V2Net (' + VERSION + ')') m_quit.setShortcut('Ctrl+Q') m_quit.triggered.connect(APP.quit) menu.addAction(m_quit) # Add Tray tray = QSystemTrayIcon() tray.setIcon(QIcon("icon.png")) tray.setVisible(True) tray.setContextMenu(menu) # sys.exit(app.exec_()) exitcode = APP.exec_() finally: quitapp(exitcode)
class MainWindow(QWidget): 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 set_log_to_file(self, state): logger = logging.getLogger(__name__) file = logging.FileHandler(HOME + '\\switcher.log') if state == QtCore.Qt.Checked: self.log_to_file = True file.setFormatter( logging.Formatter('%(filename)s[LINE:%(lineno)d]# %(levelname)-8s [%(asctime)s] %(message)s')) file.setLevel(self.get_numeric_loglevel(options['log_level'])) logger.addHandler(file) else: if 'file' in logger.handlers: logger.removeHandler(file) self.log_to_file = False def starter(self): if not self.running: self.running = True self.start_btn.setText('Stop switcher') self.systemTrayIcon.setIcon(QtGui.QIcon('C:\\Users\\Admin\\Pictures\\tray_stop.jpg')) start_app() elif self.running: self.running = False self.start_btn.setText('Start switcher') self.systemTrayIcon.setIcon(QtGui.QIcon('C:\\Users\\Admin\\Pictures\\tray_start.jpg')) stop_app() return def main_window_ui(self): grid = QGridLayout(self) self.setLayout(grid) grid.setSpacing(5) # Here goes options layout self.topleft = QFrame(self) self.topleft.setFrameShape(QFrame.StyledPanel) self.topleft_grid = QGridLayout(self) self.topleft.setLayout(self.topleft_grid) self.switch_comb_label = QLabel('Switch combination:') self.switch_comb_text = QLineEdit() self.switch_comb_text.setText(options['switch_combination']) self.hotkey_label = QLabel('Hotkey:') self.hotkey_comb_text = QLineEdit() self.hotkey_comb_text.setText(options['hotkey']) self.topleft_grid.addWidget(self.switch_comb_label, 0, 0) self.topleft_grid.addWidget(self.switch_comb_text, 1, 0) self.topleft_grid.addWidget(self.hotkey_label, 2, 0) self.topleft_grid.addWidget(self.hotkey_comb_text, 3, 0) grid.addWidget(self.topleft, 0, 0) self.topright = QFrame(self) self.topright.setFrameShape(QFrame.StyledPanel) self.topright_grid = QGridLayout(self) self.topright.setLayout(self.topright_grid) self.info_label = QLabel('===INFO===') self.info_label.setAlignment(QtCore.Qt.AlignHCenter) self.info_author = QLabel('Author: Kurashov Sergey') self.info_author.setAlignment(QtCore.Qt.AlignHCenter) self.info_contacts = QLabel('Contacts: [email protected]') self.info_contacts.setAlignment(QtCore.Qt.AlignHCenter) self.info_sourcecode = QLabel('<a href="https://github.com/shimielder/win_switcher">Sourcecode on GitHub</a>') self.info_sourcecode.setAlignment(QtCore.Qt.AlignHCenter) self.info_sourcecode.setOpenExternalLinks(True) self.topright_grid.addWidget(self.info_label, 0, 0) self.topright_grid.addWidget(self.info_author, 1, 0) self.topright_grid.addWidget(self.info_contacts, 2, 0) self.topright_grid.addWidget(self.info_sourcecode, 3, 0) grid.addWidget(self.topright, 0, 1) self.middle = QFrame(self) self.middle.setFrameShape(QFrame.StyledPanel) self.middle_grid = QGridLayout(self) self.middle.setLayout(self.middle_grid) self.dictionsries_label = QLabel('Dictionaries to switch:') self.dict_one = QPlainTextEdit() self.dict_one.clear() self.dict_one.appendPlainText(options['layouts'][0]) self.dict_two = QPlainTextEdit() self.dict_two.clear() self.dict_two.appendPlainText(options['layouts'][1]) self.middle_grid.addWidget(self.dictionsries_label, 0, 0, 1, 4) self.middle_grid.addWidget(self.dict_one, 1, 0, 1, 4) self.middle_grid.addWidget(self.dict_two, 2, 0, 1, 4) grid.addWidget(self.middle, 1, 0, 1, 2) self.bottom = QFrame(self) self.bottom.setFrameShape(QFrame.StyledPanel) self.bottom_grid = QGridLayout(self) self.bottom.setLayout(self.bottom_grid) self.loglevel_label = QLabel('Logging level:') self.loglevel_dropmenu = QComboBox(self) self.loglevel_dropmenu.addItems(LOGGING_LEVELS) self.loglevel_dropmenu.setCurrentIndex(LOGGING_LEVELS.index((options['log_level'].upper()))) self.loglevel_dropmenu.activated[str].connect(self.set_logginglevel) # self.log_to_file_label = QLabel('Check to save logs to file') self.log_to_file_chk = QCheckBox('Check to save logs to file') self.log_to_file_chk.stateChanged.connect(self.set_log_to_file) self.logging_output_label = QLabel('Logging output:') self.logging_output_label.setAlignment(QtCore.Qt.AlignHCenter) self.bottom_grid.addWidget(self.loglevel_label, 0, 0) self.bottom_grid.addWidget(self.loglevel_dropmenu, 0, 1) self.bottom_grid.addWidget(self.log_to_file_chk, 0, 3, 1, 1) self.bottom_grid.addWidget(self.logging_output_label, 1, 0, 1, 4) self.bottom_grid.addWidget(self.log_textbox.widget, 2, 0, 2, 4) grid.addWidget(self.bottom, 2, 0, 1, 2) self.bottom_buttons = QFrame(self) # self.bottom_buttons.setFrameShape(QFrame.StyledPanel) self.bottom_buttons_grid = QGridLayout(self) self.bottom_buttons.setLayout(self.bottom_buttons_grid) self.start_btn = QPushButton('Start switcher') self.start_btn.clicked.connect(self.starter) self.apply_btn = QPushButton('Apply changes') self.apply_btn.clicked.connect(self.apply) self.exit_btn = QPushButton('Exit app') self.exit_btn.clicked.connect(self.exit_app) self.bottom_buttons_grid.addWidget(self.start_btn, 0, 0) self.bottom_buttons_grid.addWidget(self.apply_btn, 0, 1) self.bottom_buttons_grid.addWidget(self.exit_btn, 0, 2) grid.addWidget(self.bottom_buttons, 3, 0, 1, 2) self.resize(480, 320) self.show() def get_numeric_loglevel(self, loglevel): numeric_level = getattr(logging, loglevel.upper(), None) if not isinstance(numeric_level, int): numeric_level = 0 return numeric_level def set_logginglevel(self, loglevel): return self.log_textbox.setLevel(self.get_numeric_loglevel(loglevel)) @QtCore.pyqtSlot(QSystemTrayIcon.ActivationReason) def sys_tray(self, reason): """ По-умолчанию, trigger срабатывает всегда. Для обхода этого я повесил таймер на событие. Взято отсюда: https://riverbankcomputing.com/pipermail/pyqt/2010-November/028394.html """ if reason == QSystemTrayIcon.Trigger: self.click_tray_timer.start(QApplication.doubleClickInterval()) elif reason == QSystemTrayIcon.DoubleClick: self.click_tray_timer.stop() if self.isHidden(): self.showNormal() self.systemTrayIcon.setVisible(False) def click_timeout(self): self.starter() def apply(self): self.starter() options['layouts'] = [] options['layouts'].append(self.dict_one.toPlainText()) options['layouts'].append(self.dict_two.toPlainText()) options['log_level'] = LOGGING_LEVELS[self.loglevel_dropmenu.currentIndex()] self.set_logginglevel(options['log_level']) options['switch_combination'] = self.switch_comb_text.text() options['hotkey'] = self.hotkey_comb_text.text() logging.debug('Options from GUI: {}'.format(options)) save_to(options) self.starter() return options def exit_app(self): if self.running: self.starter() self.systemTrayIcon.setVisible(False) self.destroy() def closeEvent(self, event): self.exit_app() def changeEvent(self, event): if event.type() == QtCore.QEvent.WindowStateChange: if self.windowState() & QtCore.Qt.WindowMinimized: event.ignore() self.hide() self.systemTrayIcon.setVisible(True) self.systemTrayIcon.showMessage('', 'Running in the background.') super(MainWindow, self).changeEvent(event)
class NomnsParse(QApplication): """Application Control.""" def __init__(self, *args): 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
def inittray(self): global tray tray=QSystemTrayIcon(self) tray.setIcon(QIcon(os.path.join(sys.path[0],'clock.png'))) tray.setVisible(True) tray.activated.connect(self.activate)
class Application(QApplication): def __init__(self): super(Application, self).__init__([]) self.setQuitOnLastWindowClosed(False) # Create ouath object to handle cached token data self.sp_oauth = SpotifyOAuth(spotify_config.client_id, spotify_config.client_secret, spotify_config.redirect_uri, scope='user-read-currently-playing', cache_path=join(FOLDER_PATH, 'spotifycache')) # Create the menu, connect class functions to the app's buttons self.menu = Menu() self.menu.login_info_action.triggered.connect(self._on_login_button) self.menu.toggle_wallpaper_action.triggered.connect( self._on_toggle_wallpaper_button) self.menu.quit_action.triggered.connect(self._on_quit_button) # Create the tray and make it visible self.tray = QSystemTrayIcon() self.tray.setIcon(QIcon(resource_path(join('assets', 'icon.png')))) self.tray.setVisible(True) self.tray.setContextMenu(self.menu) # Create confirm account frame self.confirm_account_window = ConfirmAccountWindow() self.confirm_account_window.form_widget.button.clicked.connect( self._on_confirm_account_button) # Attempt login with cache self._attempt_login() def _attempt_login(self, code=None): """attempts to get token from cache if a code is not supplied""" if code: # Get access token from Spotify try: token_info = self.sp_oauth.get_access_token(code) except SpotifyOauthError: notify_bad_link() return else: # Get token from cache token_info = self.sp_oauth.get_cached_token() if not token_info: if IS_WINDOWS: text = 'Find the WallSpotify icon in your tray to log into your Spotify account.' else: text = 'Click the WallSpotify icon to log into your Spotify account.' show_notification('Not Logged In', text) return # Login with token self.spotify = login(token_info) if self.spotify: self._update_login_ui() show_notification('Login Success', 'You can now use WallSpotify.') else: show_notification('Login Failed', 'Something went wrong, try logging in again.') def _update_login_ui(self): """changes tray UI to reflect a successful login""" # Call to Spotify for user account info try: current_user_info = self.spotify.me() except Exception as e: print(e) return text = f'{current_user_info["display_name"]} - Logged into Spotify' self.menu.login_info_action.setText(text) self.menu.toggle_wallpaper_action.setEnabled(True) self.menu.login_info_action.setEnabled(False) def _on_confirm_account_button(self): """parse Spotify callback url for response code, then try to login""" # create regex for a valid url regex = compile( r'^(?:http|ftp)s?://' # http:// or https:// r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' # domain... r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip r'(?:/?|[/?]\S+)$', IGNORECASE) # get the contents of the text field text = self.confirm_account_window.form_widget.text_field.text() is_valid = True # check if text is a valid url if match(regex, text): code = self.sp_oauth.parse_response_code(text) if not code: is_valid = False else: is_valid = False self.confirm_account_window.hide() self.confirm_account_window.form_widget.text_field.setText('') if is_valid: # Attempt login using code self._attempt_login(code) else: notify_bad_link() def _on_login_button(self): """tries to open users web browser to prompt Spotify login, also shows window for callback url""" if not self.confirm_account_window.isVisible(): self.confirm_account_window.show() auth_url = self.sp_oauth.get_authorize_url() try: open_webbrowser(auth_url) except Exception as e: # todo: show a prompt to give the user the auth url print(e) def _on_toggle_wallpaper_button(self): """starts and stops wallpaper thread from users input""" # check if the toggle wallpaper option was already selected if self.menu.toggle_wallpaper_action.checked: # stop the current song thread self.menu.toggle_wallpaper_action.checked = False self.wallpaper_thread.stop() else: # start a new thread for changing wallpaper self.menu.toggle_wallpaper_action.checked = True self._start_new_thread() def _on_quit_button(self): """stops thread then quits the application""" try: self.wallpaper_thread.stop() except AttributeError: pass self.quit() def _start_new_thread(self): """creates new thread object and starts it""" self.wallpaper_thread = ChangeWallpaperThread(self.spotify, FOLDER_PATH) self.wallpaper_thread.start()