def main(): basePath = os.path.dirname(os.path.realpath(__file__)) app = QApplication(sys.argv) contextMenu = QMenu() fixAction = QAction( "Run 'create__ap --fix-unmanaged' in the terminal as root to fix possible issues" ) contextMenu.addAction(fixAction) activeIcon = QIcon() activeIcon.addFile(os.path.join(basePath, "wifi.svg")) inactiveIcon = QIcon() inactiveIcon.addFile(os.path.join(basePath, "wifi-off.svg")) trayIcon = QSystemTrayIcon(inactiveIcon) trayIcon.setContextMenu(contextMenu) trayIcon.activated.connect(handleTrayIconClick) def syncIcon(): if serviceIsActive(): trayIcon.setIcon(activeIcon) else: trayIcon.setIcon(inactiveIcon) timer = QTimer() timer.setInterval(1000) timer.timeout.connect(syncIcon) timer.start() trayIcon.show() sys.exit(app.exec_())
class SystemTray(QWidget): """只有托盘""" def __init__(self, config_list): super().__init__() self.tray = QSystemTrayIcon() self.tray.setIcon(QIcon('icons/app.ico')) self.add_menu(config_list) def add_menu(self, config_list): """托盘菜单""" tray_menu = QMenu() # 添加菜单 for config in config_list: params = config.get('params') sys_name = params.get('name') tray_menu.addAction(OpenAction(params, self)) stop_bat = params.get('stop_bat', None) if stop_bat: tray_menu.addAction(StopAction(sys_name, stop_bat, self)) tray_menu.addAction(ExitAction(self)) self.tray.setContextMenu(tray_menu) def display(self): """icon的值: 0-没有图标 1-是提示 2-是警告 3-是错误""" self.tray.show() self.tray.showMessage(u"启动成功", '请通过右键操作')
def setup_app(app): # Stop window closing app.setQuitOnLastWindowClosed(False) # Init QSystemTrayIcon icon = QIcon("icon.png") tray_icon = QSystemTrayIcon(app) tray_icon.setIcon(icon) # Add Menu Actions to App new_action = QAction("New", app) show_action = QAction("Show All", app) hide_action = QAction("Hide All", app) quit_action = QAction("Exit", app) new_action.triggered.connect(new_note) show_action.triggered.connect(show_all) hide_action.triggered.connect(hide_all) quit_action.triggered.connect(quit_app) # Add Tray Menu tray_menu = QMenu() tray_menu.addAction(new_action) tray_menu.addAction(show_action) tray_menu.addAction(hide_action) tray_menu.addAction(quit_action) tray_icon.setContextMenu(tray_menu) tray_icon.activated.connect(systemIcon) tray_icon.show()
class SystemTray(QSystemTrayIcon): def __init__(self, app): super(SystemTray, self).__init__() self.app = app self.systemTray = QSystemTrayIcon( QIcon(f"{sys.argv[0]}/assets/app_icon.png")) self.trayMenu = QMenu() self.systemTray.setContextMenu(self.trayMenu) self.menu_title_setup() self.open_action_setup() self.exit_action_setup() self.systemTray.show() def menu_title_setup(self): self.trayMenu.menuTitle = self.trayMenu.addAction("Saharah Paper") self.trayMenu.menuTitle.setEnabled(False) self.trayMenu.addSeparator() def open_action_setup(self): self.trayMenu.openAction = self.trayMenu.addAction("Settings") self.trayMenu.openAction.triggered.connect(self.handle_open) def handle_open(self): self.app.mainWindow.show() def exit_action_setup(self): self.trayMenu.exitAction = self.trayMenu.addAction("Exit") self.trayMenu.exitAction.triggered.connect(self.app.quit)
class App: def __init__(self): # Create a Qt application self.app = QApplication(sys.argv) self.main = BT('main.ui') self.splash = QSplashScreen(QPixmap('../images/boot.jpg')) self.splash.show() menu = QMenu() showAction = menu.addAction("显示") hideAction = menu.addAction("隐藏") closeAction = menu.addAction("退出") showAction.triggered.connect( lambda event: self.show(showAction, hideAction)) hideAction.triggered.connect( lambda event: self.hide(showAction, hideAction)) closeAction.triggered.connect(self.exit) self.tray = QSystemTrayIcon() self.tray.setIcon(QIcon("../icon/icon.ico")) self.tray.setContextMenu(menu) self.tray.show() self.tray.setToolTip("") def run(self): # Enter Qt application main loop self.app.processEvents() self.main.window.show() self.splash.finish(self.main.window) sys.exit(self.app.exec_()) def hide(self, show_action, hiden_actoin): hiden_actoin.setDisabled(True) show_action.setEnabled(True) self.main.window.hide() return def show(self, show_action, hiden_actoin): hiden_actoin.setEnabled(True) show_action.setDisabled(True) self.main.window.show() return def exit(self): self.main.window.close() return
def start(_exit: bool = False, _show_ui: bool = True) -> None: app = QApplication(sys.argv) logo = QIcon(LOGO) main_window = MainWindow() ui = main_window.ui main_window.setWindowIcon(logo) tray = QSystemTrayIcon(logo, app) tray.activated.connect(main_window.systray_clicked) menu = QMenu() action_exit = QAction("Exit") action_exit.triggered.connect(app.exit) menu.addAction(action_exit) tray.setContextMenu(menu) ui.text.textChanged.connect(partial(queue_text_change, ui)) ui.command.textChanged.connect(partial(update_button_command, ui)) ui.keys.textChanged.connect(partial(update_button_keys, ui)) ui.write.textChanged.connect(partial(update_button_write, ui)) ui.change_brightness.valueChanged.connect( partial(update_change_brightness, ui)) ui.switch_page.valueChanged.connect(partial(update_switch_page, ui)) ui.imageButton.clicked.connect(partial(select_image, main_window)) ui.brightness.valueChanged.connect(partial(set_brightness, ui)) for deck_id, deck in api.open_decks().items(): ui.device_list.addItem(f"{deck['type']} - {deck_id}", userData=deck_id) build_device(ui) ui.device_list.currentIndexChanged.connect(partial(build_device, ui)) ui.pages.currentChanged.connect(partial(change_page, ui)) ui.actionExport.triggered.connect(partial(export_config, main_window)) ui.actionImport.triggered.connect(partial(import_config, main_window)) timer = QTimer() timer.timeout.connect(partial(sync, ui)) timer.start(1000) api.render() tray.show() if _show_ui: main_window.show() if _exit: return else: sys.exit(app.exec_())
class SystrayIcon(QObject): show_window = Signal() exit = Signal() def __init__(self, parent: QWidget): super().__init__(parent) # Bold font bold = QFont() bold.setBold(True) # Systray icon icon: QIcon = QApplication.instance().windowIcon() self._systray_icon = QSystemTrayIcon(icon, self) self._systray_icon.setToolTip("Noteeds") # Show action self._show_action = QAction("&Show", self) self._show_action.setFont(bold) # Exit action self._exit_action = QAction("E&xit", self) # Menu self._menu = QMenu(parent) self._menu.addAction(self._show_action) self._menu.addAction(self._exit_action) self._systray_icon.setContextMenu(self._menu) # Connections self._systray_icon.activated.connect(self._activated) self._show_action.triggered.connect(self.show_window) self._exit_action.triggered.connect(self.exit) def _activated(self, reason: QSystemTrayIcon.ActivationReason): if reason == QSystemTrayIcon.Trigger: self.show_window.emit() def show(self): self._systray_icon.show() def hide(self): self._systray_icon.hide()
class TrayIcon: def __init__(self): self.icons = { True: QIcon('icons/rotate_disabled.png'), False: QIcon('icons/rotate_enabled.png'), } self.tray = QSystemTrayIcon() self.tray.activated.connect(self.action) self.update_status() self.tray.show() def toggle_lock(self): global locked locked = not locked def update_status(self): self.tray.setToolTip( "Auto rotate ({})".format('enabled' if not locked else 'disabled')) self.tray.setIcon(self.icons[locked]) def action(self, signal): self.toggle_lock() self.update_status()
class UIMainWindow(object): """Container class to hold all UI-related creation methods. Must be sublcassed. """ def create_ui(self): """Setup main UI elements, dock widgets, UI-related elements, etc. """ log.debug('Loading UI') # Undo Stack self.undo_stack = QUndoStack(self) self.undo_stack.setUndoLimit(100) # Object navigation history self.obj_history = deque([], config.MAX_OBJ_HISTORY) app = QApplication.instance() base_font = QFont() base_font.fromString(self.prefs['base_font']) app.setFont(base_font) # Object class table widget # classTable = QTableView(self) classTable = classtable.TableView(self) classTable.setObjectName("classTable") classTable.setAlternatingRowColors(True) classTable.setFrameShape(QFrame.StyledPanel) classTable_font = QFont() classTable_font.fromString(self.prefs['class_table_font']) classTable.setFont(classTable_font) fm = classTable.fontMetrics() classTable.setWordWrap(True) classTable.setEditTriggers(QAbstractItemView.EditKeyPressed | QAbstractItemView.DoubleClicked | QAbstractItemView.AnyKeyPressed | QAbstractItemView.SelectedClicked) # classTable.horizontalHeader().setMovable(True) # classTable.verticalHeader().setMovable(False) classTable.horizontalHeader().setSectionResizeMode(QHeaderView.Interactive) classTable.verticalHeader().setSectionResizeMode(QHeaderView.Interactive) classTable.horizontalHeader().setDefaultSectionSize(self.prefs['default_column_width']) classTable.verticalHeader().setDefaultSectionSize(fm.height() + 0) classTable.setSelectionMode(QAbstractItemView.ExtendedSelection) classTable.setContextMenuPolicy(Qt.CustomContextMenu) classTable.customContextMenuRequested.connect(self.custom_table_context_menu) # Create table model and proxy layers for transposing and filtering self.classTableModel = classtable.IDFObjectTableModel(classTable) self.transposeableModel = classtable.TransposeProxyModel(self.classTableModel) self.transposeableModel.setSourceModel(self.classTableModel) self.sortableModel = classtable.SortFilterProxyModel(self.transposeableModel) self.sortableModel.setSourceModel(self.transposeableModel) # Assign model to table (enable sorting FIRST) # table.setSortingEnabled(True) # Disable for now, CRUD actions won't work! classTable.setModel(self.sortableModel) # Connect some signals selection_model = classTable.selectionModel() selection_model.selectionChanged.connect(self.table_selection_changed) scroll_bar = classTable.verticalScrollBar() scroll_bar.valueChanged.connect(self.scroll_changed) # These are currently broken # classTable.horizontalHeader().sectionMoved.connect(self.moveObject) # classTable.verticalHeader().sectionMoved.connect(self.moveObject) # Object class tree widget classTreeDockWidget = QDockWidget("Object Classes and Counts", self) classTreeDockWidget.setObjectName("classTreeDockWidget") classTreeDockWidget.setAllowedAreas(Qt.AllDockWidgetAreas) classTree = QTreeView(classTreeDockWidget) classTree.setUniformRowHeights(True) classTree.setAllColumnsShowFocus(True) classTree.setRootIsDecorated(False) classTree.setExpandsOnDoubleClick(True) classTree.setIndentation(15) classTree.setAnimated(True) classTree_font = QFont() classTree_font.fromString(self.prefs['class_tree_font']) classTree.setFont(classTree_font) classTree.setAlternatingRowColors(True) classTree.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel) palette = classTree.palette() palette.setColor(QPalette.Highlight, Qt.darkCyan) classTree.setPalette(palette) class_tree_window = QWidget(classTreeDockWidget) class_tree_dock_layout_v = QVBoxLayout() class_tree_dock_layout_h = QHBoxLayout() class_tree_dock_layout_v.setContentsMargins(0, 8, 0, 0) class_tree_dock_layout_h.setContentsMargins(0, 0, 0, 0) class_tree_filter_edit = QLineEdit(classTreeDockWidget) class_tree_filter_edit.setPlaceholderText("Filter Classes") class_tree_filter_edit.textChanged.connect(self.treeFilterRegExpChanged) class_tree_filter_cancel = QPushButton("Clear", classTreeDockWidget) class_tree_filter_cancel.setMaximumWidth(45) class_tree_filter_cancel.clicked.connect(self.clearTreeFilterClicked) class_tree_dock_layout_h.addWidget(class_tree_filter_edit) class_tree_dock_layout_h.addWidget(class_tree_filter_cancel) class_tree_dock_layout_v.addLayout(class_tree_dock_layout_h) class_tree_dock_layout_v.addWidget(classTree) class_tree_window.setLayout(class_tree_dock_layout_v) classTreeDockWidget.setWidget(class_tree_window) classTreeDockWidget.setContentsMargins(0,0,0,0) # Comments widget commentDockWidget = QDockWidget("Comments", self) commentDockWidget.setObjectName("commentDockWidget") commentDockWidget.setAllowedAreas(Qt.AllDockWidgetAreas) commentView = UndoRedoTextEdit(commentDockWidget, self) commentView.setLineWrapMode(QTextEdit.FixedColumnWidth) commentView.setLineWrapColumnOrWidth(499) commentView.setFrameShape(QFrame.StyledPanel) commentView_font = QFont() commentView_font.fromString(self.prefs['comments_font']) commentView.setFont(commentView_font) commentDockWidget.setWidget(commentView) # Info and help widget infoDockWidget = QDockWidget("Info", self) infoDockWidget.setObjectName("infoDockWidget") infoDockWidget.setAllowedAreas(Qt.AllDockWidgetAreas) infoView = QTextEdit(infoDockWidget) infoView.setFrameShape(QFrame.StyledPanel) infoView.setReadOnly(True) infoDockWidget.setWidget(infoView) # Node list and jump menu widget refDockWidget = QDockWidget("Field References", self) refDockWidget.setObjectName("refDockWidget") refDockWidget.setAllowedAreas(Qt.AllDockWidgetAreas) ref_model = reftree.ReferenceTreeModel(None, refDockWidget) refView = QTreeView(refDockWidget) refView.setModel(ref_model) refView.setUniformRowHeights(True) refView.setRootIsDecorated(False) refView.setIndentation(15) refView.setColumnWidth(0, 160) refView.setFrameShape(QFrame.StyledPanel) refDockWidget.setWidget(refView) refView.doubleClicked.connect(self.ref_tree_double_clicked) # Logging and debugging widget logDockWidget = QDockWidget("Log Viewer", self) logDockWidget.setObjectName("logDockWidget") logDockWidget.setAllowedAreas(Qt.AllDockWidgetAreas) logView = QPlainTextEdit(logDockWidget) logView.setLineWrapMode(QPlainTextEdit.NoWrap) logView.setReadOnly(True) logView_font = QFont() logView_font.fromString(self.prefs['base_font']) logView.setFont(logView_font) logView.ensureCursorVisible() logDockWidget.setWidget(logView) # Undo view widget undoDockWidget = QDockWidget("Undo History", self) undoDockWidget.setObjectName("undoDockWidget") undoDockWidget.setAllowedAreas(Qt.AllDockWidgetAreas) undoView = QUndoView(self.undo_stack) undoDockWidget.setWidget(undoView) # Define corner docking behaviour self.setDockNestingEnabled(True) self.setCorner(Qt.TopLeftCorner, Qt.LeftDockWidgetArea) self.setCorner(Qt.TopRightCorner, Qt.RightDockWidgetArea) self.setCorner(Qt.BottomLeftCorner, Qt.LeftDockWidgetArea) self.setCorner(Qt.BottomRightCorner, Qt.RightDockWidgetArea) # Assign main widget and dock widgets to QMainWindow self.setCentralWidget(classTable) self.addDockWidget(Qt.LeftDockWidgetArea, classTreeDockWidget) self.addDockWidget(Qt.RightDockWidgetArea, commentDockWidget) self.addDockWidget(Qt.RightDockWidgetArea, infoDockWidget) self.addDockWidget(Qt.RightDockWidgetArea, refDockWidget) self.addDockWidget(Qt.RightDockWidgetArea, logDockWidget) self.addDockWidget(Qt.RightDockWidgetArea, undoDockWidget) # Store widgets for access by other objects self.classTable = classTable self.commentView = commentView self.infoView = infoView self.classTree = classTree self.logView = logView self.undoView = undoView self.refView = refView self.filterTreeBox = class_tree_filter_edit # Store docks for access by other objects self.commentDockWidget = commentDockWidget self.infoDockWidget = infoDockWidget self.classTreeDockWidget = classTreeDockWidget self.logDockWidget = logDockWidget self.undoDockWidget = undoDockWidget self.refDockWidget = refDockWidget # Perform other UI-related initialization tasks self.center() self.setUnifiedTitleAndToolBarOnMac(True) self.setWindowIcon(QIcon(':/images/logo.png')) # Status bar setup self.statusBar().showMessage('Status: Ready') self.unitsLabel = QLabel() self.unitsLabel.setAlignment(Qt.AlignCenter) self.unitsLabel.setMinimumSize(self.unitsLabel.sizeHint()) self.unitsLabel.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken) self.statusBar().addPermanentWidget(self.unitsLabel) self.pathLabel = QLabel() self.pathLabel.setAlignment(Qt.AlignCenter) self.pathLabel.setMinimumSize(self.pathLabel.sizeHint()) self.pathLabel.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken) self.statusBar().addPermanentWidget(self.pathLabel) self.versionLabel = QLabel() self.versionLabel.setAlignment(Qt.AlignCenter) self.versionLabel.setMinimumSize(self.versionLabel.sizeHint()) self.versionLabel.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken) self.statusBar().addPermanentWidget(self.versionLabel) self.progressBarIDF = QProgressBar() self.progressBarIDF.setAlignment(Qt.AlignCenter) self.progressBarIDF.setMaximumWidth(200) self.statusBar().addPermanentWidget(self.progressBarIDF) self.clipboard = QApplication.instance().clipboard() self.obj_clipboard = [] self.setStyleSheet(""" QToolTip { background-color: gray; color: white; border: black solid 1px } # QMenu { # background-color: rgbf(0.949020, 0.945098, 0.941176); # color: rgb(255,255,255); # } # QMenu::item::selected { # background-color: rgbf(0.949020, 0.945098, 0.941176); # } """) def create_tray_menu(self): """Creates an icon and menu for the system tray """ # Menu for the system tray self.trayIconMenu = QMenu(self) self.trayIconMenu.addAction(self.minimizeAction) self.trayIconMenu.addAction(self.maximizeAction) self.trayIconMenu.addAction(self.restoreAction) self.trayIconMenu.addSeparator() self.trayIconMenu.addAction(self.exitAct) # System tray itself self.trayIcon = QSystemTrayIcon(self) self.trayIcon.setContextMenu(self.trayIconMenu) self.trayIcon.setIcon(QIcon(':/images/logo.png')) self.trayIcon.setToolTip('IDF+') self.trayIcon.show() def create_actions(self): """Creates appropriate actions for use in menus and toolbars. """ self.newAct = QAction(QIcon(':/images/new1.png'), "&New", self, shortcut=QKeySequence.New, statusTip="Create a new file", iconVisibleInMenu=True, triggered=self.new_file) self.openAct = QAction(QIcon(':/images/open.png'), "&Open...", self, shortcut=QKeySequence.Open, statusTip="Open an existing file", iconVisibleInMenu=True, triggered=self.open_file) self.saveAct = QAction(QIcon(':/images/save.png'), "&Save", self, shortcut=QKeySequence.Save, statusTip="Save the document to disk", iconVisibleInMenu=True, triggered=self.save) self.saveFormatAct = QAction(QIcon(':/images/save.png'), "&Format && Save", self, shortcut=QKeySequence('Ctrl+Shift+F'), statusTip="Format File and Save to disk", iconVisibleInMenu=True, triggered=self.format_save) self.saveAsAct = QAction(QIcon(':/images/saveas.png'), "Save &As...", self, shortcut=QKeySequence.SaveAs, statusTip="Save the document under a new name", iconVisibleInMenu=True, triggered=self.save_as) self.exitAct = QAction(QIcon(':/images/quit.png'), "E&xit", self, shortcut=QKeySequence('Ctrl+Q'), iconVisibleInMenu=True, statusTip="Exit the application", triggered=self.closeAllWindows) self.cutObjAct = QAction(QIcon(':/images/cut.png'), "Cu&t Object", self, shortcut=QKeySequence.Cut, statusTip="Cut current selection's contents to clipboard", iconVisibleInMenu=True, triggered=self.cutObject, iconText='Cut Obj') self.copyAct = QAction(QIcon(':/images/copy.png'), "&Copy Selected Values", self, statusTip="Copy current selection's contents to clipboard", iconVisibleInMenu=True, triggered=self.copySelected) self.pasteAct = QAction(QIcon(':/images/paste.png'), "&Paste Selected Values", self, statusTip="Paste clipboard into current selection", iconVisibleInMenu=True, triggered=self.pasteSelected) self.pasteExtAct = QAction(QIcon(':/images/paste.png'), "&Paste from External", self, shortcut=QKeySequence('Ctrl+Shift+v'), statusTip="Paste from external program", iconVisibleInMenu=True, triggered=self.paste_from_external) self.transposeAct = QAction("Transpose", self, shortcut=QKeySequence('Ctrl+t'), statusTip="Transpose rows and columns in object display", triggered=self.transpose_table) self.newObjAct = QAction(QIcon(':/images/new2.png'), "New Object", self, shortcut=QKeySequence('Ctrl+Shift+n'), statusTip="Create new object in current class", iconVisibleInMenu=True, triggered=self.newObject, iconText='New Obj') self.copyObjAct = QAction(QIcon(':/images/copy.png'), "Copy Object", self, shortcut=QKeySequence.Copy, statusTip="Copy the current Object(s)", iconVisibleInMenu=True, triggered=self.copyObject, iconText='Copy Obj') self.pasteObjAct = QAction(QIcon(':/images/paste.png'), "Paste Object", self, shortcut=QKeySequence.Paste, statusTip="Paste the currently copies Object(s)", iconVisibleInMenu=True, triggered=self.pasteObject, iconText='Paste Obj') self.dupObjAct = QAction(QIcon(':/images/copy.png'), "Duplicate Object", self, shortcut=QKeySequence('Shift+Ctrl+d'), statusTip="Duplicate the current Object(s)", iconVisibleInMenu=True, triggered=self.duplicateObject, iconText='Dup Obj') self.delObjAct = QAction(QIcon(':/images/delete.png'), "Delete Object", self, shortcut=QKeySequence.Delete, statusTip="Delete the current Object(s)", iconVisibleInMenu=True, triggered=self.deleteObject, iconText='Del Obj') self.undoAct = QAction(QIcon(':/images/undo.png'), "&Undo", self, shortcut=QKeySequence.Undo, statusTip="Undo previous action", iconVisibleInMenu=True, triggered=self.undo_stack.undo) self.redoAct = QAction(QIcon(':/images/redo.png'), "&Redo", self, shortcut=QKeySequence.Redo, statusTip="Redo previous action", iconVisibleInMenu=True, triggered=self.undo_stack.redo) self.groupAct = QAction("Hide Groups in Class Tree", self, shortcut=QKeySequence('Ctrl+g'), triggered=self.toggle_groups, checkable=True) # self.navForwardAct = QAction("Forward", self, # shortcut=QKeySequence('Ctrl+Plus'), # statusTip="Go forward to the next object", # triggered=self.navForward) # # self.navBackAct = QAction("Back", self, # shortcut=QKeySequence('Ctrl+Minus'), # statusTip="Go back to the previous object", # triggered=self.navBack) self.showInFolderAct = QAction(QIcon(':/images/new.png'), "&Show in folder", self, shortcut=QKeySequence('Ctrl+Shift+t'), statusTip="Open location of current file", iconVisibleInMenu=True) self.showInFolderAct.triggered.connect(lambda: self.show_in_folder()) self.epDocGettingStartedAction = QAction("EnergyPlus Getting Started", self, triggered=self.energy_plus_docs) self.epDocIORefAction = QAction("EnergyPlus I/O Reference", self, triggered=self.energy_plus_docs) self.epDocOutputDetailsAction = QAction("EnergyPlus Output Details and Examples", self, triggered=self.energy_plus_docs) self.epDocEngineeringRefAction = QAction("EnergyPlus Engineering Reference", self, triggered=self.energy_plus_docs) self.epDocAuxiliaryProgsAction = QAction("EnergyPlus Auxiliary Programs", self, triggered=self.energy_plus_docs) self.epDocEMSGuideAction = QAction("EnergyPlus EMS Application Guide", self, triggered=self.energy_plus_docs) self.epDocComplianceAction = QAction("Using EnergyPlus for Compliance", self, triggered=self.energy_plus_docs) self.epDocInterfaceAction = QAction("External Interface Application Guide", self, triggered=self.energy_plus_docs) self.epDocTipsTricksAction = QAction("Tips and Tricks Using EnergyPlus", self, triggered=self.energy_plus_docs) self.epDocPlantGuideAction = QAction("EnergyPlus Plant Application Guide", self, triggered=self.energy_plus_docs) self.epDocAcknowledgmentsAction = QAction("EnergyPlus Acknowledgments", self, triggered=self.energy_plus_docs) self.openInEditorAct = QAction(QIcon(':/images/new.png'), "&Open in text editor", self, shortcut=QKeySequence('Ctrl+e'), statusTip="Open current file in default editor", iconVisibleInMenu=True, triggered=self.open_in_text_editor) self.helpAct = QAction("&EnergyPlus Help (Online)", self, statusTip="Show the EnergyPlus' help", triggered=self.energyplus_help) self.aboutAct = QAction("&About IDF+", self, statusTip="Show the application's About box", triggered=self.about) self.clearRecentAct = QAction("Clear Recent", self, statusTip="Clear recent files", triggered=self.clear_recent) self.minimizeAction = QAction("Mi&nimize", self, triggered=self.hide) self.maximizeAction = QAction("Ma&ximize", self, triggered=self.showMaximized) self.restoreAction = QAction("&Restore", self, triggered=self.showNormal) self.showPrefsAction = QAction("&Preferences", self, triggered=self.show_prefs_dialog) self.showSearchAction = QAction("&Search && Replace", self, shortcut=QKeySequence('Ctrl+f'), triggered=self.show_search_dialog) self.findThisAct = QAction("Find This", self, triggered=self.find_this) self.jumpFilterGeometry = QAction("Include Geometry", self, triggered=self.jump_to_filter_geometry, checkable=True) self.setIPUnitsAction = QAction("&IP Units", self, triggered=self.toggle_units, checkable=True) self.setSIUnitsAction = QAction("&SI Units", self, triggered=self.toggle_units, checkable=True) self.classWithObjsAction = QAction("Show Only Classes With Objects", self, shortcut=QKeySequence('Ctrl+l'), statusTip="Show Only Classes With Objects", triggered=self.toggle_full_tree, checkable=True) self.fillRightAction = QAction("Fill right", self, shortcut=QKeySequence('Ctrl+d'), statusTip="Fill right", triggered=self.fill_right) self.logDockWidgetAct = self.logDockWidget.toggleViewAction() self.transposeAct.setEnabled(False) self.setSIUnitsAction.setChecked(True) self.undoAct.setEnabled(False) self.redoAct.setEnabled(False) self.saveAct.setEnabled(False) self.undo_stack.canUndoChanged.connect(self.toggle_can_undo) self.undo_stack.canRedoChanged.connect(self.toggle_can_redo) self.logDockWidgetAct.toggled.connect(self.start_log_watcher) def toggle_can_undo(self): if self.undo_stack.canUndo(): new_state = True else: new_state = False self.undoAct.setEnabled(new_state) self.set_dirty(new_state) def toggle_can_redo(self): if self.undo_stack.canRedo(): new_state = True else: new_state = False self.redoAct.setEnabled(new_state) def create_menus(self): """Create all required items for menus. """ # File Menu self.fileMenu = self.menuBar().addMenu("&File") self.fileMenuActions = (self.newAct, self.openAct, self.saveAct, self.saveAsAct, self.saveFormatAct, None, self.exitAct) self.update_file_menu() self.fileMenu.aboutToShow.connect(self.update_file_menu) # Edit Menu self.editMenu = self.menuBar().addMenu("&Edit") self.editMenu.addAction(self.undoAct) self.editMenu.addAction(self.redoAct) self.editMenu.addSeparator().setText('Objects') self.editMenu.addAction(self.newObjAct) self.editMenu.addAction(self.dupObjAct) self.editMenu.addSeparator() self.editMenu.addAction(self.cutObjAct) self.editMenu.addAction(self.copyObjAct) self.editMenu.addAction(self.pasteObjAct) self.editMenu.addAction(self.pasteExtAct) self.editMenu.addSeparator() self.editMenu.addAction(self.delObjAct) self.editMenu.addSeparator().setText('Values') self.editMenu.addAction(self.copyAct) self.editMenu.addAction(self.pasteAct) self.editMenu.addSeparator() self.editMenu.addAction(self.fillRightAction) self.editMenu.addSeparator() self.editMenu.addAction(self.showSearchAction) # Tools Menu self.toolsMenu = self.menuBar().addMenu("&Tools") self.toolsMenu.addAction(self.showInFolderAct) self.toolsMenu.addAction(self.openInEditorAct) self.toolsMenu.addSeparator() self.toolsMenu.addAction(self.showPrefsAction) # View Menu self.viewMenu = self.menuBar().addMenu("&View") action_group = QActionGroup(self) self.viewMenu.addAction(action_group.addAction(self.setSIUnitsAction)) self.viewMenu.addAction(action_group.addAction(self.setIPUnitsAction)) self.viewMenu.addSeparator().setText('Dockable Widgets') self.viewMenu.addAction(self.classTreeDockWidget.toggleViewAction()) self.viewMenu.addAction(self.infoView.parent().toggleViewAction()) self.viewMenu.addAction(self.commentView.parent().toggleViewAction()) self.viewMenu.addAction(self.logDockWidgetAct) self.viewMenu.addAction(self.undoView.parent().toggleViewAction()) self.viewMenu.addSeparator().setText('Toolbars') self.viewMenu.addAction(self.fileToolBar.toggleViewAction()) self.viewMenu.addAction(self.editToolBar.toggleViewAction()) # self.viewMenu.addAction(self.navToolBar.toggleViewAction()) self.viewMenu.addAction(self.filterToolBar.toggleViewAction()) self.viewMenu.addSeparator() self.viewMenu.addAction(self.classWithObjsAction) self.viewMenu.addAction(self.groupAct) self.viewMenu.addSeparator() self.viewMenu.addAction(self.transposeAct) # Jump Menu self.jumpToMenu = self.menuBar().addMenu("&Jump") self.update_jump_menu() self.jumpToMenu.aboutToShow.connect(self.update_jump_menu) self.jumpFilterGeometry.setEnabled(False) # Help Menu self.helpMenu = self.menuBar().addMenu("&Help") self.helpMenu.addAction(self.helpAct) self.helpMenu.addAction(self.aboutAct) self.helpMenu.addSeparator() self.helpMenu.addAction(self.epDocGettingStartedAction) self.helpMenu.addAction(self.epDocIORefAction) self.helpMenu.addAction(self.epDocOutputDetailsAction) self.helpMenu.addAction(self.epDocEngineeringRefAction) self.helpMenu.addAction(self.epDocAuxiliaryProgsAction) self.helpMenu.addAction(self.epDocEMSGuideAction) self.helpMenu.addAction(self.epDocComplianceAction) self.helpMenu.addAction(self.epDocInterfaceAction) self.helpMenu.addAction(self.epDocTipsTricksAction) self.helpMenu.addAction(self.epDocPlantGuideAction) self.helpMenu.addAction(self.epDocAcknowledgmentsAction) def create_tool_bars(self): """Creates the necessary toolbars. """ # File Toolbar self.fileToolBar = self.addToolBar("File Toolbar") self.fileToolBar.setObjectName('fileToolbar') self.fileToolBar.addAction(self.newAct) self.fileToolBar.addAction(self.openAct) self.fileToolBar.addAction(self.saveAct) self.fileToolBar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) # Edit Toolbar self.editToolBar = self.addToolBar("Edit Toolbar") self.editToolBar.setObjectName('editToolbar') self.editToolBar.addAction(self.undoAct) self.editToolBar.addAction(self.redoAct) self.editToolBar.addAction(self.newObjAct) self.editToolBar.addAction(self.dupObjAct) self.editToolBar.addAction(self.delObjAct) self.editToolBar.addAction(self.cutObjAct) self.editToolBar.addAction(self.copyObjAct) self.editToolBar.addAction(self.pasteObjAct) self.editToolBar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) # Object history navigation toolbar # self.navToolBar = self.addToolBar("Navigation Toolbar") # self.navToolBar.setObjectName('viewToolBar') # self.navToolBar.addAction(self.navForwardAct) # self.navToolBar.addAction(self.navBackAct) # self.navToolBar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) # Object filter toolbar self.filterToolBar = self.addToolBar("Filter Toolbar") self.filterToolBar.setObjectName('filterToolBar') self.filterBox = QLineEdit() self.filterBox.setPlaceholderText("Filter Objects") self.filterBox.setMaximumWidth(160) self.filterBox.setFixedWidth(160) # filterLabel = QLabel("Filter Obj:", self) # filterLabel.setBuddy(self.filterBox) # self.filterToolBar.addWidget(filterLabel) self.filterBox.textChanged.connect(self.tableFilterRegExpChanged) self.filterBox.textChanged.connect(self.treeFilterRegExpChanged) clearFilterButton = QPushButton('Clear') clearFilterButton.setMaximumWidth(45) clearFilterButton.clicked.connect(self.clearFilterClicked) self.filterToolBar.addWidget(self.filterBox) self.filterToolBar.addWidget(clearFilterButton) self.caseSensitivity = QCheckBox('Case Sensitive') self.caseSensitivity.stateChanged.connect(self.caseSensitivityChanged) self.filterToolBar.addWidget(self.caseSensitivity) self.filterToolBar.addSeparator() self.filterToolBar.addAction(self.transposeAct) def create_shortcuts(self): """Creates keyboard shortcuts. """ # QShortcut(QKeySequence('Ctrl+l'), self).activated.connect(self.toggle_full_tree) # QShortcut(QKeySequence('Ctrl+d'), self).activated.connect(self.fill_right) # QShortcut(QKeySequence('Ctrl+d'), self).activated.connect(self.fill_right) # def createAction(self, text, slot=None, shortcut=None, icon=None, # tip=None, checkable=False, signal="triggered()"): # action = QAction(text, self) # if icon is not None: # action.setIcon(QIcon(":/%s.png" % icon)) # if shortcut is not None: # action.setShortcut(shortcut) # if tip is not None: # action.setToolTip(tip) # action.setStatusTip(tip) # if slot is not None: # self.connect(action, QtCore.SIGNAL(signal), slot) # if checkable: # action.setCheckable(True) # return action # def custom_table_context_menu(self, position): # Create a menu and populate it with actions menu = QMenu(self) menu.addAction(self.undoAct) menu.addAction(self.redoAct) menu.addSeparator() menu.addAction(self.copyObjAct) menu.addAction(self.dupObjAct) menu.addAction(self.delObjAct) menu.addAction(self.newObjAct) menu.addAction(self.cutObjAct) menu.addSeparator() menu.addMenu(self.jumpToMenu) menu.addSeparator() menu.addAction(self.findThisAct) menu.popup(self.classTable.viewport().mapToGlobal(position)) self.mouse_position = position def reset_progress_bar(self): self.progressBarIDF.hide() def center(self): """Called to center the window on the screen on startup. """ screen = QDesktopWidget().screenGeometry() size = self.geometry() self.move((screen.width() - size.width()) / 2, (screen.height() - size.height()) / 2) def show_prefs_dialog(self): """Handles showing the settings dialog and setting its values. """ dlg = PrefsDialog(self, self.prefs) if dlg.exec_(): # Refresh the table view to take into account any new prefs self.load_table_view(self.current_obj_class) # Clear the idd cache if requested if self.prefs.get('clear_idd_cache', False) == True: self.clear_idd_cache() # Update preferences if various flags apply if not self.idf: return options_dict = dict() if self.prefs.get('apply_default_save_behaviour', False) == True: options_dict.update({'sort_order': self.prefs.get('sort_order'), 'special_formatting': self.prefs.get('special_formatting')}) # if self.prefs.get('apply_default_units_behaviour', False) == True: # options_dict.update({'save_units': self.prefs.get('save_units')}) # if self.prefs.get('apply_default_hidden_class_behaviour', False) == True: # options_dict.update({'save_hidden_classes': self.prefs.get('save_hidden_classes')}) if options_dict: self.idf.set_options(options_dict) self.set_dirty(True) def show_search_dialog(self): """Opens the search dialog. """ SearchReplaceDialog(self).show() def find_this(self): """Searches for fields with similar content. """ index = self.classTable.indexAt(self.mouse_position) text = self.classTable.model().data(index, Qt.EditRole) if text: SearchReplaceDialog(self, initial_query=text).show()
def start(_exit: bool = False) -> None: app = QApplication(sys.argv) first_start = False if not os.path.isfile(STATE_FILE): first_start = True logo = QIcon(LOGO) main_window = MainWindow() ui = main_window.ui main_window.setWindowIcon(logo) tray = QSystemTrayIcon(logo, app) tray.activated.connect(main_window.systray_clicked) menu = QMenu() action_show = QAction("Show") action_show.triggered.connect(main_window.show) action_exit = QAction("Exit") action_exit.triggered.connect(app.exit) menu.addAction(action_show) menu.addAction(action_exit) tray.setContextMenu(menu) ui.text.textChanged.connect(partial(queue_text_change, ui)) ui.command.textChanged.connect(partial(update_button_command, ui)) ui.keys.textChanged.connect(partial(update_button_keys, ui)) ui.write.textChanged.connect(partial(update_button_write, ui)) ui.change_brightness.valueChanged.connect( partial(update_change_brightness, ui)) ui.switch_page.valueChanged.connect(partial(update_switch_page, ui)) ui.imageButton.clicked.connect(partial(select_image, main_window)) ui.brightness.valueChanged.connect(partial(set_brightness, ui)) ui.information.currentIndexChanged.connect(partial(set_information, ui)) items = api.open_decks().items() print("wait for device(s)") while len(items) == 0: time.sleep(3) items = api.open_decks().items() print("found " + str(len(items)) + ": " + ",".join(str(i) for i in list(items))) for deck_id, deck in items: ui.device_list.addItem(f"{deck['type']} - {deck_id}", userData=deck_id) build_device(ui) ui.device_list.currentIndexChanged.connect(partial(build_device, ui)) ui.pages.currentChanged.connect(partial(change_page, ui)) ui.actionExport.triggered.connect(partial(export_config, main_window)) ui.actionImport.triggered.connect(partial(import_config, main_window)) ui.actionExit.triggered.connect(app.exit) timer = QTimer() timer.timeout.connect(partial(sync, ui)) timer.start(1000) api.render() tray.show() if first_start: main_window.show() if _exit: return else: sys.exit(app.exec_())
class UI: """ WSL2 端口自动转发 """ def __init__(self, qt_application=None): self.qt_application = qt_application # 实例化配置管理类 self.settings_manage = SettingsManage() self.__setting = self.settings_manage.get() # 实例化windows命令处理类 self.wsl2 = WinCmd() # 初始化启动脚本 if not isfile(self.wsl2.WSL_VBS_PATH): copyfile(self.wsl2.WSL_VBS_PATH_TEMP, self.wsl2.WSL_VBS_PATH) if not isfile(self.wsl2.WSL_BAT_PATH): self.settings_manage.save_file_content( self.wsl2.WSL_BAT_PATH, self.__setting.get('wsl_bat_content', '')) # 加载UI文件 self.ui = QUiLoader().load(ResourcePath.resource_path('lib/wsl2.ui')) # 设置界面图标 app_icon = QIcon(ResourcePath.resource_path("lib/logo.ico")) self.ui.setWindowIcon(app_icon) # 设置选中状态 self.ui.auto_start_wsl.setChecked( self.__setting.get('auto_start_wsl', False)) self.ui.fire_wall_open.setChecked( self.__setting.get('fire_wall_open', False)) self.ui.fire_wall_close.setChecked( self.__setting.get('fire_wall_close', False)) # 设置文本框的值 self.ui.port_text.appendPlainText('\n'.join( self.__setting.get('ports', []))) self.ui.bat_text.appendPlainText(self.wsl2.get_bat_script()) # 按钮监听 self.ui.get_wsl2_ip.clicked.connect(self.__get_wsl2_ip) self.ui.port_add.clicked.connect(self.__port_add) self.ui.port_del.clicked.connect(self.__port_del) self.ui.port_info.clicked.connect(self.__port_info) self.ui.wsl_l_v.clicked.connect(self.__wsl_l_v) self.ui.port_reset.clicked.connect(self.__port_reset) self.ui.end_wsl.clicked.connect(self.__end_wsl) self.ui.start_wsl.clicked.connect(self.__start_wsl) self.ui.start_wsl_all.clicked.connect(self.start_wsl_all) self.ui.save_settings.clicked.connect(self.__save_settings) self.ui.save_settings_ports.clicked.connect(self.__save_settings) # 设置系统托盘图标的菜单 tp_icon = QIcon(ResourcePath.resource_path("lib/logo.ico")) self.tp = QSystemTrayIcon(self.ui) self.tp.setIcon(tp_icon) self.ui_hide = QAction(icon=tp_icon, text='隐藏(Hide)', triggered=self.ui.hide) self.ui_show = QAction(icon=tp_icon, text='显示(Show)', triggered=self.ui.show) self.ui_exit = QAction(icon=tp_icon, text='退出(Exit)', triggered=self.__quit_app) self.tp_menu = QMenu() self.tp_menu.addAction(self.ui_hide) self.tp_menu.addAction(self.ui_show) self.tp_menu.addAction(self.ui_exit) self.tp.setContextMenu(self.tp_menu) self.tp.activated.connect(self.__tp_connect_action) self.tp.show() self.tp.showMessage('WSL2AutoPortForward', 'WSL2端口自动转发工具已启动', QSystemTrayIcon.MessageIcon.Information) def __tp_connect_action(self, activation_reason): """ 监听托盘图标点击 :param activation_reason: 点击类型 :return: """ if activation_reason == QSystemTrayIcon.ActivationReason.Trigger: # 左单击 if self.ui.isHidden(): self.ui.show() else: self.ui.hide() elif activation_reason == QSystemTrayIcon.ActivationReason.Context: # 右单击 self.tp_menu.show() elif activation_reason == QSystemTrayIcon.ActivationReason.DoubleClick: # 双击 self.ui.show() def __quit_app(self): """ 退出APP :return: """ re = QMessageBox.question(self.ui, "提示", "退出系统", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if re == QMessageBox.Yes: # 关闭窗体程序 self.qt_application.quit() # 在应用程序全部关闭后,TrayIcon其实还不会自动消失, # 直到你的鼠标移动到上面去后,才会消失, # 这是个问题,(如同你terminate一些带TrayIcon的应用程序时出现的状况), # 这种问题的解决我是通过在程序退出前将其setVisible(False)来完成的。 self.tp.setVisible(False) def __wsl_l_v(self): """ 获取wsl信息 :return: """ wsl_l_v_txt = ' ' + self.wsl2.wsl_l_v(exec_run=True).replace( '\x00', '').strip() if not wsl_l_v_txt: # 未查询到wsl信息提示 wsl_l_v_txt = '未查询到wsl信息!' QMessageBox.information(self.ui, '系统提示', wsl_l_v_txt) self.ui.wsl_l_v_text.setPlainText(wsl_l_v_txt) def __get_wsl2_ip(self): wsl2_ip_info = self.wsl2.get_wsl2_ip() if not wsl2_ip_info: # 未查询到端口转发信息提示 QMessageBox.information(self.ui, '系统提示', '未查询到IP信息!') else: wsl2_ip_info = 'WSL2当前IP为:' + wsl2_ip_info self.ui.wsl_l_v_text.setPlainText(wsl2_ip_info) def __port_add(self): wsl2_ip_info = self.wsl2.get_wsl2_ip() if not wsl2_ip_info: # 未查询到端口转发信息提示 QMessageBox.information(self.ui, '系统提示', '未查询到IP信息!') else: self.ui.result_text.clear() ports = self.ui.port_text.toPlainText() port_str = '' for port in ports.splitlines(): if not port.strip(): continue self.__port_add_one(port, wsl2_ip_info) port_str += (',' + port if port_str else port) self.__fire_wall_rule_add(port_str) self.ui.result_text.appendPlainText('Succeed!') def __port_del(self, del_port=True, del_fire=True): self.ui.result_text.clear() ports = self.ui.port_text.toPlainText() if del_port: for port in ports.splitlines(): if not port.strip(): continue self.__port_del_one(port) if del_fire: self.__fire_wall_rule_del() self.ui.result_text.appendPlainText('Succeed!') def __port_reset(self): port_info = self.wsl2.port_info() self.ui.result_text.clear() for port in port_info: self.__port_del_one(port['port']) self.__fire_wall_rule_del() self.ui.result_text.appendPlainText('Succeed!') def __port_info(self): """ 获取端口转发信息 :return: """ info_str = self.wsl2.port_info(True).strip() if not info_str: # 未查询到端口转发信息提示 info_str = '未查询到端口转发信息!' QMessageBox.information(self.ui, '系统提示', info_str) self.ui.result_text.setPlainText(info_str) def __wsl2_auto_port_forward(self): """ 一键自动转发 @return: """ self.__port_del(del_port=False, del_fire=True) self.__port_add() def __end_wsl(self): """ 停止wsl :return: """ self.start_qt_process(self.wsl2.end_wsl(exec_run=False)) info_str = 'wsl 已全部停止' QMessageBox.information(self.ui, '系统提示', info_str) def __start_wsl(self): """ 启动wsl :return: """ self.start_qt_process(self.wsl2.start_wsl(exec_run=False)) def start_wsl_all(self): """ 启动wsl并转发端口 :return: """ self.__start_wsl() self.__wsl2_auto_port_forward() def __save_settings(self): """ 保存全部配置 :return: """ # 保存脚本 self.__save_bat_script() # 保存配置信息 self.settings_manage.set('fire_wall_open', self.ui.fire_wall_open.isChecked()) self.settings_manage.set('fire_wall_close', self.ui.fire_wall_close.isChecked()) self.settings_manage.set('auto_start_wsl', self.ui.auto_start_wsl.isChecked()) self.settings_manage.set('ports', self.ui.port_text.toPlainText().splitlines()) # 保存成功提示 QMessageBox.information(self.ui, '系统提示', '配置保存成功!') def __save_bat_script(self): """ 保存启动脚本 :return: """ content = self.ui.bat_text.toPlainText() self.settings_manage.set('wsl_bat_content', content) self.wsl2.save_bat_script(content) def __fire_wall_rule_add(self, port): """ 添加防火墙 :param port: 端口号,多个端口逗号隔开 :return: """ if self.ui.fire_wall_open.isChecked(): self.start_qt_process( self.wsl2.fire_wall_rule_add( wsl_port=port, wall_type=self.wsl2.FireWallRuleIn, exec_run=False)) self.ui.result_text.appendPlainText('>>> 添加防火墙:【' + self.wsl2.FireWallRuleIn + '】...') self.start_qt_process( self.wsl2.fire_wall_rule_add( wsl_port=port, wall_type=self.wsl2.FireWallRuleOut, exec_run=False)) self.ui.result_text.appendPlainText('>>> 添加防火墙:【' + self.wsl2.FireWallRuleOut + '】...') def __fire_wall_rule_del(self): """ 删除防火墙 :return: """ if self.ui.fire_wall_close.isChecked(): self.start_qt_process( self.wsl2.fire_wall_rule_del( wall_type=self.wsl2.FireWallRuleIn, exec_run=False)) self.ui.result_text.appendPlainText('>>> 删除防火墙:【' + self.wsl2.FireWallRuleIn + '】...') self.start_qt_process( self.wsl2.fire_wall_rule_del( wall_type=self.wsl2.FireWallRuleOut, exec_run=False)) self.ui.result_text.appendPlainText('>>> 删除防火墙:【' + self.wsl2.FireWallRuleOut + '】...') def __port_add_one(self, port, wsl2_ip_info): """ 添加单个端口 :param port: 端口号 :param wsl2_ip_info: 转发的IP :return: """ self.start_qt_process( self.wsl2.port_add(wsl_ip=wsl2_ip_info, wsl_port=port, exec_run=False)) self.ui.result_text.appendPlainText('>>> 添加端口:【' + port + '】...') def __port_del_one(self, port): """ 删除单个端口 :param port: 端口号 :return: """ self.start_qt_process(self.wsl2.port_del(wsl_port=port, exec_run=False)) self.ui.result_text.appendPlainText('>>> 删除端口:【' + port + '】...') def start_qt_process(self, cmd): """ 启动子进程执行耗时命令 :param cmd: :return: """ process = QProcess(self.ui) process.start(cmd) result = process.waitForStarted() return result
class ControlMainWindow(QMainWindow): def __init__(self, crypter): super(ControlMainWindow, self).__init__(None) self.icon = QSystemTrayIcon() self.icon.setIcon(QtGui.QIcon('./eve_tray.png')) self.icon.show() self.setWindowIcon(QtGui.QIcon('./eve_tray.png')) self.setWindowTitle('Pve Launcher') self.ui = Ui_main_window() self.ui.setupUi(self) self.icon.activated.connect(self.activate) self.account_list_model = QStringListModel() self.ui.listView.setModel(self.account_list_model) self.login_manager = EveLoginManager(crypter) self.init_none_ui(crypter) self.settings = None self.load_settings() self.ui.txt_client_path.setText(self.settings['eve_path']) def init_none_ui(self, crypter): self.login_manager.load() acc_list = [] for account_name in self.login_manager.accounts: acc_list.append(account_name) self.account_list_model.setStringList(acc_list) version_thread = Thread(target=self.check_eve_version) version_thread.start() def load_settings(self): try: with open('pvesettings.json', 'r') as settings_file: self.settings = json.load(settings_file) except FileNotFoundError: self.settings = dict() self.settings['eve_path'] = "" def save_settings(self): with open('pvesettings.json', 'w') as settings_file: json.dump(self.settings, settings_file) def closeEvent(self, event): self.login_manager.save() self.save_settings() def changeEvent(self, event): if event.type() == QEvent.WindowStateChange: if self.windowState() & Qt.WindowMinimized: self.icon.show() QTimer.singleShot(0, self, SLOT('hide()')) event.ignore() def func_launch(self): indexes = self.ui.listView.selectedIndexes() # i get QModelIndex here for idx in indexes: try: self.login_manager.login(idx.data(), self.get_auth_code, self.get_charname, self.ui.txt_client_path.text(), self.ui.cbox_server.currentText()) except Exception as e: logger.exception('Failed to launch') invoke_in_main_thread(QMessageBox.critical, self, "Launch Error", e.__str__(), QMessageBox.Ok) def func_edit(self): indexes = self.ui.listView.selectedIndexes() # i get QModelIndex here for idx in indexes: account = self.login_manager.accounts[idx.data()] dialog = AccountDialog("Edit Account", account.login_name, account.plain_password(self.login_manager.coder), account.direct_x, account.profile_name) if dialog.show(): # result = [name, password, path, dx]: path = dialog.result[2] if not path.endswith(os.sep): path = path + os.sep account = EveAccount(dialog.result[0], dialog.result[1], self.login_manager.coder, None, None, dialog.result[3], dialog.result[2]) self.login_manager.add_account(account) def func_add(self): dialog = AccountDialog("Create Account") if dialog.show(): # [name, password, profile_name, dx] account = EveAccount(dialog.result[0], dialog.result[1], self.login_manager.coder, None, None, dialog.result[3], dialog.result[2]) self.login_manager.add_account(account) acc_list = self.account_list_model.stringList() acc_list.append(account.login_name) self.account_list_model.setStringList(acc_list) def func_delete(self): indexes = self.ui.listView.selectedIndexes() # i get QModelIndex here model = self.ui.listView.model() for idx in indexes: self.login_manager.del_account(idx.data()) model.removeRow(idx.row()) def func_clear_cache(self): self.login_manager.clear_cache() def activate(self, reason): if reason == QSystemTrayIcon.Trigger or reason == QSystemTrayIcon.DoubleClick: self.showNormal() self.raise_() self.activateWindow() # self.setWindowState(Qt.WindowNoState) # self.activateWindow() def func_browse_eve(self): folder = QDir.toNativeSeparators( QFileDialog.getExistingDirectory(None, "Eve Directory", "", QFileDialog.ShowDirsOnly)) if not folder.endswith(os.sep): folder += os.sep self.ui.txt_client_path.setText(folder) self.settings['eve_path'] = folder def check_eve_version(self): headers = {'User-Agent': EveLoginManager.useragent} #version_url = "http://client.eveonline.com/patches/premium_patchinfoTQ_inc.txt" #req = request.Request(version_url, headers=headers) #response = request.urlopen(req) #version_data = response.read().decode('utf-8') #match = re.match("BUILD:(\\d+)", version_data) server_status = EveApi.get_server_status() if server_status.version is None: return None version_string = str(server_status.version) eve_version_okay: bool = check_eve_version( version_string, self.ui.txt_client_path.text()) if not eve_version_okay: invoke_in_main_thread(QtWidgets.QMessageBox.information, self, "Eve Clients out of date", "Your eve client is out of date.", QtWidgets.QMessageBox.Ok) def set_server_status(self, text, number): self.ui.label_server_status.setText( QApplication.translate("main_window", text, None) + "({0:d})".format(number)) def get_auth_code(self, opener, request): """ :param opener: urllib.request.build_opener for sending an authcode per mail :param request: request to send using the given opener :return: the authcode """ inputDialog = QInputDialog(self) inputDialog.setInputMode(QInputDialog.TextInput) inputDialog.setCancelButtonText("Cancel") inputDialog.setLabelText("Please enter your Authcode") inputDialog.setWindowTitle("TwoFactorAuth") inputDialog.setModal(True) response = None if inputDialog.exec_() == QInputDialog.Rejected: # send mail return None, None else: return response, inputDialog.textValue().strip() inputDialog.setCancelButtonText("Cancel") if inputDialog.exec_() == QInputDialog.Rejected: return response, None return response, inputDialog.textValue().strip() def get_charname(self): """ :param mailurl: url to call for sending an authcode per mail :return: the authcode """ inputDialog = QInputDialog(self) inputDialog.setInputMode(QInputDialog.TextInput) inputDialog.setLabelText("Please enter a Charname") inputDialog.setWindowTitle("Charname Challange") inputDialog.setModal(True) if inputDialog.exec_() == QInputDialog.Rejected: return None return inputDialog.textValue().strip()
class MainWindow(QMainWindow): """The main window of the application Currently just displays "Hello, world!". """ def __init__(self): QMainWindow.__init__(self) icon = QIcon(ICON_IMAGE) self.setMinimumSize(300, 50) self.setWindowTitle("Echo VR Tray Tool") self.setWindowIcon(icon) main_widget = QWidget(self) self.setCentralWidget(main_widget) main_layout = QGridLayout(main_widget) discord_status_header = QLabel("Discord status:") discord_status_header.setFont(_HEADER_FONT) main_layout.addWidget(discord_status_header, 0, 0, Qt.AlignRight) self._discord_status_label = QLabel("Unknown") main_layout.addWidget(self._discord_status_label, 0, 1, Qt.AlignLeft) echo_vr_status_header = QLabel("Echo VR client status:") echo_vr_status_header.setFont(_HEADER_FONT) main_layout.addWidget(echo_vr_status_header, 1, 0, Qt.AlignRight) self._echo_vr_client_status_label = QLabel("Unknown") main_layout.addWidget(self._echo_vr_client_status_label, 1, 1, Qt.AlignLeft) main_layout.setRowStretch(2, 1) self.tray_icon = QSystemTrayIcon(icon, self) tray_menu = QMenu() show_action = QAction("Show", self) show_action.triggered.connect(self.show) tray_menu.addAction(show_action) quit_action = QAction("Exit", self) quit_action.triggered.connect(self._quit) tray_menu.addAction(quit_action) self.tray_icon.setContextMenu(tray_menu) self.tray_icon.show() self._discord_presence_thread = None self._start_discord_presence_thread() def closeEvent(self, event): """Overridden to minimize to tray instead of exiting""" event.ignore() self.hide() self.tray_icon.showMessage( "Application is still running", "Echo VR Tray Tool was minimized to tray. Right-click and press 'exit' to quit.", QSystemTrayIcon.Information, 3000, ) def _quit(self): if self._discord_presence_thread: self._discord_presence_thread.exit() self._discord_presence_thread.wait() qApp.quit() def _start_discord_presence_thread(self): if self._discord_presence_thread: return self._discord_presence_thread = DiscordPresenceThread() self._discord_presence_thread.connection_status_changed.connect( self._discord_connection_status_changed) self._discord_presence_thread.game_client_status_changed.connect( self._game_client_status_changed) self._discord_presence_thread.start() def _discord_connection_status_changed(self, connected): if connected: self._discord_status_label.setText("Connected") self._discord_status_label.setStyleSheet(_GREEN_LABEL) else: self._discord_status_label.setText("Disconnected") self._discord_status_label.setStyleSheet(_RED_LABEL) def _game_client_status_changed(self, connected): if connected: self._echo_vr_client_status_label.setText("Connected") self._echo_vr_client_status_label.setStyleSheet(_GREEN_LABEL) else: self._echo_vr_client_status_label.setText("Disconnected") self._echo_vr_client_status_label.setStyleSheet(_RED_LABEL)
class MainWindow(Ui_Form): def __init__(self, parent=None, args=None): super(MainWindow, self).__init__(parent) self.appname = "poliBeePsync" self.settings_fname = 'pbs-settings.ini' self.data_fname = 'pbs.data' self.setupUi(self) self.w = QWidget() self.status_signal = MySignal() self.status_signal.sig.connect(self.update_status_bar) self.logging_signal = MySignal() self.logging_signal.sig.connect(self.myStream_message) logging_console_hdl = SignalLoggingHandler(self.logging_signal) logger.addHandler(logging_console_hdl) commonlogger.addHandler(logging_console_hdl) self.timer = QTimer(self) # settings_path is a string containing the path to settings self.settings_path = None # settings is a dictionary of settings self.settings = None # load_settings() sets settings_path and settings self.load_settings() self.load_data() if args.default_timeout: self.user.default_timeout = args.default_timeout self.timer.timeout.connect(self.syncfiles) self.timer.start(1000 * 60 * int(self.settings['UpdateEvery'])) self.loginthread = LoginThread(self.user, self) self.loginthread.signal_error.sig.connect(self.update_status_bar) self.loginthread.signal_ok.sig.connect(self.update_status_bar) self.refreshcoursesthread = RefreshCoursesThread(self.user, self) self.refreshcoursesthread.dumpuser.sig.connect(self.dumpUser) self.refreshcoursesthread.newcourses.sig.connect(self.addtocoursesview) self.refreshcoursesthread.newcourses.sig.connect(self.syncnewcourses) self.refreshcoursesthread.removable.sig.connect(self.rmfromcoursesview) self.downloadthread = DownloadThread(self.user, self.settings['RootFolder'], self) self.downloadthread.dumpuser.sig.connect(self.dumpUser) self.downloadthread.download_signal.connect( self.update_course_download) self.downloadthread.initial_sizes.connect(self.setinizialsizes) self.downloadthread.date_signal.connect(self.update_file_localtime) self._window.userCode.setText(str(self.user.username)) self._window.userCode.editingFinished.connect(self.setusercode) self._window.password.setText(self.user.password) self._window.password.editingFinished.connect(self.setpassword) self._window.trylogin.clicked.connect(self.testlogin) self._window.courses_model = CoursesListModel( self.user.available_courses) self._window.coursesView.setModel(self._window.courses_model) self._resizeview() self._window.refreshCourses.clicked.connect(self.refreshcourses) self._window.syncNow.clicked.connect(self.syncfiles) self._window.rootfolder.setText(self.settings['RootFolder']) self._window.rootfolder.textChanged.connect(self.rootfolderslot) init_checkbox(self._window.addSyncNewCourses, self.settings, 'SyncNewCourses', state_slot=self.syncnewslot) init_checkbox(self._window.startupSync, self.settings, 'SyncOnStartup', state_slot=self.sync_on_startup_slot) self._window.timerMinutes.setValue(int(self.settings['UpdateEvery'])) self._window.timerMinutes.valueChanged.connect(self.updateminuteslot) self._window.changeRootFolder.clicked.connect(self.chooserootdir) self._window.version_label.setText( "Current version: {}".format(__version__)) self._window.check_version.clicked.connect(self.checknewversion) self._window.about.clicked.connect(self.showabout) self.trayIconMenu = QMenu() self.trayIcon = QSystemTrayIcon(self.icon, self.w) self.trayIcon.activated.connect(self._activate_traymenu) self.createTray() try: if args.sync_on_startup or \ self.settings['SyncOnStartup'] == str(True): self.syncfiles() except KeyError: pass if args.sync_interval: logger.info('Sync interval overridden with ' f'{args.sync_interval} minutes') self.timer.start(1000 * 60 * args.sync_interval) @Slot() def showabout(self, **kwargs): msgBox = QMessageBox(self._window) msgBox.setTextFormat(Qt.RichText) msgBox.setWindowTitle('About poliBeePSync') text = """ <html> <head/> <body> <p> poliBeePsync is a program written by Davide Olianas and Raffaele Di Campli released under GNU GPLv3+. More information is available on the <a href=\"https://github.com/Jacotsu/polibeepsync\"> <span style=\" text-decoration: underline; color:#0000ff;\"> official github</span></a>. Feel free to contact us at <a href=\"mailto:[email protected]\">[email protected]</a> for suggestions and bug reports. </p> <p> Want to learn how to make softwares like this? Then join <br> <a href='https://poul.org/'> <img src=':/root/imgs/PinguiniStilNovoFullLogoBlack.svg'> </a> </p> <p> <a href='https://liberapay.com/jacotsu/donate'> Want to offer me a sandwich? </a> </p> </body> </html> """ msgBox.setInformativeText(text) msgBox.exec() @Slot() def _resizeview(self, **kwargs): self._window.coursesView.setColumnWidth(3, 160) self._window.coursesView.resizeColumnToContents(1) self._window.coursesView.setColumnWidth(0, 320) def checknewversion(self): rawdata = requests.get('https://pypi.python.org/pypi/' 'poliBeePsync/json') latest = json.loads(rawdata.text)['info']['version'] if latest != __version__: newtext = 'Current version: {}. Latest version: {}. '\ 'Click <a href="https://jacotsu.github.io/polibeepsync/build/'\ 'html/installation.html">here</a>'\ ' to find out how to upgrade'.format(__version__, latest) else: newtext = "Current version: {} up-to-date.".format(__version__) self._window.version_label.setText(newtext) def _update_time(self, folder, file, path_list): logger.debug(f'inside {folder.name}') for path in path_list: logger.debug(f'namegoto: {path}') folder_dict = {'name': path} fakefolder = Folder(folder_dict) logger.debug(f'contained folders: {folder.folders}') ind = folder.folders.index(fakefolder) goto = folder.folders[ind] self._update_time(goto, file, path_list) if file in folder.files: ind = folder.files.index(file) thisfile = folder.files[ind] thisfile.local_creation_time = file.local_creation_time @Slot(tuple) def update_file_localtime(self, data, **kwargs): course, coursefile, path = data rootpath = os.path.join(self.settings['RootFolder'], course.save_folder_name) if path.startswith(rootpath): partial = path[len(rootpath):] path_list = filter(None, partial.split(os.path.sep)) self._update_time(course.documents, coursefile, path_list) @Slot(Course) def update_course_download(self, course, **kwargs): if course in self.user.available_courses: updating = self.user.available_courses[course.name] updating.downloaded_size = course.downloaded_size row = self._window.courses_model.courses.index(updating) where = self._window.courses_model.index(row, 3) self._window.courses_model.dataChanged.emit(where, where) @Slot(Course) def setinizialsizes(self, course, **kwargs): if course in self.user.available_courses: updating = self.user.available_courses[course.name] updating.downloaded_size = course.downloaded_size updating.size = course.size row = self._window.courses_model.courses.index(updating) where = self._window.courses_model.index(row, 3) self._window.courses_model.dataChanged.emit(where, where) self.dumpUser() @Slot(list) def syncnewcourses(self, newlist): if self.settings['SyncNewCourses'] == 'True': for elem in newlist: elem.sync = True def load_settings(self): for path in [ user_config_dir(self.appname), user_data_dir(self.appname) ]: try: os.makedirs(path, exist_ok=True) except OSError: logger.critical('OSError while calling os.makedirs.', exc_info=True) logger.critical(f"I couldn't create {path}.\nStart" " poliBeePsync with --log-level=debug " "error to get more details.") self.settings_path = os.path.join(user_config_dir(self.appname), self.settings_fname) defaults = { # Update every 8 hours 'UpdateEvery': '480', 'RootFolder': os.path.join(os.path.expanduser('~'), self.appname), 'SyncNewCourses': 'False', 'SyncOnStartup': 'False' } self.settings = filesettings.settingsFromFile(self.settings_path, defaults) def load_data(self): try: with open( os.path.join(user_data_dir(self.appname), self.data_fname), 'rb') as f: self.user = pickle.load(f) self.user.password = keyring\ .get_password('beep.metid.polimi.it', self.user.username) logger.info("Data has been loaded successfully.") except (EOFError, pickle.PickleError): logger.error('Settings corrupted', exc_info=True) self.user = User('', '') except FileNotFoundError: logger.error('Settings file not found.') self.user = User('', '') logger.error("I couldn't find data in the" " predefined directory. Ignore this" "message if you're using poliBeePsync" " for the first time.") @Slot(str) def update_status_bar(self, status): self._window.statusbar.showMessage(status) @Slot(int) def syncnewslot(self, state): if state == 2: self.settings['SyncNewCourses'] = 'True' logger.info('New courses will now be automatically synced') else: self.settings['SyncNewCourses'] = 'False' logger.info('New courses will NOT be automatically synced') filesettings.settingsToFile(self.settings, self.settings_path) @Slot(int) def sync_on_startup_slot(self, state): if state == 2: self.settings['SyncOnStartup'] = 'True' logger.info('All courses will be synced at startup') else: self.settings['SyncOnStartup'] = 'False' logger.info('No course will be synced at startup') filesettings.settingsToFile(self.settings, self.settings_path) @Slot(int) def updateminuteslot(self, minutes): self.settings['UpdateEvery'] = str(minutes) filesettings.settingsToFile(self.settings, self.settings_path) self.timer.start(1000 * 60 * int(self.settings['UpdateEvery'])) logger.info('All courses will be automatically synced every ' f'{self.settings["UpdateEvery"]} minutes') @Slot(str) def rootfolderslot(self, path): self.settings['RootFolder'] = path filesettings.settingsToFile(self.settings, self.settings_path) logger.info(f'Root folder set to: {path}') @Slot() def chooserootdir(self): currentdir = self.settings['RootFolder'] flags = QFileDialog.DontResolveSymlinks | QFileDialog.ShowDirsOnly newroot = QFileDialog.getExistingDirectory(None, "Open Directory", currentdir, flags) if newroot != "" and str(newroot) != currentdir: self.settings['RootFolder'] = str(newroot) filesettings.settingsToFile(self.settings, self.settings_path) self._window.rootfolder.setText(newroot) # we delete the already present downloadthread and recreate it # because otherwise it uses the old download folder. I don't know # if there's a cleaner approach del self.downloadthread self.downloadthread = DownloadThread(self.user, self.settings['RootFolder'], self) self.downloadthread.dumpuser.sig.connect(self.dumpUser) self.dumpUser() @Slot() def setusercode(self): newcode = self._window.userCode.text() try: if len(newcode) == 8: self.user.username = newcode logger.info(f'User code changed to {newcode}.') keyring.set_password('beep.metid.polimi.it', self.user.username, self.user.password) except OSError: logger.critical("I couldn't save data to disk. Run" " poliBeePsync with option --log-level=debug" " error to get more details.") logger.error( 'OSError raised while trying to write the User' 'instance to disk.', exc_info=True) @Slot() def setpassword(self): newpass = self._window.password.text() self.user.password = newpass try: keyring.set_password('beep.metid.polimi.it', self.user.username, self.user.password) logger.info("Password changed.") except OSError: logger.critical("I couldn't save data to disk. Run" " poliBeePsync with option --log-level=debug" " error to get more details.") logger.error( 'OSError raised while trying to write the User' 'instance to disk.', exc_info=True) @Slot() def testlogin(self): if not self.loginthread.isRunning(): self.loginthread.exiting = False self.loginthread.start() self.status_signal.sig.emit("Logging in, please wait.") @Slot(list) def addtocoursesview(self, addlist): for elem in addlist: self._window.courses_model.insertRows(0, 1, elem) @Slot(list) def rmfromcoursesview(self, removelist): for elem in removelist: index = self._window.courses_model.courses.index(elem) self._window.courses_model.removeRows(index, 1) @Slot() def dumpUser(self): # we don't use the message... with open(os.path.join(user_data_dir(self.appname), self.data_fname), 'wb') as f: tmp_pw = self.user.password self.user.password = '' pickle.dump(self.user, f) self.user.password = tmp_pw @Slot() def refreshcourses(self): self.status_signal.sig.emit('Searching for online updates...' 'this may take a while.') if not self.loginthread.isRunning(): self.loginthread.exiting = False self.loginthread.signal_ok.sig.connect(self.do_refreshcourses) self.loginthread.start() def do_refreshcourses(self): self.loginthread.signal_ok.sig.disconnect(self.do_refreshcourses) if not self.refreshcoursesthread.isRunning(): self.refreshcoursesthread.start() @Slot() def syncfiles(self): # we delete the already present downloadthread and recreate it # because otherwise it uses the old download folder. I don't know # if there's a cleaner approach del self.downloadthread self.downloadthread = DownloadThread(self.user, self.settings['RootFolder'], self) self.downloadthread.dumpuser.sig.connect(self.dumpUser) self.refreshcoursesthread.finished.connect(self.do_syncfiles) self.refreshcourses() @Slot() def do_syncfiles(self): self.refreshcoursesthread.finished.disconnect(self.do_syncfiles) self.status_signal.sig.emit('Started syncing.') self.downloadthread.start() @Slot(str) def myStream_message(self, message): self._window.status.moveCursor(QTextCursor.End) self._window.status.insertPlainText(message + "\n") def restore_window(self): self._window.setWindowState(self.windowState() & ~Qt.WindowMinimized | Qt.WindowActive) self._window.show() def createTray(self): restoreAction = QAction("&Restore", self, triggered=self.restore_window) quitAction = QAction("&Quit", self, triggered=QApplication.instance().quit) self.trayIconMenu.addAction(restoreAction) self.trayIconMenu.addAction(quitAction) self.trayIcon.setContextMenu(self.trayIconMenu) self.trayIcon.show() @Slot(str) def _activate_traymenu(self, reason): if reason == QSystemTrayIcon.ActivationReason.DoubleClick: self.restore_window() else: self.trayIconMenu.activateWindow() self.trayIconMenu.popup(QCursor.pos()) def closeEvent(self, event): self._window.hide() event.ignore()
def start(_exit: bool = False) -> None: show_ui = True if "-h" in sys.argv or "--help" in sys.argv: print(f"Usage: {os.path.basename(sys.argv[0])}") print("Flags:") print(" -h, --help\tShow this message") print(" -n, --no-ui\tRun the program without showing a UI") return elif "-n" in sys.argv or "--no-ui" in sys.argv: show_ui = False app = QApplication(sys.argv) logo = QIcon(LOGO) main_window = MainWindow() ui = main_window.ui main_window.setWindowIcon(logo) tray = QSystemTrayIcon(logo, app) tray.activated.connect(main_window.systray_clicked) menu = QMenu() action_exit = QAction("Exit") action_exit.triggered.connect(app.exit) menu.addAction(action_exit) tray.setContextMenu(menu) ui.text.textChanged.connect(partial(queue_text_change, ui)) ui.command.textChanged.connect(partial(update_button_command, ui)) ui.keys.textChanged.connect(partial(update_button_keys, ui)) ui.write.textChanged.connect(partial(update_button_write, ui)) ui.change_brightness.valueChanged.connect(partial(update_change_brightness, ui)) ui.switch_page.valueChanged.connect(partial(update_switch_page, ui)) ui.imageButton.clicked.connect(partial(select_image, main_window)) ui.removeButton.clicked.connect(partial(remove_image, main_window)) ui.settingsButton.clicked.connect(partial(show_settings, main_window)) api.streamdesk_keys.key_pressed.connect(handle_keypress) items = api.open_decks().items() if len(items) == 0: print("Waiting for Stream Deck(s)...") while len(items) == 0: time.sleep(3) items = api.open_decks().items() for deck_id, deck in items: ui.device_list.addItem(f"{deck['type']} - {deck_id}", userData=deck_id) dimmers[deck_id] = Dimmer( api.get_display_timeout(deck_id), api.get_brightness(deck_id), partial(change_brightness, deck_id), ) dimmers[deck_id].reset() build_device(ui) ui.device_list.currentIndexChanged.connect(partial(build_device, ui)) ui.pages.currentChanged.connect(partial(change_page, ui)) ui.actionExport.triggered.connect(partial(export_config, main_window)) ui.actionImport.triggered.connect(partial(import_config, main_window)) ui.actionExit.triggered.connect(app.exit) timer = QTimer() timer.timeout.connect(partial(sync, ui)) timer.start(1000) api.render() tray.show() if show_ui: main_window.show() if _exit: return else: app.exec_() api.close_decks() sys.exit()
configuration_file_path = config_path.ConfigPath('pynfinitton', 'creatingfuture.eu', '.ini') device_manager = logic.DeviceManager(configuration_file_path) # If graphical app = QApplication(sys.argv) translator = QTranslator() translator.load(QLocale(device_manager.locale), 'pynfinitton', '.', directory=os.path.join(path, 'resources', 'translations')) app.installTranslator(translator) window = gui.MainWindow(device_manager) window.resize(800, 600) if device_manager.open_on_start: window.show() tray_icon_menu = gui.TrayIconMenu(window) tray_icon = QSystemTrayIcon(QIcon('resources/ui/light/app_icon.png'), window) tray_icon.setToolTip("Pynfinitton") tray_icon.setContextMenu(tray_icon_menu) tray_icon.show() sys.exit(app.exec_())
class MainWidget(QWidget): def __init__(self, app): QWidget.__init__(self) log.info('Creating MainWidget') # Configurar janela e sessão requests self.setWindowTitle('Desafio Upload') self.setWindowIcon(QIcon("icon.png")) self.session = requests.Session() # Configurar telas mostradas em StackedLayout self.stackedLayout = QStackedLayout() self.login_layout = LoginWidget(self.session) self.login_layout.did_login.connect(self.go_to_main_ui) self.setFixedSize(self.login_layout.size()) self.main_layout = UploadViewWidget(self.session) self.stackedLayout.addWidget(self.login_layout) self.stackedLayout.addWidget(self.main_layout) self.setLayout(self.stackedLayout) # Inicializar variáveis relacionadas ao comportamento de system tray self.closeEvent = self.on_close self.system_tray = QSystemTrayIcon() self.system_tray.setContextMenu(QMenu('Hi!', self)) # Tray menu self.tray = QSystemTrayIcon(QIcon("icon.png"), self) self.tray_menu = QMenu(self) action_show_window = QAction("Mostrar janela principal", self) action_show_window.triggered.connect(self.on_show_main_window) self.tray_menu.addAction(action_show_window) action_exit = QAction("Fechar", self) action_exit.triggered.connect(app.exit) self.tray_menu.addAction(action_exit) self.tray.setContextMenu(self.tray_menu) self.tray.activated.connect(self.on_tray_activated) self.tray.hide() # # # # # # # # # # # # # # # # # # # # # Transição entre telas def go_to_main_ui(self, username: str): self.main_layout.label_welcome_username.setText( f'Bem-vindo, {username}.') self.stackedLayout.setCurrentIndex(1) # # # # # # # # # # # # # # # # # # # # # Interação com o system tray e visibilidade da janela principal def on_close(self, event): log.info('Moving program to tray') self.tray.show() def on_show_main_window(self): log.info('Moving program from tray to window') self.show() self.tray.hide() def on_tray_activated(self, event: QSystemTrayIcon.ActivationReason): if event == QSystemTrayIcon.ActivationReason.Trigger: self.on_show_main_window()
class App: killSignal = signal.SIGUSR1 def __init__(self): # Qt application creation. self.qApp = QApplication(sys.argv) # System tray icon menu creation. menu = QMenu() showMainWindowAction = menu.addAction(_('Show %s window') % (APP_NAME)) showMainWindowAction.triggered.connect(self.showMainWindow) menu.addSeparator() exitAction = menu.addAction(_('Exit')) exitAction.triggered.connect(self._quit) # System tray icon creation. self.tray = QSystemTrayIcon() self.tray.setIcon(QIcon(self.getResourceFile(APP_ICON))) self.tray.setContextMenu(menu) self.tray.show() self.tray.setToolTip(APP_NAME) self.tray.activated.connect(self._iconActivated) # QWebEngineView to handle external urls opening. self.wevExternalUrl = QWebEngineView() def _download(self, download): filename = QFileDialog.getSaveFileName(None, _('Save as'), download.path(), "") if (filename[0] == ''): download.cancel() else: download.setPath(filename[0]) download.accept() def _iconActivated(self, reason): if (reason == QSystemTrayIcon.Trigger): self.toggleVisible() def _kill(self): if (self.killSignal == signal.SIGUSR1): self.killSignal = signal.SIGTERM elif (self.killSignal == signal.SIGTERM): self.killSignal = signal.SIGKILL print('Killing...') os.killpg(0, self.killSignal) def _launchExternalUrl(self, url): self.wevExternalUrl.urlChanged.disconnect(self._launchExternalUrl) _EOU(url.toString()) def _quit(self, checked): # Sometimes QtWebEngineProcess hangs and next code is a work around. self.timer = QTimer() self.timer.timeout.connect(self._kill) self.timer.start(5000 * 10) # Two seconds for mercy. sys.exit() def getResourceFile(self, fileName): fileNameAux = fileName.lower() if (fileNameAux == APP_ICON): fileName = os.path.join(_PR2A('desktop'), 'Juasap.png') return fileName def hideMainWindow(self): self.mainWindow.hide() def run(self): self.mainWindow = MainWindow() availableGeometry = self.qApp.desktop().availableGeometry( self.mainWindow) self.mainWindow.resize(availableGeometry.width() * 0.40, availableGeometry.height() * 0.90) self.mainWindow.show() def showMainWindow(self): self.mainWindow.show() def toggleVisible(self): if self.mainWindow.isVisible(): self.hideMainWindow() else: self.showMainWindow()
class MyWidget(QWidget): def __init__(self): super().__init__() self.streamfile = "" self.streams = {} scriptdir = os.path.dirname(os.path.realpath(__file__)) icon = (scriptdir + os.path.sep + "icon/pyradio.ico") self.setWindowIcon(QtGui.QIcon(icon)) self.setStuff() # Tray self.tray = QSystemTrayIcon() self.tray.setIcon(QtGui.QIcon(icon)) self.tray.activated.connect(self.call) self.icon = QtGui.QIcon() self.icon.addFile(icon) self.setWindowIcon(self.icon) # tray menu self.trayIconMenu = QtWidgets.QMenu() self.quitAction = QtWidgets.QAction("&Quit", triggered=self.close) self.trayIconMenu.addAction(self.quitAction) self.tray.setContextMenu(self.trayIconMenu) self.trayIconMenu.setStyleSheet(open("css/main.css", "r").read()) # Media player self.radio = vlc.MediaPlayer() self.playing = False self.pal = QtGui.QPalette(self.palette()) self.playing_label = QLabel("Stopped") self.label = QLabel("Radios:") self.label.setAlignment(Qt.AlignCenter) self.playing_label.setAlignment(Qt.AlignCenter) self.btn = QPushButton("Play/Stop") self.btn.clicked.connect(self.control) self.list = QListWidget() self.list.itemDoubleClicked.connect(self.control) self.edit = QPushButton("Edit Radios") self.edit.clicked.connect(self.openfile) self.refresh = QPushButton("Refresh") self.refresh.clicked.connect(self.refreshstreams) self.slider = QSlider(QtGui.Qt.Horizontal) self.slider.setMaximum(100) self.slider.setValue(self.volume) self.slider.valueChanged.connect(self.changeVolume) self.setStyleSheet(open("css/main.css", "r").read()) self.refreshstreams() self.current = "" self.buttons = QHBoxLayout() self.layout = QVBoxLayout() self.layout.addWidget(self.label) self.layout.addWidget(self.list) self.layout.addWidget(self.playing_label) self.layout.addWidget(self.slider) self.buttons.addWidget(self.btn) self.buttons.addWidget(self.edit) self.buttons.addWidget(self.refresh) self.layout.addLayout(self.buttons) self.setLayout(self.layout) def setStuff(self): info = self.readInfo() print(info) if len(info) == 0: info = ["", "", "", "", ""] if (info[0] == ""): self.volume = 80 else: self.volume = int(info[0]) if info[3].strip() == "false" or info[3] == "": if info[1] == "": self.resize(800, 600) else: w, h = info[1].split(" ") self.resize(int(w), int(h)) if info[2] != "": x, y = info[2].split(" ") self.move(int(x), int(y)) else: self.showMaximized() if len(info) < 5: # show dialog self.chooseStreamfile() else: self.streamfile = info[4].strip() def chooseStreamfile(self): self.dialog = QFileDialog(self) self.dialog.setFileMode(QFileDialog.AnyFile) if self.dialog.exec_(): filename = self.dialog.selectedFiles() self.streamfile = filename[0] def changeVolume(self): self.volume = self.slider.value() self.radio.audio_set_volume(self.volume) def getVolume(self): try: with open("data", "r") as file: return int(file.readline()) except: with open("data", "w") as file: file.write(str(80)) return 80 def control(self): if self.playing and self.current == self.streams[ self.list.currentItem().text()]: self.stop() else: self.radio.stop() self.play() def stop(self): self.radio.stop() self.playing_label.setText("Stopped") self.playing = False def play(self): self.current = self.list.currentItem().text() for i in self.streams: if self.current == i: self.current = self.streams[i] break self.radio = vlc.MediaPlayer(self.current) self.radio.play() self.radio.audio_set_volume(self.slider.value()) self.playing_label.setText("Playing") self.playing = True self.tray.showMessage(self.list.currentItem().text(), "", self.tray.icon(), 1000) def next(self): isthis = False self.current = self.list.currentItem().text() for n, i in enumerate(self.streams): if isthis: self.list.setCurrentRow(n) break else: if self.current == i: isthis = True if n + 1 >= len(self.streams): self.list.setCurrentRow(0) self.stop() self.play() def previous(self): isthis = False self.current = self.list.currentItem().text() for n, i in enumerate(self.streams): if isthis: self.list.setCurrentRow(n - 2) break else: if self.current == i: isthis = True if n - 1 < 0: self.list.setCurrentRow(len(self.streams) - 1) break elif n == len(self.streams) - 1: self.list.setCurrentRow(n - 1) self.stop() self.play() def openfile(self): # Opens radios.txt webbrowser.open(self.streamfile) def refreshstreams(self): # Refreshes the stream list when button pressed if self.list.currentItem(): current = self.list.currentItem().text() else: current = None self.streams = {} try: with open(self.streamfile, "r") as file: lines = file.readlines() for line in lines: nline = line.strip().split(":", 1) self.streams[nline[0]] = nline[1].split("#")[0] except: self.chooseStreamfile() self.refreshstreams() return self.list.clear() for i, n in enumerate(self.streams): self.list.addItem(n) if n == current: self.list.setCurrentRow(i) if not self.list.currentItem(): self.list.setCurrentRow(0) def changeEvent(self, event): # This minimizes the program to tray when Minimize button pressed if event.type() == QEvent.WindowStateChange: if self.windowState() & Qt.WindowMinimized: print(QSystemTrayIcon.isSystemTrayAvailable()) if QSystemTrayIcon.isSystemTrayAvailable( ) and self.isActiveWindow(): event.ignore() self.tray.show() self.hide() self.listener = keyboard.Listener( on_release=self.on_release) self.listener.start() def closeEvent(self, event): file = open("data", "w+") info = str(self.volume) + "\n" + str(self.size().width()) + " " + str(self.size().height()) + "\n" +\ str(self.pos().x()) + " " + str(self.pos().y()) + "\n" if (self.isMaximized()): info += "true" else: info += "false" info += "\n" info += self.streamfile + "\n" file.write(info) file.close() def readInfo(self): try: with open("data", "r", encoding="utf-8") as file: info = file.readlines() return info except: with open("data", "w", encoding="utf-8") as file: file.write("") return "" def keyReleaseEvent(self, event): # This is for media controls when radio is opened key = event.key() if key == Qt.Key_MediaPlay or key == Qt.Key_MediaTogglePlayPause or \ key == Qt.Key_MediaPause: self.control() elif key == Qt.Key_MediaNext: self.next() elif key == Qt.Key_MediaPrevious: self.previous() def call(self, reason): # This is caled when tray icon is pressed if reason == QSystemTrayIcon.ActivationReason.Trigger: self.show() self.setFocus() self.listener.stop() del self.listener self.tray.hide() self.setWindowState(Qt.WindowActive) elif reason == QSystemTrayIcon.ActivationReason.Context: self.tray.contextMenu().show() elif reason == QSystemTrayIcon.ActivationReason.MiddleClick: print("Middle click on tray icon") else: print("Unknown reason") def on_release(self, key): # This is for media controls when program in tray. try: if key == keyboard.Key.media_play_pause: # might need a different key self.control() elif keyboard.Key.media_next == key: # might need a different key self.next() elif keyboard.Key.media_previous == key: # might need a different key self.previous() except AttributeError as e: print(e)
def start(_exit: bool = False) -> None: show_ui = True if "-h" in sys.argv or "--help" in sys.argv: print(f"Usage: {os.path.basename(sys.argv[0])}") print("Flags:") print(" -h, --help\tShow this message") print(" -n, --no-ui\tRun the program without showing a UI") return elif "-n" in sys.argv or "--no-ui" in sys.argv: show_ui = False app = QApplication(sys.argv) logo = QIcon(LOGO) main_window = MainWindow() ui = main_window.ui main_window.setWindowIcon(logo) tray = QSystemTrayIcon(logo, app) tray.activated.connect(main_window.systray_clicked) menu = QMenu() action_dim = QAction("Dim display (toggle)") action_dim.triggered.connect(dim_all_displays) action_configure = QAction("Configure...") action_configure.triggered.connect(main_window.bring_to_top) menu.addAction(action_dim) menu.addAction(action_configure) menu.addSeparator() action_exit = QAction("Exit") action_exit.triggered.connect(app.exit) menu.addAction(action_exit) tray.setContextMenu(menu) ui.text.textChanged.connect(partial(queue_text_change, ui)) ui.font_Size.valueChanged.connect(partial(update_font_size, ui)) ui.command.textChanged.connect(partial(update_button_command, ui)) ui.keys.textChanged.connect(partial(update_button_keys, ui)) ui.write.textChanged.connect(partial(update_button_write, ui)) ui.change_brightness.valueChanged.connect( partial(update_change_brightness, ui)) ui.switch_page.valueChanged.connect(partial(update_switch_page, ui)) ui.imageButton.clicked.connect(partial(select_image, main_window)) ui.removeButton.clicked.connect(partial(remove_image, main_window)) ui.settingsButton.clicked.connect(partial(show_settings, main_window)) ui.font_Color.addItem("white") ui.font_Color.addItem("black") ui.font_Color.addItem("blue") ui.font_Color.addItem("red") ui.font_Color.addItem("green") ui.font_Color.addItem("purple") ui.font_Color.addItem("cyan") ui.font_Color.addItem("magenta") ui.font_Color.currentTextChanged.connect(partial(update_font_color, ui)) ui.selected_font.addItem("Goblin_One") ui.selected_font.addItem("Open_Sans") ui.selected_font.addItem("Roboto") ui.selected_font.addItem("Lobster") ui.selected_font.addItem("Anton") ui.selected_font.addItem("Pacifico") ui.selected_font.currentTextChanged.connect( partial(update_selected_font, ui)) ui.text_Align.addItem("left") ui.text_Align.addItem("center") ui.text_Align.addItem("right") ui.text_Align.currentTextChanged.connect(partial(update_text_align, ui)) api.streamdesk_keys.key_pressed.connect(handle_keypress) items = api.open_decks().items() if len(items) == 0: print("Waiting for Stream Deck(s)...") while len(items) == 0: time.sleep(3) items = api.open_decks().items() for deck_id, deck in items: ui.device_list.addItem(f"{deck['type']} - {deck_id}", userData=deck_id) ui.target_device.addItem(deck_id) dimmers[deck_id] = Dimmer( api.get_display_timeout(deck_id), api.get_brightness(deck_id), partial(change_brightness, deck_id), ) dimmers[deck_id].reset() build_device(ui) ui.device_list.currentIndexChanged.connect(partial(build_device, ui)) ui.target_device.currentTextChanged.connect( partial(update_target_device, ui)) ui.pages.currentChanged.connect(partial(change_page, ui)) ui.actionExport.triggered.connect(partial(export_config, main_window)) ui.actionImport.triggered.connect(partial(import_config, main_window)) ui.actionCut.triggered.connect(partial(cut_button, main_window)) ui.actionCopy.triggered.connect(partial(copy_button, main_window)) ui.actionCut.setShortcuts([QKeySequence.Cut, QKeySequence("Shift+Del")]) ui.actionCopy.setShortcuts( [QKeySequence.Copy, QKeySequence("Ctrl+Insert")]) ui.actionPaste.setShortcuts( [QKeySequence.Paste, QKeySequence("Shift+Insert")]) ui.actionDelete.setShortcuts([QKeySequence.Delete]) ui.actionPaste.triggered.connect(partial(paste_button, main_window)) ui.actionDelete.triggered.connect(partial(delete_button, main_window)) ui.actionMultiPaste.triggered.connect( partial(multi_paste_Button, main_window)) ui.actionExit.triggered.connect(app.exit) timer = QTimer() timer.timeout.connect(partial(sync, ui)) timer.start(1000) api.render() tray.show() if show_ui: main_window.show() if _exit: return else: app.exec_() api.close_decks() sys.exit()
class MainWindow(QMainWindow, Ui_MainWindow): def __init__(self): super(MainWindow, self).__init__() self.setupUi(self) self.setWindowTitle("ctpbee桌面端") # self.setWindowFlag(Qt.FramelessWindowHint) # 去边框 可能会导致闪屏异常 self.setStyleSheet(qss) self.animation_show() # G.mainwindow = self self.exit_ = False self._page_history = [] self._page_maxsize = 10 self.job = Job() self.kline_job = KInterfaceObject() self.record_work = RecordWorker() self.bee_ext = None self.tray_init() self.shortcut_init() ## self.status_msg = QLabel("实时信息") self.market_msg = QLabel("最新行情") self.statusbar.addPermanentWidget(self.status_msg, stretch=5) self.statusbar.addPermanentWidget(self.market_msg, stretch=5) # btn self.pre_page_btn.clicked.connect(self.pre_page_slot) self.home_btn.clicked.connect(self.home_handle) self.market_btn.clicked.connect(self.market_handle) self.order_btn.clicked.connect(self.order_handle) self.strategy_btn.clicked.connect(self.strategy_handle) self.setting_btn.clicked.connect(self.config_handle) self.log_btn.clicked.connect(self.log_handle) self.order_btn.clicked.connect(self.order_handle) self.backtrack_btn.clicked.connect(self.backtrack_handle) self.kline_btn.clicked.connect(self.kline_handle) # # self.menuBar.triggered.connect(self.menu_triggered) # widgets self.map_ = [] self.home_widget = None self.strategy_widget = None self.account_widget = None self.market_widget = None self.order_widget = None self.kline_widget = None self.log_dialog = None self.cfg_dialog = None self.backtrack_widget = None def sign_in_success(self): self.bee_ext = CtpbeeApi('default_setting', current_app) self.bee_ext.map[EVENT_ACCOUNT] = self.on_account self.bee_ext.map[EVENT_CONTRACT] = self.on_contract self.bee_ext.map[EVENT_BAR] = self.on_bar self.bee_ext.map[EVENT_ORDER] = self.on_order self.bee_ext.map[EVENT_POSITION] = self.on_position self.bee_ext.map[EVENT_TICK] = self.on_tick self.bee_ext.map[EVENT_SHARED] = self.on_shared self.bee_ext.map[EVENT_TRADE] = self.on_trade self.bee_ext.map[EVENT_TIMER] = self.on_realtime # contracts = { contract.local_symbol: contract.name for contract in self.bee_ext.app.recorder.get_all_contracts() } G.all_contracts = contracts self.home_handle() def menu_triggered(self, q): q = q.text() if q == "退出应用": self.quit() def animation_show(self): self.animation = QPropertyAnimation(self, b'windowOpacity') self.animation.stop() self.animation.setDuration(500) self.animation.setStartValue(0) self.animation.setEndValue(1) self.animation.start() @property def page_history(self): return self._page_history @page_history.setter def page_history(self, val): if len(self._page_history) == self._page_maxsize: # 达到最大容量 self._page_history.pop(0) # 弹出第一项 self._page_history.append(val) def pre_page_slot(self): try: i = self.page_history.pop() while i == self.stackedWidget.currentIndex(): i = self.page_history.pop() self.stackedWidget.setCurrentIndex(i) except IndexError: TipDialog("到底啦~") def shortcut_init(self): sc = G.config.SHORTCUT for name, sho in sc.items(): if sho == '--': continue temp = QShortcut(QKeySequence(self.tr(sho)), self) temp.activated.connect(getattr(self, f"{name}_handle")) setattr(self, f"{name}_sc", temp) def update_shortcut(self): sc = G.config.SHORTCUT for name, sho in sc.items(): getattr(self, f"{name}_sc").setKey(QKeySequence(self.tr(sho))) def page_map(self, w): name = w.__class__.__name__ if name not in self.map_: self.map_.append(name) i = self.map_.index(name) self.page_history = i return i def home_handle(self): if self.home_widget is None: self.home_widget = HomeWidget(self) self.stackedWidget.addWidget(self.home_widget) self.stackedWidget.setCurrentIndex(self.page_map(self.home_widget)) def strategy_handle(self): if self.strategy_widget is None: self.strategy_widget = StrategyWidget(self) self.stackedWidget.addWidget(self.strategy_widget) self.stackedWidget.setCurrentIndex(self.page_map(self.strategy_widget)) def backtrack_handle(self): if self.backtrack_widget is None: self.backtrack_widget = BacktrackWidget(self) self.stackedWidget.addWidget(self.backtrack_widget) self.stackedWidget.setCurrentIndex(self.page_map( self.backtrack_widget)) def market_handle(self): if self.market_widget is None: self.market_widget = MarketWidget(self) self.stackedWidget.addWidget(self.market_widget) self.stackedWidget.setCurrentIndex(self.page_map(self.market_widget)) G.current_page = "market" def kline_handle(self): if self.kline_widget is None: self.kline_widget = KlineWidget(self) self.stackedWidget.addWidget(self.kline_widget) self.stackedWidget.setCurrentIndex(self.page_map(self.kline_widget)) if G.choice_local_symbol: self.kline_widget.symbol_list.setCurrentText(G.choice_local_symbol) self.kline_widget.k_line_reload() def order_handle(self): if self.order_widget is None: self.order_widget = OrderWidget(self) self.order_widget.show() self.order_widget.raise_() def config_handle(self): if self.cfg_dialog is None: self.cfg_dialog = ConfigDialog(self) self.cfg_dialog.show() self.cfg_dialog.raise_() def log_handle(self): if self.log_dialog is None: self.log_dialog = LogDialog(self) self.log_dialog.show() self.log_dialog.raise_() def on_account(self, ext, account: AccountData) -> None: account = account._to_dict() G.account = account self.job.account_signal.emit(account) def on_contract(self, ext, contract: ContractData): pass def on_bar(self, ext, bar: BarData) -> None: """ vue kline""" # timestamp = round(bar.datetime.timestamp() * 1000) # info = [timestamp, bar.open_price, bar.high_price, bar.low_price, # bar.close_price, bar.volume] """ echarts kline """ timestamp = bar.datetime.strftime("%Y/%m/%d %H:%M:%S") info = [ timestamp, bar.open_price, bar.close_price, bar.low_price, bar.high_price, bar.volume ] # if bar.local_symbol == G.choice_local_symbol: data = {bar.local_symbol: info} self.kline_job.qt_to_js.emit(json.dumps(data)) # 存入文件 self.record_work.record_sig.emit(bar.local_symbol, info) def on_order(self, ext, order: OrderData) -> None: active_orders = [] for order1 in self.bee_ext.app.recorder.get_all_active_orders(): o1 = order1._to_dict() active_orders.append(o1) self.job.order_activate_signal.emit(active_orders) orders = [] for order2 in self.bee_ext.app.recorder.get_all_orders(): o2 = order2._to_dict() orders.append(o2) self.job.order_order_signal.emit(orders) def on_realtime(*args): self = args[0] all_positions = self.bee_ext.app.recorder.get_all_positions() self.job.order_position_signal.emit(all_positions) def on_position(self, ext, position: PositionData) -> None: pass def on_tick(self, ext, tick: TickData) -> None: self.market_msg.setText(f"最新行情:{tick.name} {tick.last_price}") tick = tick._to_dict() local_symbol = tick['local_symbol'] G.ticks[local_symbol] = tick if G.current_page == "market": self.job.market_signal.emit(tick) self.job.kline_tick_signal.emit(tick) def on_shared(self, ext, shared: SharedData) -> None: pass def on_trade(self, ext, trade: TradeData) -> None: trades = [] for trade in self.bee_ext.app.recorder.get_all_trades(): t = trade._to_dict() trades.append(t) self.job.order_trade_signal.emit(trades) def on_init(self, ext, init): pass def tray_init(self): icon = QIcon(":menu/images/bee_temp_grey.png") menu = QMenu() openAction = menu.addAction("🍯 界面") settingAction = menu.addAction("⚙ 设置") exitAction = menu.addAction("❎ 退出") settingAction.triggered.connect(self.config_handle) openAction.triggered.connect(self.show) exitAction.triggered.connect(self.quit) self.tray = QSystemTrayIcon() self.tray.setIcon(icon) self.tray.setContextMenu(menu) self.tray.activated.connect(self.iconActivated) self.tray.show() self.tray.setToolTip("ctpbee桌面端") def quit(self): self.exit_ = True self.close() def iconActivated(self, reason): if reason is QSystemTrayIcon.Trigger: self.show() self.raise_() def closeEvent(self, event: QCloseEvent): if self.exit_: G.pool_done = True self.tray.deleteLater() try: for k, v in current_app.extensions.items(): current_app.suspend_extension(k) current_app.release() except: pass if self.cfg_dialog: self.cfg_dialog.close() if self.log_dialog: self.log_dialog.close() if self.order_widget: self.order_widget.close() event.accept() else: self.tray.showMessage("ctpbee", "以最小化隐藏在托盘", msecs=1) self.hide() event.ignore()
class SystemTrayIcon(QObject): clicked = pyqtSignal() double_clicked = pyqtSignal() def __init__(self, parent, menu, is_logging=False): QObject.__init__(self) def getIcon(name): return QIcon(':/images/tray_icons/' + name + '.png') self._icons = { STATUS_INIT: getIcon("disconnected") if is_logging \ else getIcon("sync"), STATUS_DISCONNECTED: getIcon("disconnected"), STATUS_WAIT: getIcon("default"), STATUS_PAUSE: getIcon("pause"), STATUS_IN_WORK: getIcon("sync"), STATUS_INDEXING: getIcon("sync"), } self._statuses = { STATUS_INIT: tr("Pvtbox"), STATUS_DISCONNECTED: tr("Pvtbox connecting..."), STATUS_WAIT: tr("Pvtbox"), STATUS_PAUSE: tr("Pvtbox paused"), STATUS_IN_WORK: tr("Pvtbox syncing..."), STATUS_INDEXING: tr("Pvtbox indexing...") } self._tray = QSystemTrayIcon(self._icons[STATUS_INIT], parent) self.set_tool_tip(self._statuses[STATUS_INIT]) self._tray.setContextMenu(menu) menu.aboutToShow.connect(self.clicked.emit) self._tray.activated.connect(self._on_activated) self._tray.installEventFilter(self) self._tray.setVisible(True) self._tray.show() self._tray_show_timer = QTimer(self) self._tray_show_timer.setInterval(3000) self._tray_show_timer.setSingleShot(False) self._tray_show_timer.timeout.connect(self.show) def eventFilter(self, obj, ev): if ev.type() == QEvent.ToolTip: self.clicked.emit() return False def _on_activated(self, reason): ''' Slot for system tray icon activated signal. See http://doc.qt.io/qt-5/qsystemtrayicon.html @param reason Tray activation reason ''' if reason == QSystemTrayIcon.Trigger: # This is usually when left mouse button clicked on tray icon self.clicked.emit() elif reason == QSystemTrayIcon.DoubleClick: self.double_clicked.emit() @property def menu(self): return self._tray.contextMenu() def set_tool_tip(self, tip): self._tray.setToolTip(tip) def show_tray_notification(self, text, title=""): if not title: title = tr('Pvtbox') # Convert strings to unicode if type(text) in (str, str): text = ensure_unicode(text) if type(title) in (str, str): title = ensure_unicode(title) logger.info("show_tray_notification: %s, title: %s", text, title) if self._tray.supportsMessages(): self._tray.showMessage(title, text) else: logger.warning("tray does not supports messages") def request_to_user( self, dialog_id, text, buttons=("Yes", "No"), title="", close_button_index=-1, close_button_off=False, parent=None, on_clicked_cb=None, details=''): msg_box = QMessageBox(parent) # msg_box = QMessageBox() if not title: title = tr('Pvtbox') msg_box.setWindowTitle(title) msg_box.setText(str(text)) pvtboxIcon = QIcon(':/images/icon.png') msg_box.setWindowIcon(pvtboxIcon) if details: msg_box.setDetailedText(details) if close_button_off: if get_platform() == 'Darwin': msg_box.setWindowFlags(Qt.Tool) else: msg_box.setWindowFlags(Qt.Dialog | Qt.CustomizeWindowHint | Qt.WindowTitleHint) msg_box.setAttribute(Qt.WA_MacFrameworkScaled) msg_box.setModal(True) buttons = list(buttons) for button in buttons: msg_box.addButton(button, QMessageBox.ActionRole) msg_box.show() msg_box.raise_() msg_box.exec_() try: button_index = buttons.index(msg_box.clickedButton().text()) except (ValueError, AttributeError): button_index = -1 # message box was closed with close button if len(buttons) == 1 and close_button_index == -1: # if only one button then call callback close_button_index = 0 if len(buttons) > close_button_index >= 0: button_index = close_button_index if button_index >= 0 and callable(on_clicked_cb): on_clicked_cb(dialog_id, button_index) def update_status_icon(self, new_status, new_substatus): icon = self._icons[STATUS_DISCONNECTED] if new_status == STATUS_INIT \ else self._icons[new_status] self._tray.setIcon(icon) tool_tip = self._statuses[new_status] if new_status == STATUS_IN_WORK and new_substatus == SUBSTATUS_SHARE: tool_tip = tr("Pvtbox downloading share...") self.set_tool_tip(tool_tip) self.show() def show(self): self._tray.setVisible(True) self._tray.show() def hide(self): if self._tray_show_timer.isActive(): self._tray_show_timer.stop() self._tray.hide() def __del__(self): self._tray.removeEventFilter(self) self._tray.activated.disconnect() self.hide()
class MainWindow(Ui_Form): def __init__(self, parent=None): super(MainWindow, self).__init__(parent) self.appname = "poliBeePsync" self.settings_fname = 'pbs-settings.ini' self.data_fname = 'pbs.data' self.setupUi(self) self.w = QWidget() self.status_signal = MySignal() self.status_signal.sig.connect(self.update_status_bar) self.logging_signal = MySignal() self.logging_signal.sig.connect(self.myStream_message) logging_console_hdl = SignalLoggingHandler(self.logging_signal) logger.addHandler(logging_console_hdl) commonlogger.addHandler(logging_console_hdl) self.about_text() self.timer = QTimer(self) # settings_path is a string containing the path to settings self.settings_path = None # settings is a dictionary of settings self.settings = None # load_settings() sets settings_path and settings self.load_settings() self.load_data() self.timer.timeout.connect(self.syncfiles) self.timer.start(1000 * 60 * int(self.settings['UpdateEvery'])) self.loginthread = LoginThread(self.user, self) self.loginthread.signal_error.sig.connect(self.update_status_bar) self.loginthread.signal_ok.sig.connect(self.update_status_bar) self.refreshcoursesthread = RefreshCoursesThread(self.user, self) self.refreshcoursesthread.dumpuser.sig.connect(self.dumpUser) self.refreshcoursesthread.newcourses.sig.connect(self.addtocoursesview) self.refreshcoursesthread.newcourses.sig.connect(self.syncnewcourses) self.refreshcoursesthread.removable.sig.connect(self.rmfromcoursesview) self.downloadthread = DownloadThread(self.user, self.settings['RootFolder'], self) self.downloadthread.dumpuser.sig.connect(self.dumpUser) self.downloadthread.download_signal.connect( self.update_course_download) self.downloadthread.initial_sizes.connect(self.setinizialsizes) self.downloadthread.date_signal.connect(self.update_file_localtime) self._window.userCode.setText(str(self.user.username)) self._window.userCode.editingFinished.connect(self.setusercode) self._window.password.setText(self.user.password) self._window.password.editingFinished.connect(self.setpassword) self._window.trylogin.clicked.connect(self.testlogin) self._window.courses_model = CoursesListModel(self.user. available_courses) self._window.coursesView.setModel(self._window.courses_model) self._resizeview() self._window.refreshCourses.clicked.connect(self.refreshcourses) self._window.syncNow.clicked.connect(self.syncfiles) if self.settings['SyncNewCourses'] == str(True): self._window.sync_new = Qt.Checked else: self._window.sync_new = Qt.Unchecked self._window.rootfolder.setText(self.settings['RootFolder']) self._window.rootfolder.textChanged.connect(self.rootfolderslot) self._window.addSyncNewCourses.setCheckState(self._window.sync_new) self._window.addSyncNewCourses.stateChanged.connect(self.syncnewslot) self._window.timerMinutes.setValue(int(self.settings['UpdateEvery'])) self._window.timerMinutes.valueChanged.connect(self.updateminuteslot) self._window.changeRootFolder.clicked.connect(self.chooserootdir) self._window.version_label.setText("Current version: {}." .format(__version__)) self._window.check_version.clicked.connect(self.checknewversion) self.trayIconMenu = QMenu() self.trayIcon = QSystemTrayIcon(self.icon, self.w) self.trayIcon.activated.connect(self._activate_traymenu) self.createTray() @Slot() def _resizeview(self, **kwargs): self._window.coursesView.setColumnWidth(3, 160) self._window.coursesView.resizeColumnToContents(1) self._window.coursesView.setColumnWidth(0, 320) def checknewversion(self): rawdata = requests.get('https://pypi.python.org/pypi/' 'poliBeePsync/json') latest = json.loads(rawdata.text)['info']['version'] self._window.version_label.setTextFormat(Qt.RichText) self._window.version_label.setOpenExternalLinks(True) self._window.version_label.setLocale(QLocale(QLocale.English, QLocale.UnitedStates)) self._window.version_label.setScaledContents(True) self._window.version_label.setWordWrap(True) if latest != __version__: newtext = """<p>Current version: {}.<br> Latest version: {}. </p> <p>Visit <a href='https://jacotsu.github.io/polibeepsync/dirhtml/index.html\ #how-to-install-upgrade-remove'>here</a> to find out how to upgrade. """.format(__version__, latest) else: newtext = "Current version: {} up-to-date.".format(__version__) self._window.version_label.setText(newtext) def _update_time(self, folder, file, path_list): logger.debug(f'inside {folder.name}') for path in path_list: logger.debug(f'namegoto: {path}') folder_dict = {'name': path} fakefolder = Folder(folder_dict) logger.debug(f'contained folders: {folder.folders}') ind = folder.folders.index(fakefolder) goto = folder.folders[ind] self._update_time(goto, file, path_list) if file in folder.files: ind = folder.files.index(file) thisfile = folder.files[ind] thisfile.local_creation_time = file.local_creation_time @Slot(tuple) def update_file_localtime(self, data, **kwargs): course, coursefile, path = data rootpath = os.path.join(self.settings['RootFolder'], course.save_folder_name) if path.startswith(rootpath): partial = path[len(rootpath):] path_list = filter(None, partial.split(os.path.sep)) self._update_time(course.documents, coursefile, path_list) @Slot(Course) def update_course_download(self, course, **kwargs): if course in self.user.available_courses: updating = self.user.available_courses[course.name] updating.downloaded_size = course.downloaded_size row = self._window.courses_model.courses.index(updating) where = self._window.courses_model.index(row, 3) self._window.courses_model.dataChanged.emit(where, where) @Slot(Course) def setinizialsizes(self, course, **kwargs): if course in self.user.available_courses: updating = self.user.available_courses[course.name] updating.downloaded_size = course.downloaded_size updating.total_file_size = course.total_file_size row = self._window.courses_model.courses.index(updating) where = self._window.courses_model.index(row, 3) self._window.courses_model.dataChanged.emit(where, where) self.dumpUser() @Slot(list) def syncnewcourses(self, newlist): if self.settings['SyncNewCourses'] == 'True': for elem in newlist: elem.sync = True def load_settings(self): for path in [user_config_dir(self.appname), user_data_dir(self.appname)]: try: os.makedirs(path, exist_ok=True) except OSError: logger.critical('OSError while calling os.makedirs.', exc_info=True) logger.critical(f"I couldn't create {path}.\nStart" " poliBeePsync with --debug " "error to get more details.") self.settings_path = os.path.join(user_config_dir(self.appname), self.settings_fname) defaults = { 'UpdateEvery': '60', 'RootFolder': os.path.join(os.path.expanduser('~'), self.appname), 'SyncNewCourses': 'False' } self.settings = filesettings.settingsFromFile(self.settings_path, defaults) def load_data(self): try: with open(os.path.join(user_data_dir(self.appname), self.data_fname), 'rb') as f: self.user = pickle.load(f) self.user.password = keyring\ .get_password('beep.metid.polimi.it', self.user.username) logger.info("Data has been loaded successfully.") except (EOFError, pickle.PickleError): logger.error('Settings corrupted', exc_info=True) self.user = User('', '') except FileNotFoundError: logger.error('Settings file not found.') self.user = User('', '') logger.error("I couldn't find data in the" " predefined directory. Ignore this" "message if you're using poliBeePsync" " for the first time.") @Slot(str) def update_status_bar(self, status): self._window.statusbar.showMessage(status) @Slot(int) def syncnewslot(self, state): if state == 2: self.settings['SyncNewCourses'] = 'True' else: self.settings['SyncNewCourses'] = 'False' filesettings.settingsToFile(self.settings, self.settings_path) @Slot(int) def updateminuteslot(self, minutes): self.settings['UpdateEvery'] = str(minutes) filesettings.settingsToFile(self.settings, self.settings_path) self.timer.start(1000 * 60 * int(self.settings['UpdateEvery'])) @Slot(str) def rootfolderslot(self, path): self.settings['RootFolder'] = path filesettings.settingsToFile(self.settings, self.settings_path) @Slot() def chooserootdir(self): currentdir = self.settings['RootFolder'] flags = QFileDialog.DontResolveSymlinks | QFileDialog.ShowDirsOnly newroot = QFileDialog.getExistingDirectory(None, "Open Directory", currentdir, flags) if newroot != "" and str(newroot) != currentdir: self.settings['RootFolder'] = str(newroot) filesettings.settingsToFile(self.settings, self.settings_path) self._window.rootfolder.setText(newroot) # we delete the already present downloadthread and recreate it # because otherwise it uses the old download folder. I don't know # if there's a cleaner approach del self.downloadthread self.downloadthread = DownloadThread(self.user, self.settings['RootFolder'], self) self.downloadthread.dumpuser.sig.connect(self.dumpUser) self.dumpUser() @Slot() def setusercode(self): newcode = self._window.userCode.text() try: if len(newcode) == 8: self.user.username = newcode logger.info(f'User code changed to {newcode}.') keyring.set_password('beep.metid.polimi.it', self.user.username, self.user.password) except OSError: logger.critical("I couldn't save data to disk. Run" " poliBeePsync with option --debug" " error to get more details.") logger.error('OSError raised while trying to write the User' 'instance to disk.', exc_info=True) @Slot() def setpassword(self): newpass = self._window.password.text() self.user.password = newpass try: keyring.set_password('beep.metid.polimi.it', self.user.username, self.user.password) logger.info("Password changed.") except OSError: logger.critical("I couldn't save data to disk. Run" " poliBeePsync with option --debug" " error to get more details.") logger.error('OSError raised while trying to write the User' 'instance to disk.', exc_info=True) @Slot() def testlogin(self): if not self.loginthread.isRunning(): self.loginthread.exiting = False self.loginthread.start() self.status_signal.sig.emit("Logging in, please wait.") @Slot(list) def addtocoursesview(self, addlist): for elem in addlist: self._window.courses_model.insertRows(0, 1, elem) @Slot(list) def rmfromcoursesview(self, removelist): for elem in removelist: index = self._window.courses_model.courses.index(elem) self._window.courses_model.removeRows(index, 1) @Slot() def dumpUser(self): # we don't use the message... with open(os.path.join(user_data_dir(self.appname), self.data_fname), 'wb') as f: tmp_pw = self.user.password self.user.password = '' pickle.dump(self.user, f) self.user.password = tmp_pw @Slot() def refreshcourses(self): self.status_signal.sig.emit('Searching for online updates...' 'this may take a while.') if not self.loginthread.isRunning(): self.loginthread.exiting = False self.loginthread.signal_ok.sig.connect(self.do_refreshcourses) self.loginthread.start() def do_refreshcourses(self): self.loginthread.signal_ok.sig.disconnect(self.do_refreshcourses) if not self.refreshcoursesthread.isRunning(): self.refreshcoursesthread.start() @Slot() def syncfiles(self): # we delete the already present downloadthread and recreate it # because otherwise it uses the old download folder. I don't know # if there's a cleaner approach del self.downloadthread self.downloadthread = DownloadThread(self.user, self.settings['RootFolder'], self) self.downloadthread.dumpuser.sig.connect(self.dumpUser) self.refreshcoursesthread.finished.connect(self.do_syncfiles) self.refreshcourses() @Slot() def do_syncfiles(self): self.refreshcoursesthread.finished.disconnect(self.do_syncfiles) self.status_signal.sig.emit('Started syncing.') self.downloadthread.start() @Slot(str) def myStream_message(self, message): self._window.status.moveCursor(QTextCursor.End) self._window.status.insertPlainText(message + "\n") def restore_window(self): self._window.setWindowState(self.windowState() & ~Qt.WindowMinimized | Qt.WindowActive) self._window.show() def createTray(self): restoreAction = QAction("&Restore", self, triggered=self.restore_window) quitAction = QAction("&Quit", self, triggered=qApp.quit) self.trayIconMenu.addAction(restoreAction) self.trayIconMenu.addAction(quitAction) self.trayIcon.setContextMenu(self.trayIconMenu) self.trayIcon.show() @Slot(str) def _activate_traymenu(self, reason): if reason == QSystemTrayIcon.ActivationReason.DoubleClick: self.restore_window() else: self.trayIconMenu.activateWindow() self.trayIconMenu.popup(QCursor.pos()) def closeEvent(self, event): self._window.hide() event.ignore() def about_text(self): self._window.label_3 = QLabel() self._window.label_3.setTextFormat(Qt.RichText) self._window.label_3.setOpenExternalLinks(True) self._window.label_3.setLocale(QLocale(QLocale.English, QLocale.UnitedStates)) self._window.label_3.setScaledContents(True) self._window.label_3.setWordWrap(True) text = """ <html> <head/> <body> <p>poliBeePsync is a program written by Davide Olianas, released under GNU GPLv3+.</p> <p>Feel free to contact me at <a href=\"mailto:[email protected]\">[email protected]</a> for suggestions and bug reports.</p> <p>More information is available on the <a href=\"http://www.davideolianas.com/polibeepsync\"> <span style=\" text-decoration: underline; color:#0000ff;\"> official website</span></a>. </p> </body> </html> """ self._window.label_3.setText(QApplication.translate("Form", text, None))
class Chrono(QMainWindow): def __init__(self, parent=None): super(Chrono, self).__init__(parent) self.createMenus() self.createSystemTrayIcon() self.timer = QTimer(self) self.timer.timeout.connect(self.tick) self.isRunning = False self.refresh_rate = 100 # ms self.progressBar = QProgressBar() self.progressBar.setValue(0) self.begin_time = self.end_time = 0 self.label = QLabel(" ") self.button = QPushButton() self.button.setIcon(self.style().standardIcon(QStyle.SP_MediaPause)) self.end_delay = self.begin_delay = 0 bottomLayout = QHBoxLayout() bottomLayout.addWidget(self.progressBar) bottomLayout.addWidget(self.button) self.button.clicked.connect(self.pause) mainLayout = QVBoxLayout() mainLayout.addWidget(self.label) mainLayout.addLayout(bottomLayout) centralWidget = QWidget() centralWidget.setLayout(mainLayout) self.setCentralWidget(centralWidget) self.statusBar = QStatusBar(self) self.setStatusBar(self.statusBar) self.notification = self.notification_popup = self.notification_tray = self.notification_sound = True self.notification_soundfile = os.path.dirname( sys.argv[0]) + '/notification.mp3' # os.path.dirname(__file__) + self.setWindowTitle(TITLE) self.resize(400, self.sizeHint().height()) self.setFixedHeight(self.sizeHint().height()) def createMenus(self): menus = QMenuBar() fileMenu = menus.addMenu("&Fichier") file_newMenu = fileMenu.addMenu( self.style().standardIcon(QStyle.SP_FileIcon), "Nouveau") file_newMenu.addAction("Date", self.createDateDialog, 'CTRL+D') file_newMenu.addAction("Durée", self.createDurationDialog, 'CTRL+N') fileMenu.addSeparator() fileMenu.addAction(self.style().standardIcon(QStyle.SP_BrowserStop), "Quitter", sys.exit, 'CTRL+Q') optionMenu = menus.addMenu("&Options") optionMenu.addAction( self.style().standardIcon(QStyle.SP_MessageBoxInformation), "Évènements", self.createNotificationPopup, 'CTRL+O') optionMenu.addAction( QAction("Rester au premier plan", optionMenu, triggered=self.stayOnTop, checkable=True)) aideMenu = menus.addMenu("&Aide") aideMenu.addAction( self.style().standardIcon(QStyle.SP_DialogHelpButton), "À propos", lambda: QMessageBox.information( self, "À propos", TITLE + " " + str(VERSION)), 'CTRL+H') aideMenu.addSeparator() aideMenu.addAction( self.style().standardIcon(QStyle.SP_TitleBarMenuButton), "À propos de Qt", QApplication.aboutQt, 'CTRL+A') self.setMenuBar(menus) def createSystemTrayIcon(self): self.tray = QSystemTrayIcon() self.tray.setIcon(QIcon(os.path.dirname(sys.argv[0]) + '/icon.svg')) # os.path.dirname(__file__) + self.tray.setToolTip(TITLE) self.tray.show() systemTrayMenu = QMenu() pauseAction = QAction(self.style().standardIcon(QStyle.SP_MediaPause), "Pause / Reprendre", systemTrayMenu) pauseAction.triggered.connect(self.pause) systemTrayMenu.addAction(pauseAction) systemTrayMenu.addSeparator() systemTrayMenu.addAction( self.style().standardIcon(QStyle.SP_BrowserStop), "Quitter", sys.exit) self.tray.setContextMenu(systemTrayMenu) self.tray.activated.connect(self.show) def stayOnTop(self): self.setWindowFlags(self.windowFlags() ^ Qt.WindowStaysOnTopHint) # self.windowFlags() | Qt.CustomizeWindowHint | Qt.Window | Qt.WindowStaysOnTopHint | Qt.X11BypassWindowManagerHint) # Qt.Dialog | Qt.WindowStaysOnTopHint | Qt.X11BypassWindowManagerHint) self.show() def createNotificationPopup(self): popup = QDialog(self) popup.setFixedSize(popup.sizeHint().height(), popup.sizeHint().width()) popup.setWindowTitle("Évènements") innerLayout = QVBoxLayout() GroupBox = QGroupBox("Activer les notifications") GroupBox.setCheckable(True) GroupBox.setChecked(self.notification) checkBox_popup = QCheckBox("Afficher une popup") checkBox_notification = QCheckBox("Afficher une notification") checkBox_sound = QCheckBox("Jouer un son") if self.notification_popup: checkBox_popup.setCheckState(Qt.Checked) if self.notification_tray: checkBox_notification.setCheckState(Qt.Checked) if self.notification_sound: checkBox_sound.setCheckState(Qt.Checked) innerLayout.addWidget(checkBox_popup) innerLayout.addWidget(checkBox_notification) innerLayout.addWidget(checkBox_sound) innerLayout.addStretch(1) GroupBox.setLayout(innerLayout) button = QPushButton("Ok") button.clicked.connect(lambda: self.changeNotificationSettings( popup, GroupBox, checkBox_popup, checkBox_notification, checkBox_sound)) outerLayout = QVBoxLayout() outerLayout.addWidget(GroupBox) outerLayout.addWidget(button) popup.setLayout(outerLayout) popup.exec_() def changeNotificationSettings(self, popup, GroupBox, checkBox_popup, checkBox_notification, checkBox_sound): self.notification, self.notification_popup, self.notification_tray, self.notification_sound = GroupBox.isChecked( ), checkBox_popup.isChecked(), checkBox_notification.isChecked( ), checkBox_sound.isChecked() if not any([ self.notification_popup, self.notification_tray, self.notification_sound ]): self.notification = False popup.close() def createDateDialog(self): popup = QDialog(self) popup.setFixedSize(270, 60) popup.setWindowTitle("Nouvelle date") layout = QHBoxLayout() prefix = QLabel("Heure cible: ") layout.addWidget(prefix) qline = QTimeEdit() qline.setDisplayFormat("hh:mm:ss") qline.setTime(QTime.currentTime()) layout.addWidget(qline) button = QPushButton("Ok") button.clicked.connect(lambda: self.createDate(popup, qline.time().hour(), qline.time().minute(), qline.time().second())) layout.addWidget(button) popup.setLayout(layout) popup.exec_() def createDurationDialog(self): popup = QDialog(self) popup.setFixedSize(150, 150) popup.setWindowTitle("Nouvelle durée") layout = QVBoxLayout() hourLayout = QHBoxLayout() hourLabel = QLabel("Heures:") hourSpin = QSpinBox() hourLayout.addWidget(hourLabel) hourLayout.addWidget(hourSpin) minuteLayout = QHBoxLayout() minuteLabel = QLabel("Minutes:") minuteSpin = QSpinBox() minuteLayout.addWidget(minuteLabel) minuteLayout.addWidget(minuteSpin) secondLayout = QHBoxLayout() secondLabel = QLabel("Secondes:") secondSpin = QSpinBox() secondLayout.addWidget(secondLabel) secondLayout.addWidget(secondSpin) layout.addLayout(hourLayout) layout.addLayout(minuteLayout) layout.addLayout(secondLayout) button = QPushButton("Ok") button.clicked.connect(lambda: self.createDuration( popup, hourSpin.value(), minuteSpin.value(), secondSpin.value())) layout.addWidget(button) popup.setLayout(layout) popup.exec_() def createDuration(self, popup: QDialog, hours: int, minutes: int, seconds: int): popup.close() self.begin_time = datetime.timestamp(datetime.now()) self.end_time = self.begin_time + seconds + minutes * 60 + hours * 3600 self.progressBar.setRange(0, 100) self.progressBar.setValue(0) self.isRunning = True self.timer.stop() self.timer.start(self.refresh_rate) def createDate(self, popup: QDialog, hours: int, minutes: int, seconds: int): popup.close() self.begin_time = datetime.timestamp(datetime.now()) now = datetime.now().time() target = time(hours, minutes, seconds) now_delta = timedelta(hours=now.hour, minutes=now.minute, seconds=now.second) target_delta = timedelta(hours=target.hour, minutes=target.minute, seconds=target.second) if target_delta == now_delta: self.end_time = self.begin_time + 60 * 60 * 24 else: d = target_delta - now_delta self.end_time = self.begin_time + d.seconds self.progressBar.setRange(0, 100) self.progressBar.setValue(0) self.isRunning = True self.timer.stop() self.timer.start(self.refresh_rate) def tick(self): self.progressBar.setValue( 100 * (datetime.timestamp(datetime.now()) - self.begin_time) / (self.end_time - self.begin_time)) seconds = int( ceil(self.end_time - datetime.timestamp(datetime.now())) % 60) minutes = int( ceil(self.end_time - datetime.timestamp(datetime.now())) / 60 % 60) hours = int( ceil(self.end_time - datetime.timestamp(datetime.now())) / 3600) self.label.setText(f'{hours:02}:{minutes:02}:{seconds:02}') self.setWindowTitle(f'{TITLE} - {hours:02}:{minutes:02}:{seconds:02}') self.tray.setToolTip(f'{hours:02}:{minutes:02}:{seconds:02}') if datetime.timestamp(datetime.now()) >= self.end_time: self.isRunning = False self.timer.stop() self.progressBar.setRange(0, 0) self.show() self.notify() def notify(self): if not self.notification: return if self.notification_tray: self.tray.showMessage( "Finished", "Le décompte est terminé", self.style().standardIcon(QStyle.SP_MessageBoxInformation)) if self.notification_sound: test = QMediaPlayer() test.setMedia(QUrl.fromLocalFile(self.notification_soundfile)) test.play() if self.notification_popup: QMessageBox.information(self, "Finished", "Le décompte est terminé") def pause(self): if not self.isRunning: return self.progressBar.setDisabled(self.timer.isActive()) if self.timer.isActive(): self.end_delay = self.end_time - datetime.timestamp(datetime.now()) self.begin_delay = datetime.timestamp( datetime.now()) - self.begin_time print(self.begin_time) print(self.end_time) print(self.end_delay) self.statusBar.showMessage("Pause") self.tray.setToolTip(self.tray.toolTip() + ' - Pause') self.timer.stop() self.button.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay)) else: self.begin_time = datetime.timestamp( datetime.now()) - self.begin_delay self.end_time = datetime.timestamp(datetime.now()) + self.end_delay print(self.begin_time) print(self.end_time) self.statusBar.clearMessage() self.timer.start() self.button.setIcon(self.style().standardIcon( QStyle.SP_MediaPause)) # Override def closeEvent(self, event): self.hide() event.ignore()
class MainWindow(QObject): def __init__(self, parent=None): """Main window, holding all user interface including. Args: parent: parent class of main window Returns: None Raises: None """ super(MainWindow, self).__init__() self._window = None self._old_pos = None self._tray = QSystemTrayIcon(self._window) if self._tray.isSystemTrayAvailable(): self._tray.setIcon(QIcon('./media/dekban.png')) else: self._tray = None self.setup_ui() @property def window(self): """The main window object""" return self._window def setup_ui(self): loader = QUiLoader() file = QFile('./main_window.ui') file.open(QFile.ReadOnly) self._window = loader.load(file) file.close() self._window.setWindowFlags(Qt.Window | Qt.FramelessWindowHint) self._window.installEventFilter(self) self.center() self._old_pos = self._window.pos() self.set_title() self.set_buttons() self.set_edits() self.set_icon_combo() self.set_tray() self._tray.show() def set_title(self): """Setup label""" self._window.title.setText('Welcome to PySide2 Tutorial') font = QFont("Arial", 20, QFont.Black) self._window.title.setFont(font) # set widget size (x, y, width, height) self._window.title.setGeometry(0, 0, 600, 30) # set alignment self._window.title.setAlignment(Qt.AlignBottom | Qt.AlignCenter) def set_buttons(self): """Setup buttons""" self._window.send_btn.setText('Send Msg') self._window.exit_btn.setText('Exit') self._window.send_btn.setIcon(QIcon('./media/import.svg')) self._window.send_btn.clicked.connect(self.send_message) self._window.exit_btn.clicked.connect(self.exit) def set_edits(self): """Setup line edit and text edit""" self._window.title_line.setPlaceholderText('Input Msg Title') self._window.msg_edit.setPlaceholderText('Input Msg') def set_icon_combo(self): """Setup options in icon select combobox.""" self._window.icon_combo.addItem(QIcon('./media/font.png'), 'font') self._window.icon_combo.addItem(QIcon('./media/paint.png'), 'paint') self._window.icon_combo.addItem(QIcon('./media/dekban.png'), 'default') self._window.icon_combo.currentIndexChanged.connect(self.set_icon) def set_tray(self): menu = QMenu(self._window) action_show = menu.addAction("Show/Hide") action_show.triggered.connect( lambda: self._window.hide() if self._window.isVisible() else self._window.show()) action_quit = menu.addAction("Quit") action_quit.triggered.connect(self._window.close) self._tray.setContextMenu(menu) def eventFilter(self, obj, event): if obj is self._window: if event.type() == QtCore.QEvent.MouseButtonPress: self.mouse_press_event(event) elif event.type() == QtCore.QEvent.MouseMove: self.mouse_move_event(event) return super(MainWindow, self).eventFilter(obj, event) def center(self): qr = self._window.frameGeometry() cp = QDesktopWidget().availableGeometry().center() qr.moveCenter(cp) self._window.move(qr.topLeft()) def mouse_press_event(self, event): self._old_pos = event.globalPos() def mouse_move_event(self, event): delta_x = int(event.globalPos().x()) - self._old_pos.x() delta_y = int(event.globalPos().y()) - self._old_pos.y() self._window.move(self._window.x() + delta_x, self._window.y() + delta_y) self._old_pos = event.globalPos() @QtCore.Slot(int) def set_icon(self, index): icon = self._window.icon_combo.itemIcon(index) self._tray.setIcon(icon) @QtCore.Slot() def send_message(self): title = self._window.title_line.text() msg = self._window.msg_edit.toPlainText() self._tray.showMessage(title, msg, QIcon('./media/dekban.png')) @QtCore.Slot() def exit(self): self._window.close()
class MainWindow(QMainWindow, Ui_MainWindow): def __init__(self): super(self.__class__, self).__init__() self.quit_ = False self.setupUi(self) self.setWindowFlags(Qt.WindowStaysOnTopHint) self.setStyleSheet(qss) self.job = Job() self.layout_init() self.tray_init() self.job.msg_box_signal.connect(self.msg_box_slot) # 主界面 self.initial = InitialWidget(self) self.setCentralWidget(self.initial) @Slot(dict) def msg_box_slot(self, data): """消息弹窗插槽""" QMessageBox.information(self, "bee box", data['msg'], QMessageBox.Ok, QMessageBox.Ok) def layout_init(self): # self.setWindowFlags(Qt.FramelessWindowHint) # 隐藏整个头部 self.desktop = QDesktopWidget() taskbar_height = self.desktop.screenGeometry().height() - self.desktop.availableGeometry().height() # 任务栏高度 self.move((self.desktop.availableGeometry().width() - self.width() - 10), self.desktop.availableGeometry().height() - self.height() - taskbar_height) # 初始化位置到右下角 """边缘圆角""" # self.bmp = QBitmap(self.size()) # self.bmp.fill() # self.ppp = QPainter(self.bmp) # self.ppp.setPen(Qt.black) # self.ppp.setBrush(Qt.black) # self.ppp.drawRoundedRect(self.bmp.rect(), 10, 10) # self.ppp.end() # self.setMask(self.bmp) def tray_init(self): icon = QIcon(":/icon/icon/CTPBEE.png") menu = QMenu() exitAction = menu.addAction("❎ 退出") exitAction.triggered.connect(self.quit_action) self.tray = QSystemTrayIcon() self.tray.setIcon(icon) self.tray.setContextMenu(menu) self.tray.activated.connect(self.iconActivated) self.tray.show() self.tray.setToolTip("bee box") def home_handler(self): self.widget = HomeWidget(self) self.setCentralWidget(self.widget) def iconActivated(self, reason): """托盘点击插槽""" if reason is QSystemTrayIcon.Trigger: self.show() self.raise_() def quit_action(self): self.quit_ = True self.close() def closeEvent(self, event: QCloseEvent): if self.quit_: G.pool_done = True try: self.widget.close() except: pass event.accept() else: self.hide() event.ignore()
class GUI: def __init__(self, root): self.root = root self.app = QApplication([]) self.icons = { "timelapse": QIcon(resource_path('icons/timelapse.png')), "sync": QIcon(resource_path('icons/sync.png')), "sync_disabled": QIcon(resource_path('icons/sync_disabled.png')), "logo": QIcon(resource_path('icons/logo.png')), "settings": QIcon(resource_path('icons/settings.png')), "github": QIcon(resource_path('icons/github.png')) } self.main_window = MainWindow(self) self.offset_window = None self.settings_window = None self.messages = [[], []] self.section_labels = [ self.main_window.ui.right_status, self.main_window.ui.right_status_2 ] menu = QMenu('FS Time Sync') menu.setStyleSheet(""" QMenu { background-color: #151515; color: #ffffff; } QMenu::item { padding: 5px 10px 5px 10px; } QMenu::item:selected { background-color: #ffffff; color: #151515; } """) self.tray_actions = {} self.tray_actions["sync_now"] = menu.addAction("Sync Now") self.tray_actions["sync_now"].triggered.connect( lambda: self.root.sync_sim(force=True)) self.tray_actions["hide_show"] = menu.addAction("Hide") self.tray_actions["hide_show"].triggered.connect(self.hide) self.tray_actions["exit"] = menu.addAction("Exit") self.tray_actions["exit"].triggered.connect(self.exit) self.tray = QSystemTrayIcon() self.tray.setIcon(self.icons['logo']) self.tray.setToolTip("FS Time Sync") self.tray.setContextMenu(menu) self.tray.activated.connect(self.trayActivated) self.tray.show() def main_window_act(self, func, *args, **kwargs): self.main_window.act.emit([func, args, kwargs]) def hide(self): self.tray_actions["hide_show"].setText("Show") self.tray_actions["hide_show"].triggered.connect(self.show) if self.offset_window: self.offset_window.close() self.main_window.saveState() self.main_window.hide() def trayActivated(self, reason): if reason == self.tray.ActivationReason.Trigger: self.show() def single_instance_triggered(self): self.tray.showMessage("FS Time Sync", "FS Time Sync is already running.") self.show() def show(self): self.tray_actions["hide_show"].setText("Hide") self.tray_actions["hide_show"].triggered.connect(self.hide) self.main_window.show() # Brings window forward, but doesn't force it to stay active. self.main_window.setWindowState(Qt.WindowActive) self.main_window.setWindowState(Qt.WindowNoState) def exit(self): self.app.quit() def start(self): # Add single instance trigger. self.root.si.add_trigger(self.single_instance_triggered) # Settings are triggered here. if not self.root.settings.get( "startup", "tray"): # Start regularly or as tray icon self.main_window.show() else: self.tray.showMessage("FS Time Sync", "FS Time Sync Started in Tray.") self.tray_actions["hide_show"].setText("Show") self.tray_actions["hide_show"].triggered.connect(self.show) if self.root.settings.get("startup", "auto_sync"): self.root.enable_live_sync() self.app.exec_() def add_message(self, section, code, msg): for message in self.messages[section]: if message["code"] == code: message["msg"] = msg break else: self.messages[section].append({"code": code, "msg": msg}) self.main_window.act.emit([self.section_labels[section].setText, msg]) def remove_message(self, section, code): for message in self.messages[section]: if message["code"] == code: self.messages[section].remove(message) break else: return # No action just return if len(self.messages[section]) > 0: print(self.messages[section]) self.main_window.act.emit([ self.section_labels[section].setText, self.messages[section][-1]["msg"] ]) else: self.main_window.act.emit( [self.section_labels[section].setText, ""])
class MainWindow(QMainWindow, Ui_MainWindow): def __init__(self): super(MainWindow, self).__init__() self.green_color = None self.yellow_color = None self.red_color = None self.tray = QSystemTrayIcon(self) self.connected_port = None self.config_filename = None self.base_output_filename = None self.output_hex_filename = None self.output_binary_filename = None self.port_read_thread = PortReadThread(self.read_port, self.stop_read) self.log = Logger().create_log() self.log.info("Launched MinXSS Beacon Decoder.") self.setup_ui() self.connect_ui_to_functions() self.setup_output_files() QApplication.instance().aboutToQuit.connect(self.prepare_to_exit) self.show() def setup_ui(self): self.setupUi(self) self.setup_colors() self.setup_tray_icon() self.setup_available_ports() self.setup_last_used_settings() def setup_colors(self): green = QColor(55, 195, 58) palette_green = QtGui.QPalette() palette_green.setColor(QtGui.QPalette.Text, green) palette_green.setColor(QtGui.QPalette.Foreground, green) self.green_color = palette_green yellow = QColor(244, 212, 66) palette_yellow = QtGui.QPalette() palette_yellow.setColor(QtGui.QPalette.Text, yellow) palette_yellow.setColor(QtGui.QPalette.Foreground, yellow) self.yellow_color = palette_yellow red = QColor(242, 86, 77) palette_red = QtGui.QPalette() palette_red.setColor(QtGui.QPalette.Text, red) palette_red.setColor(QtGui.QPalette.Foreground, red) self.red_color = palette_red def setup_tray_icon(self): if self.tray.isSystemTrayAvailable(): self.tray.setIcon(QIcon('assets/icon_256.png')) menu = QMenu() action_quit = menu.addAction("Quit") action_quit.triggered.connect(self.close) self.tray.setContextMenu(menu) self.tray.show() else: self.tray = None def setup_available_ports(self): """ Determine what ports are available for serial reading and populate the combo box with these options """ self.comboBox_serialPort.clear() list_port_info_objects = list_ports.comports() port_names = [x[0] for x in list_port_info_objects] self.comboBox_serialPort.addItems(port_names) def connect_ui_to_functions(self): self.actionConnect.triggered.connect(self.connect_clicked) self.checkBox_saveData.stateChanged.connect(self.save_data_toggled) self.checkBox_forwardData.stateChanged.connect(self.forward_data_toggled) self.checkBox_decodeKiss.stateChanged.connect(self.decode_kiss_toggled) self.lineEdit_callsign.editingFinished.connect(self.ground_station_config_changed) self.lineEdit_latitude.editingFinished.connect(self.ground_station_config_changed) self.lineEdit_longitude.editingFinished.connect(self.ground_station_config_changed) self.actionCompletePass.triggered.connect(self.complete_pass_clicked) def ground_station_config_changed(self): self.setup_output_files() self.write_gui_config_options_to_config_file() def setup_output_files(self): self.set_base_output_filename() self.setup_output_file_decoded_data_as_hex() self.setup_output_file_decoded_data_as_binary() def set_base_output_filename(self): callsign = self.lineEdit_callsign.text() latitude = self.lineEdit_latitude.text() longitude = self.lineEdit_longitude.text() self.base_output_filename = os.path.join(os.path.expanduser("~"), "MinXSS_Beacon_Decoder", "output", datetime.datetime.utcnow().isoformat().replace(':', '_')) + '_' + callsign + '_' + latitude + '_' + longitude def setup_output_file_decoded_data_as_hex(self): self.ensure_output_folder_exists() self.set_output_hex_filename() with open(self.output_hex_filename, 'w') as output_hex_file: self.log.info("Opening new .txt file to output decoded data as hex.") self.display_gui_output_hex_is_saving() output_hex_file.close() # Will later append to file as needed @staticmethod def ensure_output_folder_exists(): if not os.path.exists(os.path.join(os.path.expanduser("~"), "MinXSS_Beacon_Decoder", "output")): os.makedirs(os.path.join(os.path.expanduser("~"), "MinXSS_Beacon_Decoder", "output")) def set_output_hex_filename(self): self.output_hex_filename = self.base_output_filename + ".txt" def display_gui_output_hex_is_saving(self): self.textBrowser_savingDataToFile.setText("Saving data to files: {} and .dat.".format(self.output_hex_filename)) self.textBrowser_savingDataToFile.setPalette(self.green_color) def setup_output_file_decoded_data_as_binary(self): self.ensure_output_folder_exists() self.set_output_binary_filename() with open(self.output_binary_filename, 'w') as buffer_output_binary_file: self.log.info("Opening new binary file to output decoded data.") buffer_output_binary_file.close() def set_output_binary_filename(self): self.output_binary_filename = self.base_output_filename + ".dat" def write_gui_config_options_to_config_file(self): config = configparser.ConfigParser() config.read(self.config_filename) config.set('input_properties', 'serial_port', self.comboBox_serialPort.currentText()) config.set('input_properties', 'baud_rate', self.lineEdit_baudRate.text()) config.set('input_properties', 'ip_address', self.lineEdit_ipAddress.text()) config.set('input_properties', 'port', self.lineEdit_ipPort.text()) config.set('input_properties', 'decode_kiss', str(self.checkBox_decodeKiss.isChecked())) config.set('input_properties', 'forward_data', str(self.checkBox_forwardData.isChecked())) config.set('input_properties', 'callsign', self.lineEdit_callsign.text()) config.set('input_properties', 'latitude', self.lineEdit_latitude.text()) config.set('input_properties', 'longitude', self.lineEdit_longitude.text()) with open(os.path.join(os.path.expanduser("~"), "MinXSS_Beacon_Decoder", "input_properties.cfg"), 'w') as configfile: config.write(configfile) self.log.info('Updated input_properties.cfg file with new settings.') def setup_last_used_settings(self): config = configparser.ConfigParser() self.config_filename = os.path.join(os.path.expanduser("~"), "MinXSS_Beacon_Decoder", "input_properties.cfg") if self.need_new_config_file(config): self.log.info('No input_properties.cfg file found. Creating the default one.') self.write_default_config() config.read(self.config_filename) self.set_instance_variables_from_config(config) def need_new_config_file(self, config): if not os.path.isfile(self.config_filename): return True if os.stat(self.config_filename).st_size == 0: return True try: config.read(self.config_filename) except configparser.MissingSectionHeaderError: return True if not config.has_option('input_properties', 'serial_port'): return True if not config.has_option('input_properties', 'baud_rate'): return True if not config.has_option('input_properties', 'ip_address'): return True if not config.has_option('input_properties', 'port'): return True if not config.has_option('input_properties', 'decode_kiss'): return True if not config.has_option('input_properties', 'forward_data'): return True if not config.has_option('input_properties', 'callsign'): return True if not config.has_option('input_properties', 'latitude'): return True if not config.has_option('input_properties', 'longitude'): return True def write_default_config(self): with open(self.config_filename, "w") as config_file: print("[input_properties]", file=config_file) print("serial_port = 3", file=config_file) print("baud_rate = 19200", file=config_file) print("ip_address = localhost", file=config_file) print("port = 10000", file=config_file) print("decode_kiss = True", file=config_file) print("forward_data = True", file=config_file) print("callsign = SFJPM86", file=config_file) print("latitude = 40.240", file=config_file) print("longitude = -105.2353", file=config_file) def set_instance_variables_from_config(self, config): self.tabWidget_serialIp.setCurrentIndex(1) # TODO: Make this an actual config parameter self.comboBox_serialPort.insertItem(0, config.get('input_properties', 'serial_port')) self.comboBox_serialPort.setCurrentIndex(0) self.lineEdit_baudRate.setText(config.get('input_properties', 'baud_rate')) self.lineEdit_ipAddress.setText(config.get('input_properties', 'ip_address')) self.lineEdit_ipPort.setText(config.get('input_properties', 'port')) self.lineEdit_callsign.setText(config.get('input_properties', 'callsign')) self.lineEdit_latitude.setText(config.get('input_properties', 'latitude')) self.lineEdit_longitude.setText(config.get('input_properties', 'longitude')) self.checkBox_decodeKiss.setChecked(self.str2bool(config.get('input_properties', 'decode_kiss'))) self.checkBox_forwardData.setChecked(self.str2bool(config.get('input_properties', 'forward_data'))) # Intentionally don't do saveData -- always defaults to on to avoid frustration of lost data @staticmethod def str2bool(bool_string): if bool_string == 'True': return True if bool_string == 'False': return False raise ValueError('Can only accept exact strings "True" or "False". Was passed {}'.format(bool_string)) def connect_clicked(self): self.write_gui_config_options_to_config_file() connect_button_text = str(self.actionConnect.iconText()) if connect_button_text == "Connect": self.connect_to_port() else: self.disconnect_from_port() def connect_to_port(self): self.log.info("Attempting to connect to port.") self.toggle_connect_button(is_currently_connect=True) if self.user_chose_serial_port(): self.connected_port, port_readable = self.connect_to_serial_port() else: # user chose TCP/IP socket self.connected_port, port_readable = self.connect_to_socket_port() if port_readable: self.port_read_thread.start() self.display_gui_reading() else: self.display_gui_read_failed() def toggle_connect_button(self, is_currently_connect): if is_currently_connect: connect_button_text = 'Disconnect' else: connect_button_text = 'Connect' self.actionConnect.setText(QApplication.translate("MainWindow", connect_button_text, None, -1)) def user_chose_serial_port(self): if self.tabWidget_serialIp.currentIndex() == self.tabWidget_serialIp.indexOf(self.serial): return True else: return False # implying user chose TCP/IP socket def connect_to_serial_port(self): port = self.comboBox_serialPort.currentText() baud_rate = self.lineEdit_baudRate.text() connect_serial = connect_port_get_packet.ConnectSerial(port, baud_rate, self.log) connected_port = connect_serial.connect_to_port() port_readable = connected_port.port_readable return connected_port, port_readable def connect_to_socket_port(self): ip_address = self.lineEdit_ipAddress.text() port = self.lineEdit_ipPort.text() connect_socket = connect_port_get_packet.ConnectSocket(ip_address, port) connected_port = connect_socket.connect_to_port() port_readable = connect_socket.port_readable return connected_port, port_readable def display_gui_reading(self): reading = QApplication.translate("MainWindow", "Reading", None, -1) if self.user_chose_serial_port(): self.label_serialStatus.setText(reading) self.label_serialStatus.setPalette(self.green_color) else: self.label_socketStatus.setText(reading) self.label_socketStatus.setPalette(self.green_color) def display_gui_read_failed(self): read_failed = QApplication.translate("MainWindow", "Read failed", None, -1) if self.user_chose_serial_port: self.label_serialStatus.setText(read_failed) self.label_serialStatus.setPalette(self.red_color) else: self.label_socketStatus.setText(read_failed) self.label_socketStatus.setPalette(self.red_color) def disconnect_from_port(self): self.log.info("Attempting to disconnect from port.") self.toggle_connect_button(is_currently_connect=False) self.display_gui_port_closed() self.stop_read() def display_gui_port_closed(self): port_closed = QApplication.translate("MainWindow", "Port closed", None, -1) if self.user_chose_serial_port(): self.label_serialStatus.setText(port_closed) self.label_serialStatus.setPalette(self.red_color) else: self.label_socketStatus.setText(port_closed) self.label_socketStatus.setPalette(self.red_color) def complete_pass_clicked(self): self.upload_data() def upload_data(self): if self.do_forward_data(): self.display_gui_uploading() file_upload.upload(self.output_binary_filename) self.display_gui_upload_complete() def do_forward_data(self): return self.checkBox_forwardData.isChecked() def display_gui_uploading(self): self.label_uploadStatus.setText("Upload status: Uploading") def display_gui_upload_complete(self): self.label_uploadStatus.setText("Upload status: Complete") def read_port(self): while True: buffer_data = self.connected_port.read_packet() if len(buffer_data) == 0: continue buffer_data = self.decode_kiss(buffer_data) buffer_data_hex_string = self.convert_buffer_data_to_hex_string(buffer_data) self.display_gui_hex(buffer_data_hex_string) self.save_data_to_disk(buffer_data_hex_string, buffer_data) minxss_parser = MinxssParser(buffer_data) telemetry = minxss_parser.parse_packet() self.display_gui_telemetry(telemetry) def decode_kiss(self, buffer_data): if self.do_decode_kiss(): # C0 is a special KISS character that get replaced; unreplace it buffer_data = buffer_data.replace(bytearray([0xdb, 0xdc]), bytearray([0xc0])) # DB is a special KISS character that get replaced; unreplace it buffer_data = buffer_data.replace(bytearray([0xdb, 0xdd]), bytearray([0xdb])) return buffer_data def do_decode_kiss(self): return self.checkBox_decodeKiss.isChecked() @staticmethod def convert_buffer_data_to_hex_string(buffer_data): return ' '.join('0x{:02x}'.format(x) for x in buffer_data) def display_gui_hex(self, buffer_data_hex_string): self.textBrowser_serialOutput.append(buffer_data_hex_string) scroll_to_bottom = self.textBrowser_serialOutput.verticalScrollBar().maximum() self.textBrowser_serialOutput.verticalScrollBar().setValue(scroll_to_bottom) def save_data_to_disk(self, buffer_data_hex_string, buffer_data): if self.do_save_data(): output_hex_file = open(self.output_hex_filename, 'a') output_hex_file.write(buffer_data_hex_string) output_hex_file.close() output_binary_file = open(self.output_binary_filename, 'ab') output_binary_file.write(buffer_data) output_binary_file.close() def do_save_data(self): return self.checkBox_saveData.isChecked() def display_gui_telemetry(self, telemetry): if not telemetry: return self.label_lastPacketTime.setText( "Last packet at: {} local / {} UTC".format(self.get_local_time(), self.get_utc_time())) self.display_gui_telemetry_spacecraft_state(telemetry) self.display_gui_telemetry_solar_data(telemetry) self.display_gui_telemetry_power(telemetry) self.display_gui_telemetry_temperature(telemetry) self.label_spacecraftMode.setPalette(self.green_color) self.color_code_telemetry(telemetry) @staticmethod def get_local_time(): local_time = datetime.datetime.now().replace(microsecond=0).isoformat(' ') return local_time @staticmethod def get_utc_time(): utc_time = datetime.datetime.utcnow().replace(microsecond=0).isoformat(' ') return utc_time def display_gui_telemetry_spacecraft_state(self, telemetry): self.label_flightModel.setText("{0:0=1d}".format(telemetry['FlightModel'])) self.label_commandAcceptCount.setText("{0:0=1d}".format(telemetry['CommandAcceptCount'])) if telemetry['SpacecraftMode'] == 0: self.label_spacecraftMode.setText("Unknown") elif telemetry['SpacecraftMode'] == 1: self.label_spacecraftMode.setText("Phoenix") elif telemetry['SpacecraftMode'] == 2: self.label_spacecraftMode.setText("Safe") elif telemetry['SpacecraftMode'] == 4: self.label_spacecraftMode.setText("Science") if telemetry['PointingMode'] == 0: self.label_pointingMode.setText("Coarse Point") elif telemetry['PointingMode'] == 1: self.label_pointingMode.setText("Fine Point") if telemetry['EnableX123'] == 1: self.label_enableX123.setText("Yes") else: self.label_enableX123.setText("No") if telemetry['EnableSps'] == 1: self.label_enableSps.setText("Yes") else: self.label_enableSps.setText("No") if telemetry['Eclipse'] == 1: self.label_eclipse.setText("Eclipse") else: self.label_eclipse.setText("In Sun") def display_gui_telemetry_solar_data(self, telemetry): self.label_spsX.setText("{0:.2f}".format(round(telemetry['SpsX'], 2))) self.label_spsY.setText("{0:.2f}".format(round(telemetry['SpsY'], 2))) self.label_xp.setText("{0:.2f}".format(round(telemetry['Xp'], 2))) def display_gui_telemetry_power(self, telemetry): self.label_batteryVoltage.setText("{0:.2f}".format(round(telemetry['BatteryVoltage'], 2))) battery_current = self.get_battery_current(telemetry) self.label_batteryCurrent.setText("{0:.2f}".format(round(battery_current, 2))) solar_panel_minus_y_power = telemetry['SolarPanelMinusYVoltage'] * telemetry['SolarPanelMinusYCurrent'] / 1e3 solar_panel_plus_x_power = telemetry['SolarPanelPlusXVoltage'] * telemetry['SolarPanelPlusXCurrent'] / 1e3 solar_panel_plus_y_power = telemetry['SolarPanelPlusYVoltage'] * telemetry['SolarPanelPlusYCurrent'] / 1e3 self.label_solarPanelMinusYPower.setText("{0:.2f}".format(round(solar_panel_minus_y_power, 2))) self.label_solarPanelPlusXPower.setText("{0:.2f}".format(round(solar_panel_plus_x_power, 2))) self.label_solarPanelPlusYPower.setText("{0:.2f}".format(round(solar_panel_plus_y_power, 2))) def get_battery_current(self, telemetry): if telemetry['BatteryChargeCurrent'] > telemetry['BatteryDischargeCurrent']: battery_current = telemetry['BatteryChargeCurrent'] / 1e3 self.label_batteryCurrentText.setText("Battery Charge Current") else: battery_current = telemetry['BatteryDischargeCurrent'] / 1e3 self.label_batteryCurrentText.setText("Battery Discharge Current") return battery_current def display_gui_telemetry_temperature(self, telemetry): self.label_commBoardTemperature.setText("{0:.2f}".format(round(telemetry['CommBoardTemperature'], 2))) self.label_batteryTemperature.setText("{0:.2f}".format(round(telemetry['BatteryTemperature'], 2))) self.label_epsBoardTemperature.setText("{0:.2f}".format(round(telemetry['EpsBoardTemperature'], 2))) self.label_cdhTemperature.setText("{0:.2f}".format(round(telemetry['CdhBoardTemperature'], 2))) self.label_motherboardTemperature.setText("{0:.2f}".format(round(telemetry['MotherboardTemperature'], 2))) self.label_solarPanelMinusYTemperature.setText("{0:.2f}".format(round(telemetry['SolarPanelMinusYTemperature'], 2))) self.label_solarPanelPlusXTemperature.setText("{0:.2f}".format(round(telemetry['SolarPanelPlusXTemperature'], 2))) self.label_solarPanelPlusYTemperature.setText("{0:.2f}".format(round(telemetry['SolarPanelPlusYTemperature'], 2))) def color_code_telemetry(self, telemetry): self.color_code_spacecraft_state(telemetry) self.color_code_solar_data(telemetry) self.color_code_power(telemetry) self.color_code_temperature(telemetry) def color_code_spacecraft_state(self, telemetry): if telemetry['SpacecraftMode'] == 0: self.label_spacecraftMode.setPalette(self.red_color) elif telemetry['SpacecraftMode'] == 1: self.label_spacecraftMode.setPalette(self.red_color) elif telemetry['SpacecraftMode'] == 2: self.label_spacecraftMode.setPalette(self.yellow_color) elif telemetry['SpacecraftMode'] == 4: self.label_spacecraftMode.setPalette(self.green_color) if telemetry['PointingMode'] == 0: self.label_pointingMode.setPalette(self.yellow_color) elif telemetry['PointingMode'] == 1: self.label_pointingMode.setPalette(self.green_color) def color_code_solar_data(self, telemetry): if abs(telemetry['SpsX']) <= 3.0: self.label_spsX.setPalette(self.green_color) else: self.label_spsX.setPalette(self.red_color) if abs(telemetry['SpsY']) <= 3.0: self.label_spsY.setPalette(self.green_color) else: self.label_spsY.setPalette(self.red_color) if 0 <= telemetry['Xp'] <= 24860.0: self.label_xp.setPalette(self.green_color) else: self.label_xp.setPalette(self.red_color) def color_code_power(self, telemetry): solar_panel_minus_y_power = telemetry['SolarPanelMinusYVoltage'] * telemetry['SolarPanelMinusYCurrent'] / 1e3 solar_panel_plus_x_power = telemetry['SolarPanelPlusXVoltage'] * telemetry['SolarPanelPlusXCurrent'] / 1e3 solar_panel_plus_y_power = telemetry['SolarPanelPlusYVoltage'] * telemetry['SolarPanelPlusYCurrent'] / 1e3 battery_current = self.get_battery_current(telemetry) if -1.0 <= solar_panel_minus_y_power <= 10.4: self.label_solarPanelMinusYPower.setPalette(self.green_color) else: self.label_solarPanelMinusYPower.setPalette(self.red_color) if -1.0 <= solar_panel_plus_x_power <= 5.9: self.label_solarPanelPlusXPower.setPalette(self.green_color) else: self.label_solarPanelPlusXPower.setPalette(self.red_color) if -1.0 <= solar_panel_plus_y_power <= 10.4: self.label_solarPanelPlusYPower.setPalette(self.green_color) else: self.label_solarPanelPlusYPower.setPalette(self.red_color) if telemetry['BatteryVoltage'] >= 7.2: self.label_batteryVoltage.setPalette(self.green_color) elif telemetry['BatteryVoltage'] >= 6.6: self.label_batteryVoltage.setPalette(self.yellow_color) else: self.label_batteryVoltage.setPalette(self.red_color) if 0 <= battery_current <= 2.9: self.label_batteryCurrent.setPalette(self.green_color) else: self.label_batteryCurrent.setPalette(self.red_color) def color_code_temperature(self, telemetry): if -8.0 <= telemetry['CommBoardTemperature'] <= 60.0: self.label_commBoardTemperature.setPalette(self.green_color) else: self.label_commBoardTemperature.setPalette(self.red_color) if 5.0 <= telemetry['BatteryTemperature'] <= 25: self.label_batteryTemperature.setPalette(self.green_color) elif 2.0 <= telemetry['BatteryTemperature'] < 5.0 or telemetry['BatteryTemperature'] > 25.0: self.label_batteryTemperature.setPalette(self.yellow_color) else: self.label_batteryTemperature.setPalette(self.red_color) if -8.0 <= telemetry['EpsBoardTemperature'] <= 45.0: self.label_epsBoardTemperature.setPalette(self.green_color) else: self.label_epsBoardTemperature.setPalette(self.red_color) if -8.0 <= telemetry['CdhBoardTemperature'] <= 29.0: self.label_cdhTemperature.setPalette(self.green_color) else: self.label_cdhTemperature.setPalette(self.red_color) if -13.0 <= telemetry['MotherboardTemperature'] <= 28.0: self.label_motherboardTemperature.setPalette(self.green_color) else: self.label_motherboardTemperature.setPalette(self.red_color) if -75.0 <= telemetry['SolarPanelMinusYTemperature'] <= 85.0: self.label_solarPanelMinusYTemperature.setPalette(self.green_color) else: self.label_solarPanelMinusYTemperature.setPalette(self.red_color) if -75.0 <= telemetry['SolarPanelPlusXTemperature'] <= 85.0: self.label_solarPanelPlusXTemperature.setPalette(self.green_color) else: self.label_solarPanelPlusXTemperature.setPalette(self.red_color) if -75.0 <= telemetry['SolarPanelPlusYTemperature'] <= 85.0: self.label_solarPanelPlusYTemperature.setPalette(self.green_color) else: self.label_solarPanelPlusYTemperature.setPalette(self.red_color) def stop_read(self): self.connected_port.close() def save_data_toggled(self): if self.do_save_data(): self.setup_output_files() else: self.display_gui_no_output_data() def display_gui_no_output_data(self): self.textBrowser_savingDataToFile.setText("Not saving data to file.") self.textBrowser_savingDataToFile.setPalette(self.red_color) def forward_data_toggled(self): if self.do_forward_data(): self.display_gui_upload_idle() else: self.display_gui_upload_disabled() self.write_gui_config_options_to_config_file() def display_gui_upload_idle(self): self.label_uploadStatus.setText("Upload status: Idle") def display_gui_upload_disabled(self): self.label_uploadStatus.setText("Upload status: Disabled") def decode_kiss_toggled(self): self.write_gui_config_options_to_config_file() def prepare_to_exit(self): self.log.info("About to quit.") self.upload_data() # Only occurs if forward data is toggled on self.log.info("Closing MinXSS Beacon Decoder.")
class Window(QDialog): def __init__(self, parent=None): super(Window, self).__init__(parent) self.iconGroupBox = QGroupBox() self.iconLabel = QLabel() self.iconComboBox = QComboBox() self.showIconCheckBox = QCheckBox() self.messageGroupBox = QGroupBox() self.typeLabel = QLabel() self.durationLabel = QLabel() self.durationWarningLabel = QLabel() self.titleLabel = QLabel() self.bodyLabel = QLabel() self.typeComboBox = QComboBox() self.durationSpinBox = QSpinBox() self.titleEdit = QLineEdit() self.bodyEdit = QTextEdit() self.showMessageButton = QPushButton() self.minimizeAction = QAction() self.maximizeAction = QAction() self.restoreAction = QAction() self.quitAction = QAction() self.trayIcon = QSystemTrayIcon() self.trayIconMenu = QMenu() self.createIconGroupBox() self.createMessageGroupBox() self.iconLabel.setMinimumWidth(self.durationLabel.sizeHint().width()) self.createActions() self.createTrayIcon() self.showMessageButton.clicked.connect(self.showMessage) self.showIconCheckBox.toggled.connect(self.trayIcon.setVisible) self.iconComboBox.currentIndexChanged.connect(self.setIcon) self.trayIcon.messageClicked.connect(self.messageClicked) self.trayIcon.activated.connect(self.iconActivated) self.mainLayout = QVBoxLayout() self.mainLayout.addWidget(self.iconGroupBox) self.mainLayout.addWidget(self.messageGroupBox) self.setLayout(self.mainLayout) self.iconComboBox.setCurrentIndex(1) self.trayIcon.show() self.setWindowTitle("Systray") self.resize(400, 300) def setVisible(self, visible): self.minimizeAction.setEnabled(visible) self.maximizeAction.setEnabled(not self.isMaximized()) self.restoreAction.setEnabled(self.isMaximized() or not visible) super().setVisible(visible) def closeEvent(self, event): if not event.spontaneous() or not self.isVisible(): return if self.trayIcon.isVisible(): QMessageBox.information(self, "Systray", "The program will keep running in the system tray. " "To terminate the program, choose <b>Quit</b> in the context " "menu of the system tray entry.") self.hide() event.ignore() @Slot(int) def setIcon(self, index): icon = self.iconComboBox.itemIcon(index) self.trayIcon.setIcon(icon) self.setWindowIcon(icon) self.trayIcon.setToolTip(self.iconComboBox.itemText(index)) @Slot(str) def iconActivated(self, reason): if reason == QSystemTrayIcon.Trigger: pass if reason == QSystemTrayIcon.DoubleClick: self.iconComboBox.setCurrentIndex( (self.iconComboBox.currentIndex() + 1) % self.iconComboBox.count() ) if reason == QSystemTrayIcon.MiddleClick: self.showMessage() @Slot() def showMessage(self): self.showIconCheckBox.setChecked(True) selectedIcon = self.typeComboBox.itemData(self.typeComboBox.currentIndex()) msgIcon = QSystemTrayIcon.MessageIcon(selectedIcon) if selectedIcon == -1: # custom icon icon = QIcon(self.iconComboBox.itemIcon(self.iconComboBox.currentIndex())) self.trayIcon.showMessage( self.titleEdit.text(), self.bodyEdit.toPlainText(), icon, self.durationSpinBox.value() * 1000, ) else: self.trayIcon.showMessage( self.titleEdit.text(), self.bodyEdit.toPlainText(), msgIcon, self.durationSpinBox.value() * 1000, ) @Slot() def messageClicked(self): QMessageBox.information(None, "Systray", "Sorry, I already gave what help I could.\n" "Maybe you should try asking a human?") def createIconGroupBox(self): self.iconGroupBox = QGroupBox("Tray Icon") self.iconLabel = QLabel("Icon:") self.iconComboBox = QComboBox() self.iconComboBox.addItem(QIcon(":/images/bad.png"), "Bad") self.iconComboBox.addItem(QIcon(":/images/heart.png"), "Heart") self.iconComboBox.addItem(QIcon(":/images/trash.png"), "Trash") self.showIconCheckBox = QCheckBox("Show icon") self.showIconCheckBox.setChecked(True) iconLayout = QHBoxLayout() iconLayout.addWidget(self.iconLabel) iconLayout.addWidget(self.iconComboBox) iconLayout.addStretch() iconLayout.addWidget(self.showIconCheckBox) self.iconGroupBox.setLayout(iconLayout) def createMessageGroupBox(self): self.messageGroupBox = QGroupBox("Balloon Message") self.typeLabel = QLabel("Type:") self.typeComboBox = QComboBox() self.typeComboBox.addItem("None", QSystemTrayIcon.NoIcon) self.typeComboBox.addItem( self.style().standardIcon(QStyle.SP_MessageBoxInformation), "Information", QSystemTrayIcon.Information, ) self.typeComboBox.addItem( self.style().standardIcon(QStyle.SP_MessageBoxWarning), "Warning", QSystemTrayIcon.Warning, ) self.typeComboBox.addItem( self.style().standardIcon(QStyle.SP_MessageBoxCritical), "Critical", QSystemTrayIcon.Critical, ) self.typeComboBox.addItem(QIcon(), "Custom icon", -1) self.typeComboBox.setCurrentIndex(1) self.durationLabel = QLabel("Duration:") self.durationSpinBox = QSpinBox() self.durationSpinBox.setRange(5, 60) self.durationSpinBox.setSuffix(" s") self.durationSpinBox.setValue(15) self.durationWarningLabel = QLabel("(some systems might ignore this hint)") self.durationWarningLabel.setIndent(10) self.titleLabel = QLabel("Title:") self.titleEdit = QLineEdit("Cannot connect to network") self.bodyLabel = QLabel("Body:") self.bodyEdit = QTextEdit() self.bodyEdit.setPlainText("Don't believe me. Honestly, I don't have a clue." "\nClick this balloon for details.") self.showMessageButton = QPushButton("Show Message") self.showMessageButton.setDefault(True) messageLayout = QGridLayout() messageLayout.addWidget(self.typeLabel, 0, 0) messageLayout.addWidget(self.typeComboBox, 0, 1, 1, 2) messageLayout.addWidget(self.durationLabel, 1, 0) messageLayout.addWidget(self.durationSpinBox, 1, 1) messageLayout.addWidget(self.durationWarningLabel, 1, 2, 1, 3) messageLayout.addWidget(self.titleLabel, 2, 0) messageLayout.addWidget(self.titleEdit, 2, 1, 1, 4) messageLayout.addWidget(self.bodyLabel, 3, 0) messageLayout.addWidget(self.bodyEdit, 3, 1, 2, 4) messageLayout.addWidget(self.showMessageButton, 5, 4) messageLayout.setColumnStretch(3, 1) messageLayout.setRowStretch(4, 1) self.messageGroupBox.setLayout(messageLayout) def createActions(self): self.minimizeAction = QAction("Minimize", self) self.minimizeAction.triggered.connect(self.hide) self.maximizeAction = QAction("Maximize", self) self.maximizeAction.triggered.connect(self.showMaximized) self.restoreAction = QAction("Restore", self) self.restoreAction.triggered.connect(self.showNormal) self.quitAction = QAction("Quit", self) self.quitAction.triggered.connect(qApp.quit) def createTrayIcon(self): self.trayIconMenu = QMenu(self) self.trayIconMenu.addAction(self.minimizeAction) self.trayIconMenu.addAction(self.maximizeAction) self.trayIconMenu.addAction(self.restoreAction) self.trayIconMenu.addSeparator() self.trayIconMenu.addAction(self.quitAction) self.trayIcon = QSystemTrayIcon(self) self.trayIcon.setContextMenu(self.trayIconMenu)
class PingIndicator(QMainWindow): def __init__(self, address="8.8.8.8"): super(PingIndicator, self).__init__() self.icon = QImage(QSize(packet_amount, indicator_image_height), QImage.Format_RGBA8888) self.online = True self.destination = address self.packets = deque([], packet_amount) self.tray_icon = QSystemTrayIcon(self) self.tray_icon.setToolTip(address) self.update_timer = QTimer(self) self.update_timer.setInterval(int(timeout * 1.05)) self.update_timer.timeout.connect(self.update_indicator) self.update_timer.start() self.last_time_online = strftime("%H:%M:%S") self.reset() def update_icon(self): self.icon.fill(QColor(0, 0, 0, 0)) painter = QPainter(self.icon) (width, height) = self.icon.size().toTuple() width -= 1 height -= 1 painter.fillRect(QRect(0, 0, width, height), QColor(0, 0, 0, 0)) try: scale = min(1.0 / max(self.packets), min_scale) except ValueError: scale = min_scale for index, ping in enumerate(list(reversed(self.packets))): x = ping / float(timeout) color = QColor( int(-324 * x**2 + 390 * x + 138), # R int(-480 * x**2 + 254 * x + 226), # G int(-212 * x**2 + 160 * x + 52), # B 255, ) scaled_height = ceil(scale * ping * height) painter.fillRect( QRect(width - index, height - scaled_height, 1, scaled_height), color) self.tray_icon.setIcon(QIcon(QPixmap(self.icon))) self.tray_icon.show() def update_indicator(self): try: new_env = dict(os.environ) new_env['LC_ALL'] = 'C' output = check_output( [ "ping", "-c", "1", "-W", str(timeout / 1000), self.destination ], stderr=STDOUT, env=new_env, ).decode("ascii") # man ping for line in output.splitlines(): pos = line.find("time=") if pos != -1: new_label = line[pos + 5:-3].center(4) self.packets.append(round(float(new_label), 2)) if not self.online: self.online = True self.tray_icon.contextMenu().actions()[0].setText( "Last disconnect: " + self.last_time_online) else: self.last_time_online = strftime("%H:%M:%S") break else: raise ValueError("No time could be parsed.") except CalledProcessError as cpe: self.packets.append(timeout) if self.online: self.online = False self.tray_icon.contextMenu().actions()[0].setText( "Offline since: " + strftime("%H:%M:%S")) print(cpe) except KeyboardInterrupt: self.close() self.update_icon() self.update_menu() return True def reset(self): self.packets.clear() self.update_icon() menu = QMenu() menu.addAction("Online since: " + strftime("%H:%M:%S")) menu.addAction("Lost: -, Avg: -") menu.addAction("Max: -, Min: -") menu.addSeparator() menu.addAction("Reset").triggered.connect(self.reset) menu.addAction("Quit").triggered.connect(self.close) self.tray_icon.setContextMenu(menu) def update_menu(self): self.tray_icon.contextMenu().actions()[1].setText( "Lost: %d, Avg: %dms" % (self.packets.count(timeout), avg(self.packets)), ) self.tray_icon.contextMenu().actions()[2].setText( "Max: %dms, Min: %dms" % (max(self.packets), min(self.packets)), )
class App(QApplication): def __init__(self, argv): super().__init__(argv) self._create_tray_icon() self._create_ui() self._create_interaction_server() self._session = None def open_preferences(self): prefs_dialog = PreferencesDialog() prefs_dialog.exec() def _mode_changed(self): action = self._mode_group.checkedAction() if action == self._mode_off: self._stop_session() elif action == self._mode_enabled: self._interaction_server.train = False self._start_session() elif action == self._mode_training: self._interaction_server.train = True self._start_session() def _start_session(self): if self._session is not None: return self._session = QProcess(self) self._session.finished.connect(self._session_ended) self._session.readyReadStandardOutput.connect(self._log_append_stdout) self._session.readyReadStandardError.connect(self._log_append_stderr) settings = QSettings() self._session.start(sys.executable, [ 'run_session.py', settings.value('CyKitAddress', app.DEFAULT_CYKIT_ADDRESS), str(settings.value('CyKitPort', app.DEFAULT_CYKIT_PORT)), str(self._interaction_server.port) ]) def _stop_session(self): if self._session is not None: self._session.close() # TODO: Handle non-null exit codes def _session_ended(self): self._session = None self._mode_off.setChecked(True) def _log_append_stdout(self): process = self.sender() self._log_window.moveCursor(QTextCursor.End) self._log_window.insertPlainText( process.readAllStandardOutput().data().decode('utf-8')) self._log_window.moveCursor(QTextCursor.End) def _log_append_stderr(self): process = self.sender() self._log_window.moveCursor(QTextCursor.End) self._log_window.insertPlainText( process.readAllStandardError().data().decode('utf-8')) self._log_window.moveCursor(QTextCursor.End) def _select_letter(self, letter): self._letter_ui.setText(letter) def _create_tray_icon(self): menu = QMenu() self._mode_group = QActionGroup(menu) self._mode_group.triggered.connect(self._mode_changed) self._mode_off = QAction("&Off", parent=menu) self._mode_off.setCheckable(True) self._mode_off.setChecked(True) self._mode_group.addAction(self._mode_off) menu.addAction(self._mode_off) self._mode_enabled = QAction("&Enabled", parent=menu) self._mode_enabled.setCheckable(True) self._mode_group.addAction(self._mode_enabled) menu.addAction(self._mode_enabled) self._mode_training = QAction("&Training mode", parent=menu) self._mode_training.setCheckable(True) self._mode_group.addAction(self._mode_training) menu.addAction(self._mode_training) menu.addSeparator() menu.addAction("&Preferences", self.open_preferences) menu.addSeparator() menu.addAction("E&xit", self.exit) pixmap = QPixmap(32, 32) pixmap.fill(Qt.white) icon = QIcon(pixmap) self._tray_icon = QSystemTrayIcon(parent=self) self._tray_icon.setContextMenu(menu) self._tray_icon.setIcon(icon) self._tray_icon.show() def _create_ui(self): self._keyboard_ui = KeyboardUI() self._keyboard_ui.show() # TODO: Get rid of this in favor of os_interaction self._letter_ui = QLabel("-") self._letter_ui.setWindowTitle("Selected letter") self._letter_ui.setStyleSheet('font-size: 72pt') self._letter_ui.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) self._letter_ui.setGeometry(600, 0, 100, 100) self._letter_ui.show() # TODO: Replace with more user-friendly log self._log_window = QTextBrowser() self._log_window.setWindowTitle("Session Log") self._log_window.setGeometry(700, 0, 500, 500) self._log_window.show() def _create_interaction_server(self): self._interaction_server = InteractionServer(self) self._interaction_server.keyboard_flash_row.connect( self._keyboard_ui.flash_row) self._interaction_server.keyboard_flash_col.connect( self._keyboard_ui.flash_col) self._interaction_server.keyboard_highlight_letter.connect( self._keyboard_ui.highlight_letter) self._interaction_server.keyboard_select_letter.connect( self._select_letter)
class Form(QDialog): def __init__(self, parent=None): self.round = 0 self.lcd = QLCDNumber(5) self.lcd2 = QLCDNumber(5) self.clock = QLCDNumber(5) super(Form, self).__init__(parent) self.setWindowTitle("Pomodoro") # Create widgets self.slider = QSlider(Qt.Horizontal) self.slider.setRange(1, 99) self.slider.setValue(25) self.slider2 = QSlider(Qt.Horizontal) self.slider2.setRange(1, 99) self.slider2.setValue(5) self.count = self.slider.value() * 60 self.rest = self.slider2.value() * 60 self.taskbar_count = 0 self.taskbar2_count = 0 self.text = QLabel("How long should the work period be?") self.text2 = QLabel("How long should the rest period be?") self.work = QLabel("WORK") self.pause = QLabel("REST") self.rounds = QLabel("Number of rounds: " + str(self.round)) self.work.setAlignment(Qt.AlignHCenter) self.work.setFont(QFont("Times", 18, QFont.Bold)) self.pause.setAlignment(Qt.AlignHCenter) self.pause.setFont(QFont("Times", 18, QFont.Bold)) self.button = QPushButton("Start timer") self.button2 = QPushButton("Stop timer") self.reset = QPushButton("Reset rounds") self.lcd.display("25:00") self.lcd2.display("05:00") mins = 25 secs = "00" self.clock.display(f"{mins}:{secs}") self.slider.valueChanged.connect(self.first_display) self.slider2.valueChanged.connect(self.second_display) self.slider.valueChanged.connect(self.clock_display) self.button2.hide() self.work.hide() self.pause.hide() self.clock.hide() # Create layout and add widgets layout = QVBoxLayout() layout.addWidget(self.text) layout.addWidget(self.lcd) layout.addWidget(self.slider) layout.addWidget(self.text2) layout.addWidget(self.lcd2) layout.addWidget(self.slider2) layout.addWidget(self.button) layout.addWidget(self.button2) layout.addWidget(self.work) layout.addWidget(self.pause) layout.addWidget(self.clock) layout.addWidget(self.rounds) layout.addWidget(self.reset) # Set dialog layout self.setLayout(layout) self.systemtray_icon = QSystemTrayIcon(QIcon("snake.png")) self.systemtray_icon.show() self.systemtray_icon.activated.connect(self.icon_activated) self.menu = QMenu(parent) self.exit_action = self.menu.addAction("Exit") self.systemtray_icon.setContextMenu(self.menu) self.exit_action.triggered.connect(self.slot_exit) # Add signals self.slider.valueChanged.connect(self.count_func) self.slider2.valueChanged.connect(self.count_func) self.button.clicked.connect(self.button_update) self.button.clicked.connect(self.timer_func) self.button.clicked.connect(self.round_count) self.button2.clicked.connect(self.stop) self.reset.clicked.connect(self.reset_rounds) def reset_rounds(self): self.round = 0 self.rounds.setText("Number of rounds: " + str(self.round)) def round_count(self): self.round += 1 self.rounds.setText("Number of rounds: " + str(self.round)) def icon_activated(self, reason): if reason in (QSystemTrayIcon.Trigger, QSystemTrayIcon.DoubleClick): self.show() def closeEvent(self, event): self.hide() event.ignore() def slot_exit(self): QApplication.exit(0) def first_display(self): minute = str(self.slider.sliderPosition()) second = ":00" leading_zero = "0" if self.slider.sliderPosition() >= 10: self.lcd.display(minute + second) else: self.lcd.display(leading_zero + minute + second) def second_display(self): minute = str(self.slider2.sliderPosition()) second = ":00" leading_zero = "0" if self.slider2.sliderPosition() >= 10: self.lcd2.display(minute + second) else: self.lcd2.display(leading_zero + minute + second) def clock_display(self): minute = str(self.slider.sliderPosition()) second = ":00" leading_zero = "0" if self.slider.sliderPosition() >= 10: self.clock.display(minute + second) else: self.clock.display(leading_zero + minute + second) def count_func(self): self.count = self.slider.sliderPosition() * 60 self.rest = self.slider2.sliderPosition() * 60 def countdown(self): minute, second = divmod(self.count, 60) zero = "0" show = self.work.show() if second < 10 and minute < 10: self.clock.display(zero + str(minute) + ":" + zero + str(second)) elif second < 10: self.clock.display(str(minute) + ":" + zero + str(second)) elif minute < 10: self.clock.display(zero + str(minute) + ":" + str(second)) else: self.clock.display(str(minute) + ":" + str(second)) self.count -= 1 if self.count < -1: self.work.hide() self.taskbar_rest() show = self.pause.show() minute, second = divmod(self.rest, 60) zero = "0" if self.rest == self.slider2.value() * 60: self.show() if second < 10 and minute < 10: self.clock.display(zero + str(minute) + ":" + zero + str(second)) elif second < 10: self.clock.display(str(minute) + ":" + zero + str(second)) elif minute < 10: self.clock.display(zero + str(minute) + ":" + str(second)) else: self.clock.display(str(minute) + ":" + str(second)) self.rest -= 1 if self.rest < -1: self.clock.display("00:00") self.taskbar_work() self.timer.stop() self.stop() show def timer_func(self): timer = QTimer() self.timer = timer self.timer.timeout.connect(self.countdown) self.timer.start(1000) def button_update(self): self.button.hide() self.text.hide() self.lcd.hide() self.slider.hide() self.text2.hide() self.lcd2.hide() self.slider2.hide() self.reset.hide() self.clock.show() self.button2.show() self.work.show() def taskbar_rest(self): if self.taskbar_count == 0: self.systemtray_icon.showMessage("PAUSE", "Time to rest!", QSystemTrayIcon.Information, 500000) self.taskbar_count = 1 def taskbar_work(self): if self.taskbar2_count == 0: self.systemtray_icon.showMessage("WORK", "Break over!", QSystemTrayIcon.Information, 500000) self.taskbar2_count = 1 def stop(self): self.timer.stop() self.button2.hide() self.work.hide() self.pause.hide() self.clock.hide() self.count = self.slider.value() * 60 self.rest = self.slider2.value() * 60 self.clock.display(str(self.slider.value()) + ":00") self.button.show() self.text.show() self.lcd.show() self.slider.show() self.text2.show() self.lcd2.show() self.slider2.show() self.reset.show() self.show() self.taskbar_count = 0 self.taskbar2_count = 0