예제 #1
0
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)
예제 #2
0
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)