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"启动成功", '请通过右键操作')
class IPtray(object): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # self.Lock=threading.RLock() self.ip = "0.0.0.0" self.app = QApplication(sys.argv) self.wMain = QWidget() self.tray = QSystemTrayIcon(self.wMain) def Ui(self): self.sub_getIP() self.actExit = QAction("退出") self.actExit.triggered.connect(self.forceEx) self.tray.setIcon(QIcon(".\icon\ip.png")) self.tray.setVisible(True) self.trayMenu = QMenu() self.trayMenu.addAction(self.actExit) self.tray.setContextMenu(self.trayMenu) # print("showing message") self.tray.showMessage("IP", "你的IP是" + self.ip) self.sub_setToolTip() #getIP运行比较慢, setToolTip放在后面. sys.exit(self.app.exec_()) def forceEx(self): sys.exit(self.app.exec_()) exit() def getIP(self): # self.Lock.acquire() while 1: try: s = socket(AF_INET, SOCK_DGRAM) s.connect(("114.114.114.114", 80)) self.ip = s.getsockname()[0] finally: s.close() # print("exit getIP") sleep(5) # self.Lock.release() def setToolTip(self): while 1: self.tray.setToolTip(self.ip) # print("exit setToopTip") sleep(5) def sub_getIP(self): self.subThread_getIP = Thread(target=self.getIP, daemon=True) self.subThread_getIP.setName("sub_getIP") # self.subThread_getIP.setDaemon = True # 设置为后台线程 self.subThread_getIP.start() # 这个线程要一直运行 def sub_setToolTip(self): self.subThread_setToolTip = Thread(target=self.setToolTip, daemon=True) self.subThread_setToolTip.setName("sub_setToolTip") # self.subThread_setToolTip.setDaemon(True) # 设置为后台线程 self.subThread_setToolTip.start()
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 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 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(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 Indicator(): APP_NAME = 'FortiVPN Quick Tray' def __init__(self): self.app = QApplication([]) self.app.setQuitOnLastWindowClosed(False) self.indicator = QSystemTrayIcon() self.indicator.setIcon(QIcon(self._get_file('./icons/icon.png'))) self.indicator.setContextMenu(self._build_menu()) self.indicator.setVisible(True) self.indicator.setToolTip('OFF') self.indicator.activated.connect(self._click_indicator) self.logs_dialog = QTextEdit() self.logs_dialog.setWindowTitle(f'{self.APP_NAME} - Logs') self.logs_dialog.setFixedSize(440, 440) self.logs_dialog.setReadOnly(True) self.logs_dialog.setWindowIcon( QIcon(self._get_file('./icons/icon.png'))) self.vpn_config = '/etc/openfortivpn/config' self.vpn_process = None self.vpn_logs_file = NamedTemporaryFile(delete=False) self.vpn_thread = VPNThread(self.vpn_logs_file) self.vpn_thread.status.connect(self._update_vpn_status) self.vpn_thread.log.connect(self.logs_dialog.append) self.app_update_thread = AppUpdateThread(self._get_file('./version')) self.app_update_thread.update_available.connect( self._show_update_notification) self.app_update_thread.start() def run(self): self.app.exec_() sys.exit() def _build_menu(self): menu = QMenu() self.connect_action = QAction('Connect') self.disconnect_action = QAction('Disconnect') self.config_action = QAction('Config') self.logs_action = QAction('Logs') self.exit_action = QAction('Exit') self.disconnect_action.setDisabled(True) self.connect_action.triggered.connect(self._click_connect) self.disconnect_action.triggered.connect(self._click_disconnect) self.config_action.triggered.connect(self._click_config) self.logs_action.triggered.connect(self._click_logs) self.exit_action.triggered.connect(self._click_exit) menu.addAction(self.connect_action) menu.addAction(self.disconnect_action) menu.addSeparator() menu.addAction(self.config_action) menu.addAction(self.logs_action) menu.addSeparator() menu.addAction(self.exit_action) return menu def _click_connect(self): self.indicator.setIcon(QIcon(self._get_file('./icons/try.png'))) self.indicator.setToolTip('TRYING') self.connect_action.setDisabled(True) self.config_action.setDisabled(True) with open(self.vpn_logs_file.name, 'w+b') as f: try: self.vpn_process = Popen(split('pkexec openfortivpn -c ' + self.vpn_config), stdin=PIPE, stdout=f, stderr=f) self.vpn_process.communicate(timeout=1) except TimeoutExpired: pass self.vpn_thread.start() def _click_disconnect(self): try: run(split('pkexec kill ' + str(self.vpn_process.pid))) except ChildProcessError: pass def _click_config(self): config_file, _ = QFileDialog.getOpenFileName( caption='Select config file', dir='/', filter='All files (*)', options=QFileDialog.DontUseNativeDialog) if config_file: self.vpn_config = config_file def _click_logs(self): if not self.vpn_thread.isRunning(): with open(self.vpn_logs_file.name) as logs: self.logs_dialog.setPlainText(logs.read()) self.logs_dialog.show() def _click_exit(self): if self.indicator.toolTip() == 'ON': _ = QMessageBox.warning( None, self.APP_NAME, 'VPN is still ON. Please Disconnect first before exiting') return if self.vpn_logs_file.name: remove_log_file(self.vpn_logs_file.name) self.app.quit() def _get_file(self, file_path): try: base = sys._MEIPASS except Exception: base = path.abspath('.') return path.join(base, file_path) def _click_indicator(self, event): if event == QSystemTrayIcon.ActivationReason.Trigger: self._click_logs() def _update_vpn_status(self, message): if message == 'ERR': self.indicator.setIcon(QIcon(self._get_file('./icons/err.png'))) self.indicator.setToolTip('ERROR') self.connect_action.setDisabled(False) self.config_action.setDisabled(False) pass if message == 'ON': self.indicator.setIcon(QIcon(self._get_file('./icons/on.png'))) self.indicator.setToolTip('ON') self.disconnect_action.setDisabled(False) pass if message == 'OFF': self.indicator.setIcon(QIcon(self._get_file('./icons/icon.png'))) self.indicator.setToolTip('OFF') self.disconnect_action.setDisabled(True) self.connect_action.setDisabled(False) self.config_action.setDisabled(False) pass def _show_update_notification(self, flag): if flag and self.indicator.supportsMessages: self.indicator.showMessage( self.APP_NAME, 'There is a new update available', QIcon(self._get_file('./icons/icon.png')))
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 Window(QWidget): def __init__(self, parent=None): super(Window, self).__init__() self.path = None self.settings() self.create_widgets() self.create_layout() def settings(self): self.resize(300, 120) self.setWindowTitle('Mp3 Downloader') def create_widgets(self): self.edit_url = QLineEdit() self.edit_name = QLineEdit() self.btn_select_path = QPushButton('Select path', self) self.btn_select_path.clicked.connect(self.select_path) self.btn_download = QPushButton('Download mp3', self) self.btn_download.clicked.connect(self.download) def create_layout(self): self.layout = QFormLayout() self.layout.addRow('Nome:', self.edit_name) self.layout.addRow('Url:', self.edit_url) self.layout.addRow('Selecionar destino:', self.btn_select_path) self.layout.addRow(self.btn_download) self.setLayout(self.layout) def select_path(self): self.path = QFileDialog.getExistingDirectory(self, 'Selecionar Pasta') def download(self): if self.verify_fields(): self.manage_interface(False) self.thread_qt() def verify_fields(self): if self.path is None: return False else: strings = [self.edit_url, self.edit_name.text(), self.path] regex_validate = QRegExp('*.mp3') regex_validate.setPatternSyntax(QRegExp.Wildcard) emptys = 0 for string in strings: if len(string.split()) == 0: emptys += 1 if emptys == 0 and regex_validate.exactMatch( self.edit_url.text()): return True def thread_qt(self): url = self.edit_url.text() name = self.edit_name.text() path = self.edit_path.text() self.thre = DownloaderMusic() self.thre.finished.connect(self.downfin) self.thre.start() def manage_interface(self, state): self.btn_download.setEnabled(state) self.edit_name.setEnabled(state) self.edit_url.setEnabled(state) self.btn_select_path def downfin(self): self.notify_icon = QSystemTrayIcon() self.notify_icon.setVisible(True) self.notify_icon.showMessage( 'Download Finalizado', u'O download da sua música foi realizado com sucesso.', QSystemTrayIcon.Information, 3000) self.manage_interface(True)
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 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
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 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)
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 KnechtWindow(QMainWindow): system_tray_click_connected = False tree_focus_changed = Signal(QTreeView) is_about_to_quit = Signal() def __init__(self, app): """ The GUI MainWindow Class :param modules.gui.main_app.KnechtApp app: Main QApplication class """ super(KnechtWindow, self).__init__() self.app = app SetupWidget.from_ui_file(self, Resource.ui_paths['knecht_model_gui'], custom_widgets={'QColorButton': QColorButton}) self.rk_icon = QIcon(QPixmap(Resource.icon_paths['RK_Icon'])) # Set version window title self.setWindowTitle(f'{self.windowTitle()} - v{self.app.version}') # ---- Setup Main Menu ---- self.main_menu = MainWindowMenu(self) # ---- Tree Setup ---- tree_view_list = [self.variantTree, self.renderTree] tree_file_list = [_('Variantenbaum'), _('Renderliste')] tree_filter_widgets = [ self.lineEdit_Var_filter, self.lineEdit_Ren_filter ] # View Mgr will replace placeholder presetTree self.view_mgr = UiViewManager(self) self.view_mgr.view_updated.connect(self._connect_message_browser) self.view_mgr.setup_initial_tab_view(self.presetTree) # Set presetTree to current View Mgr view to avoid accessing deleted object self.presetTree = self.view_mgr.current_view() replaced_views = self.view_mgr.setup_default_views( tree_view_list, tree_file_list, tree_filter_widgets) self.variantTree, self.renderTree = replaced_views[0], replaced_views[ 1] for default_view in [self.variantTree, self.renderTree]: default_view.setFocusPolicy(Qt.ClickFocus) default_view.undo_stack.cleanChanged.disconnect() # ---- Setup renderTree ---- self.renderTree.is_render_view = True self.renderTree.accepted_item_types = [Kg.render_preset, Kg.preset] # ---- Internal Clipboard ---- self.clipboard = TreeClipboard() # ---- Store last tree with focus ---- self.last_focus_tree = self.presetTree # ---- System tray and taskbar ---- self.system_tray = QSystemTrayIcon(self.rk_icon, self) self.system_tray.hide() # ---- Windows taskbar progress indicator ---- self.taskbar_btn = QWinTaskbarButton(self) self.taskbar_progress = self.taskbar_btn.progress() # Delayed Taskbar Setup (Main Window needs to be created for correct window handle) QTimer.singleShot(1, self.init_taskbar) # ---- Generic Info Overlay ---- self.overlay = MainWindowOverlay(self.centralWidget()) # ---- Close Action ---- self.actionBeenden.triggered.connect(self.close) # ---- Setup Main UI Widgets ---- MainWindowWidgets(self) # Updater self.updater = KnechtUpdate(self) self.updater.update_available.connect( self.main_menu.info_menu.update_ready) QTimer.singleShot(20000, self.auto_update) # Initial Update check self.app.focusChanged.connect(self.app_focus_changed) # ---- Translate Ui elements loaded from ui file ---- translate_main_ui(self) self.setAcceptDrops(True) def _get_drop_event_files(self, mime_data): files = [] for url in mime_data.urls(): if not url.isLocalFile(): continue file = Path(url.toLocalFile()) if file.suffix.casefold( ) in self.main_menu.file_menu.supported_file_types: files.append(file) return files def dragEnterEvent(self, event: QDragEnterEvent): if event.mimeData().hasUrls(): if self._get_drop_event_files(event.mimeData()): event.acceptProposedAction() else: event.ignore() def dropEvent(self, event: QDropEvent): if event.mimeData().hasUrls(): files = self._get_drop_event_files(event.mimeData()) if files: for file in files: self.main_menu.file_menu.guess_open_file(file) event.accept() return True event.ignore() return False def app_focus_changed(self, old_widget: QWidget, new_widget: QWidget): if isinstance(new_widget, KnechtTreeView): self.set_last_focus_tree(new_widget) def init_taskbar(self): """ Initializes the MS Windows taskbar button""" # Needs to be called after window is created/shown self.taskbar_btn.setWindow(self.windowHandle()) self.taskbar_progress.setRange(0, 100) self.taskbar_progress.valueChanged.connect(self.taskbar_progress.show) def _connect_message_browser(self, view: KnechtTreeView): LOGGER.debug('Setting up message browser for: %s', view.objectName()) view.info_overlay.setup_message_browser(self.messageBrowser, self.tabWidget) def show_tray_notification(self, title: str, message: str, clicked_callback=None): if not self.system_tray.isVisible(): self.system_tray.show() # Disconnect existing callback if self.system_tray_click_connected: try: self.system_tray.messageClicked.disconnect() except RuntimeError: LOGGER.info( 'Could not disconnect system tray messageClicked handler.') finally: self.system_tray_click_connected = False if clicked_callback is not None: self.system_tray.messageClicked.connect(clicked_callback) self.system_tray_click_connected = True self.system_tray.showMessage(title, message, self.rk_icon) def set_last_focus_tree(self, set_tree_focus): if isinstance(set_tree_focus, KnechtTreeView): self.last_focus_tree = set_tree_focus self.tree_focus_changed.emit(self.last_focus_tree) def tree_with_focus(self) -> KnechtTreeView: """ Return the current or last known QTreeView in focus """ widget_in_focus = self.focusWidget() if isinstance(widget_in_focus, KnechtTreeView): self.last_focus_tree = widget_in_focus return self.last_focus_tree def check_for_updates(self): self.updater.run_update() def auto_update(self): if self.updater.first_run: if FROZEN: self.check_for_updates() def report_missing_reset(self): msg = _( 'Die Varianten enthalten keine Reset Schaltung! Die zu sendenden Varianten ' 'werden mit vorangegangen Schaltungen kollidieren.') self.overlay.display(msg, duration=5000, immediate=True) def msg(self, txt: str, duration: int = 4000) -> None: self.statusBar().showMessage(txt, duration) self.overlay.display(txt, duration, immediate=True) def play_finished_sound(self): self._play_sound(SoundRsc.finished) def play_confirmation_sound(self): self._play_sound(SoundRsc.positive) def play_hint_sound(self): self._play_sound(SoundRsc.hint) def play_warning_sound(self): self._play_sound(SoundRsc.warning) def _play_sound(self, resource_key): try: sfx = SoundRsc.get_sound(resource_key, self) sfx.play() except Exception as e: LOGGER.error(e)