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 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 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 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 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)