Beispiel #1
0
 def _createTray(self):
     from PyQt5.QtWidgets import QSystemTrayIcon
     from PyQt5.QtGui import QIcon
     from piony.common.system import expand_pj
     tray = QSystemTrayIcon()
     tray.setIcon(QIcon(expand_pj(":/res/tray-normal.png")))
     tray.show()
     return tray
    def __init__(self, manager: Manager, icon, parent=None, testing=False) -> None:
        QSystemTrayIcon.__init__(self, icon, parent)
        self.setToolTip("ActivityWatch" + (" (testing)" if testing else ""))

        self.manager = manager
        self.testing = testing

        self._build_rootmenu()
Beispiel #3
0
	def __init__(self, icon, parent=None):
		QSystemTrayIcon.__init__(self, icon, parent)
		self.parent = parent
		
		self.timer = QtCore.QTimer(self)
		self.timer.timeout.connect(self.update)
		self.timer.setSingleShot(True)
		
		self.update()
Beispiel #4
0
 def create_tray_icon(self):
     if QSystemTrayIcon.isSystemTrayAvailable():
         logger.debug('System tray icon is available, showing')
         self.tray_icon = QSystemTrayIcon(QIcon(':/i/xnova_logo_32.png'), self)
         self.tray_icon.setToolTip(self.tr('XNova Commander'))
         self.tray_icon.activated.connect(self.on_tray_icon_activated)
         self.tray_icon.show()
     else:
         self.tray_icon = None
 def __init__(self, icon, com, parent=None):
     QSystemTrayIcon.__init__(self, icon, parent)
     menu = QMenu(parent)
     showAction = menu.addAction("Mostrar")
     showAction.triggered.connect(self.show_action)
     exitAction = menu.addAction("Fechar")
     exitAction.triggered.connect(self.close_event)
     self.activated.connect(self.tray_activated)
     self.setContextMenu(menu)
     self.com = com
     self.show()
    def __init__(self, parent=None):
        QSystemTrayIcon.__init__(self, parent=None)

        # Initial state
        self.set_disabled()

        # Right Click
        right_menu = RightClicked()
        self.setContextMenu(right_menu)

        # left click
        self.activated.connect(self.toggle)
Beispiel #7
0
def start():
    # Start logging all events
    if '--drop' in sys.argv:
        drop_db()

    utils.start_app_logging()
    if sys.platform == 'win32':
        from server import SingleInstance
        single_app = SingleInstance()

        if single_app.already_running():
            running_warning()

    app = QApplication(sys.argv)

    if not QSystemTrayIcon.isSystemTrayAvailable():
        QMessageBox.critical(
            None,
            "Systray",
            "Could not detect a system tray on this system"
        )
        sys.exit(1)

    QApplication.setQuitOnLastWindowClosed(False)

    osf = OSFApp()

    osf.start()

    osf.hide()
    sys.exit(app.exec_())
Beispiel #8
0
    def __init__(self, *args):
        super(QApplication, self).__init__(*args)

        # Tray Icon
        self._system_tray = QSystemTrayIcon()
        self._system_tray.setIcon(QIcon('ui/icon.png'))
        self._system_tray.setToolTip("Parse99")
        self._system_tray.show()

        # Settings
        self.settings = settings.Settings("parse99")

        # Plugins
        self._plugins = {'maps': Maps(self.settings)}

        # Timer
        self._timer = QTimer()
        self._timer.timeout.connect(self._parse)

        # Thread
        self._thread = None

        # Menu
        self._system_tray.setContextMenu(self._get_menu())

        # File
        self._log_file = ""
        self._file_size = 0
        self._last_line_read = 0
        self._log_new_lines = []

        # Start
        self.toggle('on')
Beispiel #9
0
	def __init__(self, icon,parent=None):
		QSystemTrayIcon.__init__(self,icon, parent)
		menu = QMenu(parent)

		showAction = menu.addAction("Show Moodly")
		showAction.triggered.connect(self.trigger.emit)

		exitAction = menu.addAction("Exit")
		exitAction.triggered.connect(self.qtrigger.emit)

		self.updateAction = menu.addAction("Update Now")
		self.updateAction.triggered.connect(self.uptrigger.emit)
		self.updateAction.setEnabled(False)

		self.activated.connect(self.activateIcon)
		self.setContextMenu(menu)
Beispiel #10
0
def start_qt_app(config):
    import sys
    from PyQt5.QtWidgets import QApplication, QSystemTrayIcon, QMessageBox
    from quamash import QEventLoop
    from ui.main import Window
    from ui.qt_gui_connection import qSignal
    app = QApplication(sys.argv)
    
    loop = QEventLoop(app)
    asyncio.set_event_loop(loop)
    
    if not QSystemTrayIcon.isSystemTrayAvailable():
        QMessageBox.critical(None, "Systray",
                             "I couldn't detect any system tray on this system.")
        sys.exit(1)

    QApplication.setQuitOnLastWindowClosed(False)
    
    gui_connection = qSignal()
    window = Window(gui_connection)
    
    def closeApp():
        print("Close app signal")
        for task in asyncio.Task.all_tasks():
            print(task)
            task.cancel()
        loop.stop()
    gui_connection.closeApp.connect(closeApp)
    
    with loop:
        #asyncio.run_coroutine_threadsafe(timer(loop, config, gui_connection), loop)
        try:
            loop.run_until_complete(timer(loop, config, gui_connection))
        except asyncio.CancelledError:
            pass
Beispiel #11
0
    def __init__(self):
        super().__init__()
        self.running = False
        self.setWindowTitle('PySwicher v{}'.format(VERSION))

        # Logging config
        self.log_textbox = QPlainTextEditLogger(self)
        logging.getLogger().addHandler(self.log_textbox)
        self.log_textbox.setFormatter(logging.Formatter('[%(asctime)s][%(levelname)s]:    %(message)s'))
        self.log_textbox.setLevel(self.get_numeric_loglevel(options['log_level']))
        self.log_to_file = False

        # System tray configuration
        self.tray_menu = QMenu(self)
        self.systemTrayIcon = QSystemTrayIcon()
        self.systemTrayIcon.setVisible(False)
        self.systemTrayIcon.setIcon(QtGui.QIcon('C:\\Users\\Admin\\Pictures\\tray_stop.jpg'))
        self.systemTrayIcon.activated.connect(self.sys_tray)
        self.exit_action = self.tray_menu.addAction('Exit')
        self.exit_action.triggered.connect(self.exit_app)
        self.systemTrayIcon.setContextMenu(self.tray_menu)
        self.click_tray_timer = QtCore.QTimer(self)  # Fix for systemtray click trigger
        self.click_tray_timer.setSingleShot(True)
        self.click_tray_timer.timeout.connect(self.click_timeout)

        self.main_window_ui()
        self.starter()
Beispiel #12
0
    def __init__(self):
        self._app = QApplication([sys.argv[0]])

        from dbus.mainloop.pyqt5 import DBusQtMainLoop
        super(Magneto, self).__init__(main_loop_class = DBusQtMainLoop)

        self._window = QSystemTrayIcon(self._app)
        icon_name = self.icons.get("okay")
        self._window.setIcon(QIcon.fromTheme(icon_name))
        self._window.activated.connect(self._applet_activated)

        self._menu = QMenu(_("Magneto Entropy Updates Applet"))
        self._window.setContextMenu(self._menu)

        self._menu_items = {}
        for item in self._menu_item_list:
            if item is None:
                self._menu.addSeparator()
                continue

            myid, _unused, mytxt, myslot_func = item
            name = self.get_menu_image(myid)
            action_icon = QIcon.fromTheme(name)

            w = QAction(action_icon, mytxt, self._window,
                        triggered=myslot_func)
            self._menu_items[myid] = w
            self._menu.addAction(w)

        self._menu.hide()
Beispiel #13
0
    def __init__(self, parent=None):
        QMainWindow.__init__(self, parent)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.connect_handlers()

        # Sub Views
        self.server_form = ServerForm()
        self.server_form.set_main_window(self)
        self.server_form.hide()

        # Initiate empty list
        self.data = []
        self.notifications = []

        # Refresh list
        self.refresh_list()

        # Set icons
        icon = QtGui.QIcon(os.path.join(os.path.dirname(__file__), "icon.png"))
        self.setWindowIcon(icon)

        self.msgIcon = QSystemTrayIcon.MessageIcon(QSystemTrayIcon.Information)
        self.systray_icon = QSystemTrayIcon(icon)
        self.systray_icon.activated.connect(self.toggle_window)
        self.systray_icon.show()
Beispiel #14
0
    def __init__(self, *args):
        super(DemoImpl, self).__init__(*args)

        loadUi('dict2.ui',self)
		
        self.setLayout(self.verticalLayout)
        self.plainTextEdit.setReadOnly(True)
        self.setWindowFlags(self.windowFlags() |
                            Qt.WindowSystemMenuHint |
                            Qt.WindowMinMaxButtonsHint)
        self.trayicon = QSystemTrayIcon()
        self.traymenu = QMenu()

        self.quitAction = QAction('GQuit', self)
        self.quitAction.triggered.connect(self.close)
        self.quitAction.setShortcut(QKeySequence('Ctrl+q'))
        self.addAction(self.quitAction)

        self.traymenu.addAction('&Normal', self.showNormal, QKeySequence('Ctrl+n'))
        self.traymenu.addAction('Mi&nimize', self.showMinimized, QKeySequence('Ctrl+i'))
        self.traymenu.addAction('&Maximum', self.showMaximized, QKeySequence('Ctrl+m'))
        self.traymenu.addAction('&Quit',self.close, QKeySequence('Ctrl+q'))

        self.trayicon.setContextMenu(self.traymenu)

        self.ticon = QIcon('icon_dict2.ico')
        self.trayicon.setIcon(self.ticon)
        self.trayicon.setToolTip('YYDict')
        self.trayicon.activated.connect(self.on_systemTrayIcon_activated)
        self.traymsg_firstshow = True

        self.button1.clicked.connect(self.searchword)
        self.comboBox.activated.connect(self.searchword)
def run(manager, testing=False):
    logger.info("Creating trayicon...")
    # print(QIcon.themeSearchPaths())

    app = QApplication(sys.argv)

    # Without this, Ctrl+C will have no effect
    signal.signal(signal.SIGINT, exit)
    # Ensure cleanup happens on SIGTERM
    signal.signal(signal.SIGTERM, exit)

    timer = QtCore.QTimer()
    timer.start(100)  # You may change this if you wish.
    timer.timeout.connect(lambda: None)  # Let the interpreter run each 500 ms.

    if not QSystemTrayIcon.isSystemTrayAvailable():
        QMessageBox.critical(None, "Systray", "I couldn't detect any system tray on this system. Either get one or run the ActivityWatch modules from the console.")
        sys.exit(1)

    widget = QWidget()

    icon = QIcon(":/logo.png")
    trayIcon = TrayIcon(manager, icon, widget, testing=testing)
    trayIcon.show()

    trayIcon.showMessage("ActivityWatch", "ActivityWatch is starting up...")

    QApplication.setQuitOnLastWindowClosed(False)

    # Run the application, blocks until quit
    return app.exec_()
Beispiel #16
0
    def update_tray_icon(self, use_monochrome_icon):
        if not QSystemTrayIcon.isSystemTrayAvailable() or not self.tray_icon:
            return

        if use_monochrome_icon:
            self.tray_icon.setIcon(QIcon(QPixmap(get_image_path('monochrome_tribler.png'))))
        else:
            self.tray_icon.setIcon(QIcon(QPixmap(get_image_path('tribler.png'))))
        self.tray_icon.show()
Beispiel #17
0
 def __init__(self, parent=None):
     super(SystemTray, self).__init__(parent)
     self.tray_icon_menu = QMenu(self)
     self.tray_icon = QSystemTrayIcon(self)
     self.tray_icon.setContextMenu(self.tray_icon_menu)
     self.icon_management = IconManagement(self.tray_icon)
     self.connection_handler = ConnectionHandler(FREQUENCY_CHECK_MS, TIME_OUT_CALL_S, self)
     self.connection_handler.value_changed.connect(self.internet_connection)
     self.connection_handler.start()
Beispiel #18
0
 def createTrayIcon(self):
     self.quitAction = QAction('Quit', self)
     self.quitAction.triggered.connect(self.closeEvent)
     self.trayIconMenu = QMenu(self)
     self.trayIconMenu.addAction(self.quitAction)
     self.trayIcon = QSystemTrayIcon(self)
     self.trayIcon.setContextMenu(self.trayIconMenu)
     self.trayIcon.setIcon(QIcon(os.path.join('images', 'flower.png')))
     self.trayIcon.show()
Beispiel #19
0
    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)
Beispiel #20
0
    def __init__(self, parent=None):
        super(MainApp, self).__init__(parent)

        # Init notification
        notify2.init('webcam-manager')
        
        # Init tray icon
        self.tray_icon = QSystemTrayIcon(QIcon.fromTheme('camera-web'), parent)        
        self.initTrayIcon()
        self.tray_icon.show()        
Beispiel #21
0
 def createTrayIcon(self):
     self.trayIconMenu = QMenu(self)
     self.trayIconMenu.addAction(self.aboutShow)
     self.trayIconMenu.addAction(self.detailsShow)
     self.trayIconMenu.addAction(self.checkNow)
     self.trayIconMenu.addAction(self.restoreAction)
     self.trayIconMenu.addAction(self.quitAction)
     self.trayIcon = QSystemTrayIcon(self)
     self.trayIcon.setContextMenu(self.trayIconMenu)
     self.trayIcon.activated.connect(self.trayIconActivated)
Beispiel #22
0
 def setupTrayIcon(self):
     _icon = QIcon(os.path.join('web', 'static', 'img', 'Logo.144x144.png'))
     self.trayIcon = QSystemTrayIcon(_icon, self)
     menu = QMenu(self)
     menu.addAction("Show", self.show)
     menu.addAction("Hide", self.hide)
     menu.addAction("Exit", self.reallyClose)
     self.trayIcon.setContextMenu(menu)
     self.trayIcon.show()
     self.trayIcon.showMessage("Antix Printer Server", "Is running")
Beispiel #23
0
 def setupStatusIcon(self):
     icon = QIcon()
     icon.addPixmap(QPixmap(":/clock.svg"), QIcon.Normal, QIcon.Off)
     self.statusIcon = QSystemTrayIcon(self)
     self.statusIcon.setIcon(icon)
     self.statusIcon.activated.connect(lambda: self.hide()
                                       if self.isVisible()
                                       else self.show())
     self.statusIcon.setToolTip("Mark's Time Tracker")
     self.statusIcon.show()
Beispiel #24
0
 def inittray(self):
     menu = QMenu()
     menu.addAction("Add tile", self.mAddTile)
     menu.addAction("Add app", self.mAddDE)
     self.lmaction = menu.addAction("[ ] Layout mode", self.mLayoutMode)
     menu.addAction("Close", self.mClose)
     self.tray = QSystemTrayIcon(QIcon('icons/maintile.png'))
     self.tray.show()
     self.tray.setContextMenu(menu)
     self.tray.activated.connect(self.trayclicked)
     self.tray.show()
Beispiel #25
0
    def createTrayIcon(self):
        self.trayIconMenu = QMenu(self)
        self.trayIconMenu.addAction(self.openAction)
        self.trayIconMenu.addAction(self.settingsAction)
        self.trayIconMenu.addSeparator()
        self.trayIconMenu.addAction(self.takeBreakAction)
        self.trayIconMenu.addAction(self.pauseAction)
        self.trayIconMenu.addSeparator()
        self.trayIconMenu.addAction(self.quitAction)

        self.trayIcon = QSystemTrayIcon(self)
        self.trayIcon.setContextMenu(self.trayIconMenu)
Beispiel #26
0
 def gui(self):
     self.systray = QSystemTrayIcon()
     self.icon = QIcon(':/sansimera.png')
     self.systray.setIcon(self.icon)
     self.systray.setToolTip('Σαν σήμερα...')
     self.menu = QMenu()
     self.exitAction = QAction('&Έξοδος', self)
     self.refreshAction = QAction('&Ανανέωση', self)
     self.aboutAction = QAction('&Σχετικά', self)
     self.notification_interval = QAction('Ει&δοποίηση εορταζόντων', self)
     self.menu.addAction(self.notification_interval)
     self.menu.addAction(self.refreshAction)
     self.menu.addAction(self.aboutAction)
     self.menu.addAction(self.exitAction)
     self.systray.setContextMenu(self.menu)
     self.notification_interval.triggered.connect(self.interval_namedays)
     self.exitAction.triggered.connect(exit)
     self.refreshAction.triggered.connect(self.refresh)
     self.aboutAction.triggered.connect(self.about)
     self.browser = QTextBrowser()
     self.browser.setOpenExternalLinks(True)
     self.setGeometry(600, 500, 400, 300)
     self.setWindowIcon(self.icon)
     self.setWindowTitle('Σαν σήμερα...')
     self.setCentralWidget(self.browser)
     self.systray.show()
     self.systray.activated.connect(self.activate)
     self.browser.append('Λήψη...')
     nicon = QIcon(':/next')
     picon = QIcon(':/previous')
     ricon = QIcon(':/refresh')
     iicon = QIcon(':/info')
     qicon = QIcon(':/exit')
     inicon = QIcon(':/notifications')
     self.nextAction = QAction('Επόμενο', self)
     self.nextAction.setIcon(nicon)
     self.previousAction = QAction('Προηγούμενο', self)
     self.refreshAction.triggered.connect(self.refresh)
     self.nextAction.triggered.connect(self.nextItem)
     self.previousAction.triggered.connect(self.previousItem)
     self.previousAction.setIcon(picon)
     self.refreshAction.setIcon(ricon)
     self.exitAction.setIcon(qicon)
     self.aboutAction.setIcon(iicon)
     self.notification_interval.setIcon(inicon)
     controls = QToolBar()
     self.addToolBar(Qt.BottomToolBarArea, controls)
     controls.setObjectName('Controls')
     controls.addAction(self.previousAction)
     controls.addAction(self.nextAction)
     controls.addAction(self.refreshAction)
     self.restoreState(self.settings.value("MainWindow/State", QByteArray()))
     self.refresh()
Beispiel #27
0
class Systray(QObject):
    trayIconMenu = None

    def __init__(self, parent):
        super().__init__(parent)

        self.trayIconMenu = ContextMenu(None)

        icon = QIcon(":/image/thunder.ico")

        self.trayIcon = QSystemTrayIcon(self)
        self.trayIcon.setIcon(icon)
        self.trayIcon.setContextMenu(self.trayIconMenu)
        self.trayIcon.setVisible(True)

        self.trayIcon.activated.connect(self.slotSystrayActivated)

    @pyqtSlot(QSystemTrayIcon.ActivationReason)
    def slotSystrayActivated(self, reason):
        if reason == QSystemTrayIcon.Context:  # right
            pass
        elif reason == QSystemTrayIcon.MiddleClick:  # middle
            pass
        elif reason == QSystemTrayIcon.DoubleClick:  # double click
            pass
        elif reason == QSystemTrayIcon.Trigger:  # left
            if app.mainWin.isHidden() or app.mainWin.isMinimized():
                app.mainWin.restore()
            else:
                app.mainWin.minimize()
Beispiel #28
0
class Icon(QWidget):
    def __init__(self, app, newEntry, listWin):
        super().__init__()
        self.newEntry = newEntry
        self.listWin = listWin
        self.app = app
        self.initUI()

    def initUI(self):
        
        menu = QMenu()
        Ajouter = QAction(QIcon(''), '&Ajouter un tag', menu)
        Ajouter.triggered.connect(self.newEntry.show)
        menu.addAction(Ajouter)

        ouvrir = QAction(QIcon(''), '&Ouvrir', menu)
        ouvrir.triggered.connect(self.listWin.show)
        menu.addAction(ouvrir)

        Quitter = QAction(QIcon(''), '&Quitter', menu)
        Quitter.triggered.connect(self.app.exit)
        menu.addAction(Quitter)

        self.icon = QSystemTrayIcon()
        self.icon.setIcon(QIcon('./icone.png'))
        self.icon.setContextMenu(menu)
        self.icon.show()
Beispiel #29
0
 def setup_tray(self, isolated):
     Lumberjack.info('< MainApp > - -> (setup_tray)')
     self.trayIcon = QSystemTrayIcon(QIcon(':/app_icons/rc/PAT_icon.png'), self)
     self.trayMenu = QMenu(self)
     showAction = self.trayMenu.addAction("Open PAT")
     self.trayMenu.addSeparator()
     exitAction = self.trayMenu.addAction("Exit")
     self.trayIcon.setContextMenu(self.trayMenu)
     self.trayMenu.triggered.connect(self.handle_tray_event)
     self.trayIcon.activated.connect(self.handle_tray_event)
     self.trayIcon.show()
     if isolated:
         self.trayIcon.showMessage('PAT Service', 'PAT service is now running..')
Beispiel #30
0
 def enable(self):
     if not self._supported:
         return
     self._trayicon = QSystemTrayIcon()
     # On OS X, the context menu is activated with either mouse buttons,
     # and activation messages are still sent, so ignore those...
     if not sys.platform.startswith('darwin'):
         self._trayicon.activated.connect(self._on_activated)
     if self._context_menu is not None:
         self._trayicon.setContextMenu(self._context_menu)
     self._enabled = True
     self._update_state()
     self._trayicon.show()
Beispiel #31
0
class Reminder(ShowCenterMixin, OsxMenuMixin, TimerMixin):
    def __init__(self):
        self.app = QApplication(sys.argv)
        self.app.setQuitOnLastWindowClosed(False)
        self.app.setApplicationName(SPINBOX_WINDOW_TITLE)
        self.screen_height = self.app.primaryScreen().geometry().height()
        styleSheet = open(APP_STYLESHEET).read()
        if sys.platform == 'darwin':
            c = self.app.palette().color(QPalette.Highlight)
            color = 'rgba({},{},{},10%)'.format(c.red(), c.green(), c.blue())
            styleSheet = styleSheet.replace('palette(highlight)', color)
        self.app.setStyleSheet(styleSheet)

        self.setup_icon()
        self.setup_menu()
        self.setup_popup()
        self.setup_window()
        self.init_saved_alarm()
        self.idle()
        self.run()

    def on_button_clicked(self, *args):
        self.window.hide()
        h = self.spins[0].value()
        m = self.spins[1].value()
        s = self.spins[2].value()
        self.start_timer(h, m, s)

    def setup_window(self):
        self.window = QWidget()
        self.window.setProperty('objectName', 'window')
        self.window.setWindowTitle(SPINBOX_WINDOW_TITLE)
        self.window.setWindowFlags(self.window.windowFlags()
                                   | Qt.WindowStaysOnTopHint | Qt.Dialog)
        vlayout = QVBoxLayout(self.window)
        hlayout = QHBoxLayout()
        hlayout.setSpacing(SPINBOX_WINDOW_SPACING)

        self.spins = []
        for label in ['h', 'm', 's']:
            button_spin = ButtonSpinBox(label=label)
            spin = button_spin.spin
            self.spins.append(spin)
            hlayout.addLayout(button_spin)

        button = QPushButton(BUTTON_LABEL)
        button.setProperty('objectName', 'start')

        vlayout.addLayout(hlayout)
        vlayout.addWidget(button)

        button.clicked.connect(self.on_button_clicked)
        self.window.closeEvent = self.on_window_close

    def on_window_close(self, *args):
        self.window.hide()

    def setup_icon(self):
        self.icon_normal = QIcon(ALARM_PATH)
        self.icon = QSystemTrayIcon(self.icon_normal)
        self.icon_urgent = QIcon(ALARM_URGENT_PATH)
        self.icon_active = QIcon(ALARM_ACTIVE_PATH)
        self.icon.activated.connect(self.activate_menu)

    def setup_menu(self):
        self.menu = QMenu()
        clock_item = QWidgetAction(self.menu)
        self.clock = QPushButton(' ')
        self.clock.setProperty('objectName', 'menu')
        font = self.clock.font()
        font.setPixelSize(CLOCK_FONT_SIZE)
        self.clock.setFont(font)
        clock_item.setDefaultWidget(self.clock)
        self.clock.clicked.connect(self.activate_window)

        exit_item = QWidgetAction(self.menu)
        label = QPushButton(EXIT_LABEL)
        label.setProperty('objectName', 'menu')
        exit_item.setDefaultWidget(label)
        label.clicked.connect(self.activate_exit)

        self.menu.addAction(clock_item)
        self.menu.addAction(exit_item)

        if sys.platform == 'darwin':
            clock_item.button = self.clock
            exit_item.button = label
            self.menu.actions = [clock_item, exit_item]
            self.set_up_menu_macos(self.menu)

    def setup_popup(self):
        self.popup = QWidget()
        self.popup.setProperty('objectName', 'popup')
        vlayout = QVBoxLayout(self.popup)
        label = QLabel(ALERT_TEXT)
        vlayout.addWidget(label)
        self.popup.setWindowFlags(Qt.Sheet | Qt.Popup | Qt.WindowStaysOnTopHint
                                  | Qt.FramelessWindowHint)

        self.popup.mouseReleaseEvent = self.on_popup_release

    def on_popup_release(self, *args):
        self.popup.hide()

    def activate_menu(self, reason):
        if reason != QSystemTrayIcon.Trigger:
            return
        if self.icon.icon().cacheKey() == self.icon_urgent.cacheKey():
            self.clear_alarm()
            self.set_icon(self.icon_normal)
        self.update_clock()
        if sys.platform == 'darwin':
            self.on_menu_activated_macos()
            self.icon.setContextMenu(self.menu)
            self.icon.setContextMenu(None)
            return
        icon_pos = self.icon.geometry().bottomRight()
        width = min(self.menu.geometry().width(), 135)
        if icon_pos.y() > self.screen_height / 2:
            pos = icon_pos - QPoint(width, 40)
            self.menu.popup(pos, self.menu.actions()[-1])
        else:
            pos = icon_pos - QPoint(width, 0)
            self.menu.popup(pos)

    def activate_window(self, *args):
        if sys.platform == 'darwin':
            clock_action = self.menu.actions[0]
            clock_action.activate(clock_action.Trigger)
        self.show_center(self.window)

    def activate_exit(self, *args):
        os._exit(0)

    def set_icon(self, icon):
        self.icon.setIcon(icon)

    def update_clock_text(self, text):
        self.clock.setText(text)

    def show_popup(self):
        self.show_center(self.popup)

    def is_menu_visible(self):
        if sys.platform == 'darwin':
            return True
        return self.menu.isVisible()

    def run(self):
        timer = QTimer()
        timer.setInterval(1000)
        timer.timeout.connect(self.idle)
        timer.start(1000)
        self.icon.show()
        sys.exit(self.app.exec_())
Beispiel #32
0
    def __init__(self, core_args=None, core_env=None):
        QMainWindow.__init__(self)

        QCoreApplication.setOrganizationDomain("nl")
        QCoreApplication.setOrganizationName("TUDelft")
        QCoreApplication.setApplicationName("Tribler")
        QCoreApplication.setAttribute(Qt.AA_UseHighDpiPixmaps)

        self.gui_settings = QSettings()
        api_port = int(
            get_gui_setting(self.gui_settings, "api_port", DEFAULT_API_PORT))
        dispatcher.update_worker_settings(port=api_port)

        self.navigation_stack = []
        self.tribler_started = False
        self.tribler_settings = None
        self.debug_window = None
        self.core_manager = CoreManager(api_port)
        self.pending_requests = {}
        self.pending_uri_requests = []
        self.download_uri = None
        self.dialog = None
        self.new_version_dialog = None
        self.start_download_dialog_active = False
        self.request_mgr = None
        self.search_request_mgr = None
        self.search_suggestion_mgr = None
        self.selected_torrent_files = []
        self.vlc_available = True
        self.has_search_results = False
        self.last_search_query = None
        self.last_search_time = None
        self.start_time = time.time()
        self.exception_handler_called = False
        self.token_refresh_timer = None

        sys.excepthook = self.on_exception

        uic.loadUi(get_ui_file_path('mainwindow.ui'), self)
        TriblerRequestManager.window = self
        self.tribler_status_bar.hide()

        # Load dynamic widgets
        uic.loadUi(get_ui_file_path('torrent_channel_list_container.ui'),
                   self.channel_page_container)
        self.channel_torrents_list = self.channel_page_container.items_list
        self.channel_torrents_detail_widget = self.channel_page_container.details_tab_widget
        self.channel_torrents_detail_widget.initialize_details_widget()
        self.channel_torrents_list.itemClicked.connect(
            self.channel_page.clicked_item)

        uic.loadUi(get_ui_file_path('torrent_channel_list_container.ui'),
                   self.search_page_container)
        self.search_results_list = self.search_page_container.items_list
        self.search_torrents_detail_widget = self.search_page_container.details_tab_widget
        self.search_torrents_detail_widget.initialize_details_widget()
        self.search_results_list.itemClicked.connect(
            self.on_channel_item_click)
        self.search_results_list.itemClicked.connect(
            self.search_results_page.clicked_item)
        self.token_balance_widget.mouseReleaseEvent = self.on_token_balance_click

        def on_state_update(new_state):
            self.loading_text_label.setText(new_state)

        self.core_manager.core_state_update.connect(on_state_update)

        self.magnet_handler = MagnetHandler(self.window)
        QDesktopServices.setUrlHandler("magnet", self.magnet_handler,
                                       "on_open_magnet_link")

        self.debug_pane_shortcut = QShortcut(QKeySequence("Ctrl+d"), self)
        self.debug_pane_shortcut.activated.connect(
            self.clicked_menu_button_debug)

        # Remove the focus rect on OS X
        for widget in self.findChildren(QLineEdit) + self.findChildren(
                QListWidget) + self.findChildren(QTreeWidget):
            widget.setAttribute(Qt.WA_MacShowFocusRect, 0)

        self.menu_buttons = [
            self.left_menu_button_home, self.left_menu_button_search,
            self.left_menu_button_my_channel,
            self.left_menu_button_subscriptions,
            self.left_menu_button_video_player,
            self.left_menu_button_downloads, self.left_menu_button_discovered
        ]

        self.video_player_page.initialize_player()
        self.search_results_page.initialize_search_results_page()
        self.settings_page.initialize_settings_page()
        self.subscribed_channels_page.initialize()
        self.edit_channel_page.initialize_edit_channel_page()
        self.downloads_page.initialize_downloads_page()
        self.home_page.initialize_home_page()
        self.loading_page.initialize_loading_page()
        self.discovering_page.initialize_discovering_page()
        self.discovered_page.initialize_discovered_page()
        self.trust_page.initialize_trust_page()

        self.stackedWidget.setCurrentIndex(PAGE_LOADING)

        # Create the system tray icon
        if QSystemTrayIcon.isSystemTrayAvailable():
            self.tray_icon = QSystemTrayIcon()
            use_monochrome_icon = get_gui_setting(self.gui_settings,
                                                  "use_monochrome_icon",
                                                  False,
                                                  is_bool=True)
            self.update_tray_icon(use_monochrome_icon)

            # Create the tray icon menu
            menu = self.create_add_torrent_menu()
            show_downloads_action = QAction('Show downloads', self)
            show_downloads_action.triggered.connect(
                self.clicked_menu_button_downloads)
            token_balance_action = QAction('Show token balance', self)
            token_balance_action.triggered.connect(
                lambda: self.on_token_balance_click(None))
            quit_action = QAction('Quit Tribler', self)
            quit_action.triggered.connect(self.close_tribler)
            menu.addSeparator()
            menu.addAction(show_downloads_action)
            menu.addAction(token_balance_action)
            menu.addSeparator()
            menu.addAction(quit_action)
            self.tray_icon.setContextMenu(menu)
        else:
            self.tray_icon = None

        self.hide_left_menu_playlist()
        self.left_menu_button_debug.setHidden(True)
        self.top_menu_button.setHidden(True)
        self.left_menu.setHidden(True)
        self.token_balance_widget.setHidden(True)
        self.settings_button.setHidden(True)
        self.add_torrent_button.setHidden(True)
        self.top_search_bar.setHidden(True)

        # Set various icons
        self.top_menu_button.setIcon(QIcon(get_image_path('menu.png')))

        self.search_completion_model = QStringListModel()
        completer = QCompleter()
        completer.setModel(self.search_completion_model)
        completer.setCompletionMode(QCompleter.UnfilteredPopupCompletion)
        self.item_delegate = QStyledItemDelegate()
        completer.popup().setItemDelegate(self.item_delegate)
        completer.popup().setStyleSheet("""
        QListView {
            background-color: #404040;
        }

        QListView::item {
            color: #D0D0D0;
            padding-top: 5px;
            padding-bottom: 5px;
        }

        QListView::item:hover {
            background-color: #707070;
        }
        """)
        self.top_search_bar.setCompleter(completer)

        # Toggle debug if developer mode is enabled
        self.window().left_menu_button_debug.setHidden(not get_gui_setting(
            self.gui_settings, "debug", False, is_bool=True))

        # Start Tribler
        self.core_manager.start(core_args=core_args, core_env=core_env)

        self.core_manager.events_manager.received_search_result_channel.connect(
            self.search_results_page.received_search_result_channel)
        self.core_manager.events_manager.received_search_result_torrent.connect(
            self.search_results_page.received_search_result_torrent)
        self.core_manager.events_manager.torrent_finished.connect(
            self.on_torrent_finished)
        self.core_manager.events_manager.new_version_available.connect(
            self.on_new_version_available)
        self.core_manager.events_manager.tribler_started.connect(
            self.on_tribler_started)
        self.core_manager.events_manager.events_started.connect(
            self.on_events_started)
        self.core_manager.events_manager.low_storage_signal.connect(
            self.on_low_storage)

        # Install signal handler for ctrl+c events
        def sigint_handler(*_):
            self.close_tribler()

        signal.signal(signal.SIGINT, sigint_handler)

        self.installEventFilter(self.video_player_page)

        # Resize the window according to the settings
        center = QApplication.desktop().availableGeometry(self).center()
        pos = self.gui_settings.value(
            "pos",
            QPoint(center.x() - self.width() * 0.5,
                   center.y() - self.height() * 0.5))
        size = self.gui_settings.value("size", self.size())

        self.move(pos)
        self.resize(size)

        self.show()
class TreeMainControl(QObject):
    """Class to handle all global controls.

    Provides methods for all controls and stores local control objects.
    """
    def __init__(self, pathObjects, parent=None):
        """Initialize the main tree controls

        Arguments:
            pathObjects -- a list of file objects to open
            parent -- the parent QObject if given
        """
        super().__init__(parent)
        self.localControls = []
        self.activeControl = None
        self.trayIcon = None
        self.isTrayMinimized = False
        self.configDialog = None
        self.sortDialog = None
        self.numberingDialog = None
        self.findTextDialog = None
        self.findConditionDialog = None
        self.findReplaceDialog = None
        self.filterTextDialog = None
        self.filterConditionDialog = None
        self.basicHelpView = None
        self.passwords = {}
        self.creatingLocalControlFlag = False
        globalref.mainControl = self
        self.allActions = {}
        try:
            # check for existing TreeLine session
            socket = QLocalSocket()
            socket.connectToServer('treeline3-session', QIODevice.WriteOnly)
            # if found, send files to open and exit TreeLine
            if socket.waitForConnected(1000):
                socket.write(
                    bytes(repr([str(path) for path in pathObjects]), 'utf-8'))
                if socket.waitForBytesWritten(1000):
                    socket.close()
                    sys.exit(0)
            # start local server to listen for attempt to start new session
            self.serverSocket = QLocalServer()
            # remove any old servers still around after a crash in linux
            self.serverSocket.removeServer('treeline3-session')
            self.serverSocket.listen('treeline3-session')
            self.serverSocket.newConnection.connect(self.getSocket)
        except AttributeError:
            print(_('Warning:  Could not create local socket'))
        mainVersion = '.'.join(__version__.split('.')[:2])
        globalref.genOptions = options.Options('general', 'TreeLine',
                                               mainVersion, 'bellz')
        optiondefaults.setGenOptionDefaults(globalref.genOptions)
        globalref.miscOptions = options.Options('misc')
        optiondefaults.setMiscOptionDefaults(globalref.miscOptions)
        globalref.histOptions = options.Options('history')
        optiondefaults.setHistOptionDefaults(globalref.histOptions)
        globalref.toolbarOptions = options.Options('toolbar')
        optiondefaults.setToolbarOptionDefaults(globalref.toolbarOptions)
        globalref.keyboardOptions = options.Options('keyboard')
        optiondefaults.setKeyboardOptionDefaults(globalref.keyboardOptions)
        try:
            globalref.genOptions.readFile()
            globalref.miscOptions.readFile()
            globalref.histOptions.readFile()
            globalref.toolbarOptions.readFile()
            globalref.keyboardOptions.readFile()
        except IOError:
            errorDir = options.Options.basePath
            if not errorDir:
                errorDir = _('missing directory')
            QMessageBox.warning(
                None, 'TreeLine',
                _('Error - could not write config file to {}').format(
                    errorDir))
            options.Options.basePath = None
        iconPathList = self.findResourcePaths('icons', iconPath)
        globalref.toolIcons = icondict.IconDict(
            [path / 'toolbar' for path in iconPathList],
            ['', '32x32', '16x16'])
        globalref.toolIcons.loadAllIcons()
        windowIcon = globalref.toolIcons.getIcon('treelogo')
        if windowIcon:
            QApplication.setWindowIcon(windowIcon)
        globalref.treeIcons = icondict.IconDict(iconPathList, ['', 'tree'])
        icon = globalref.treeIcons.getIcon('default')
        qApp.setStyle(QStyleFactory.create('Fusion'))
        self.colorSet = colorset.ColorSet()
        if globalref.miscOptions['ColorTheme'] != 'system':
            self.colorSet.setAppColors()
        self.recentFiles = recentfiles.RecentFileList()
        if globalref.genOptions['AutoFileOpen'] and not pathObjects:
            recentPath = self.recentFiles.firstPath()
            if recentPath:
                pathObjects = [recentPath]
        self.setupActions()
        self.systemFont = QApplication.font()
        self.updateAppFont()
        if globalref.genOptions['MinToSysTray']:
            self.createTrayIcon()
        qApp.focusChanged.connect(self.updateActionsAvail)
        if pathObjects:
            for pathObj in pathObjects:
                self.openFile(pathObj, True)
        else:
            self.createLocalControl()

    def getSocket(self):
        """Open a socket from an attempt to open a second Treeline instance.

        Opens the file (or raise and focus if open) in this instance.
        """
        socket = self.serverSocket.nextPendingConnection()
        if socket and socket.waitForReadyRead(1000):
            data = str(socket.readAll(), 'utf-8')
            try:
                paths = ast.literal_eval(data)
                if paths:
                    for path in paths:
                        pathObj = pathlib.Path(path)
                        if pathObj != self.activeControl.filePathObj:
                            self.openFile(pathObj, True)
                        else:
                            self.activeControl.activeWindow.activateAndRaise()
                else:
                    self.activeControl.activeWindow.activateAndRaise()
            except (SyntaxError, ValueError, TypeError, RuntimeError):
                pass

    def findResourcePaths(self, resourceName, preferredPath=''):
        """Return list of potential non-empty pathlib objects for the resource.

        List includes preferred, module and user option paths.
        Arguments:
            resourceName -- the typical name of the resource directory
            preferredPath -- add this as the second path if given
        """
        # use abspath() - pathlib's resolve() can be buggy with network drives
        modPath = pathlib.Path(os.path.abspath(sys.path[0]))
        if modPath.is_file():
            modPath = modPath.parent  # for frozen binary
        pathList = [modPath / '..' / resourceName, modPath / resourceName]
        if options.Options.basePath:
            basePath = pathlib.Path(options.Options.basePath)
            pathList.insert(0, basePath / resourceName)
        if preferredPath:
            pathList.insert(1, pathlib.Path(preferredPath))
        return [
            pathlib.Path(os.path.abspath(str(path))) for path in pathList
            if path.is_dir() and list(path.iterdir())
        ]

    def findResourceFile(self, fileName, resourceName, preferredPath=''):
        """Return a path object for a resource file.

        Add a language code before the extension if it exists.
        Arguments:
            fileName -- the name of the file to find
            resourceName -- the typical name of the resource directory
            preferredPath -- search this path first if given
        """
        fileList = [fileName]
        if globalref.lang and globalref.lang != 'C':
            fileList[0:0] = [
                fileName.replace('.', '_{0}.'.format(globalref.lang)),
                fileName.replace('.', '_{0}.'.format(globalref.lang[:2]))
            ]
        for fileName in fileList:
            for path in self.findResourcePaths(resourceName, preferredPath):
                if (path / fileName).is_file():
                    return path / fileName
        return None

    def defaultPathObj(self, dirOnly=False):
        """Return a reasonable default file path object.

        Used for open, save-as, import and export.
        Arguments:
            dirOnly -- if True, do not include basename of file
        """
        pathObj = None
        if self.activeControl:
            pathObj = self.activeControl.filePathObj
        if not pathObj:
            pathObj = self.recentFiles.firstDir()
            if not pathObj:
                pathObj = pathlib.Path.home()
        if dirOnly:
            pathObj = pathObj.parent
        return pathObj

    def openFile(self,
                 pathObj,
                 forceNewWindow=False,
                 checkModified=False,
                 importOnFail=True):
        """Open the file given by path if not already open.

        If already open in a different window, focus and raise the window.
        Arguments:
            pathObj -- the path object to read
            forceNewWindow -- if True, use a new window regardless of option
            checkModified -- if True & not new win, prompt if file modified
            importOnFail -- if True, prompts for import on non-TreeLine files
        """
        match = [
            control for control in self.localControls
            if pathObj == control.filePathObj
        ]
        if match and self.activeControl not in match:
            control = match[0]
            control.activeWindow.activateAndRaise()
            self.updateLocalControlRef(control)
            return
        if checkModified and not (forceNewWindow
                                  or globalref.genOptions['OpenNewWindow']
                                  or self.activeControl.checkSaveChanges()):
            return
        if not self.checkAutoSave(pathObj):
            if not self.localControls:
                self.createLocalControl()
            return
        QApplication.setOverrideCursor(Qt.WaitCursor)
        try:
            self.createLocalControl(pathObj, None, forceNewWindow)
            self.recentFiles.addItem(pathObj)
            if not (globalref.genOptions['SaveTreeStates'] and
                    self.recentFiles.retrieveTreeState(self.activeControl)):
                self.activeControl.expandRootNodes()
                self.activeControl.selectRootSpot()
            QApplication.restoreOverrideCursor()
        except IOError:
            QApplication.restoreOverrideCursor()
            QMessageBox.warning(
                QApplication.activeWindow(), 'TreeLine',
                _('Error - could not read file {0}').format(pathObj))
            self.recentFiles.removeItem(pathObj)
        except (ValueError, KeyError, TypeError):
            fileObj = pathObj.open('rb')
            fileObj, encrypted = self.decryptFile(fileObj)
            if not fileObj:
                if not self.localControls:
                    self.createLocalControl()
                QApplication.restoreOverrideCursor()
                return
            fileObj, compressed = self.decompressFile(fileObj)
            if compressed or encrypted:
                try:
                    textFileObj = io.TextIOWrapper(fileObj, encoding='utf-8')
                    self.createLocalControl(textFileObj, None, forceNewWindow)
                    fileObj.close()
                    textFileObj.close()
                    self.recentFiles.addItem(pathObj)
                    if not (globalref.genOptions['SaveTreeStates']
                            and self.recentFiles.retrieveTreeState(
                                self.activeControl)):
                        self.activeControl.expandRootNodes()
                        self.activeControl.selectRootSpot()
                    self.activeControl.compressed = compressed
                    self.activeControl.encrypted = encrypted
                    QApplication.restoreOverrideCursor()
                    return
                except (ValueError, KeyError, TypeError):
                    pass
            fileObj.close()
            importControl = imports.ImportControl(pathObj)
            structure = importControl.importOldTreeLine()
            if structure:
                self.createLocalControl(pathObj, structure, forceNewWindow)
                self.activeControl.printData.readData(
                    importControl.treeLineRootAttrib)
                self.recentFiles.addItem(pathObj)
                self.activeControl.expandRootNodes()
                self.activeControl.imported = True
                QApplication.restoreOverrideCursor()
                return
            QApplication.restoreOverrideCursor()
            if importOnFail:
                importControl = imports.ImportControl(pathObj)
                structure = importControl.interactiveImport(True)
                if structure:
                    self.createLocalControl(pathObj, structure, forceNewWindow)
                    self.activeControl.imported = True
                    return
            else:
                QMessageBox.warning(
                    QApplication.activeWindow(), 'TreeLine',
                    _('Error - invalid TreeLine file {0}').format(pathObj))
                self.recentFiles.removeItem(pathObj)
        if not self.localControls:
            self.createLocalControl()

    def decryptFile(self, fileObj):
        """Check for encryption and decrypt the fileObj if needed.

        Return a tuple of the file object and True if it was encrypted.
        Return None for the file object if the user cancels.
        Arguments:
            fileObj -- the file object to check and decrypt
        """
        if fileObj.read(len(encryptPrefix)) != encryptPrefix:
            fileObj.seek(0)
            return (fileObj, False)
        fileContents = fileObj.read()
        fileName = fileObj.name
        fileObj.close()
        while True:
            pathObj = pathlib.Path(fileName)
            password = self.passwords.get(pathObj, '')
            if not password:
                QApplication.restoreOverrideCursor()
                dialog = miscdialogs.PasswordDialog(
                    False, pathObj.name, QApplication.activeWindow())
                if dialog.exec_() != QDialog.Accepted:
                    return (None, True)
                QApplication.setOverrideCursor(Qt.WaitCursor)
                password = dialog.password
                if miscdialogs.PasswordDialog.remember:
                    self.passwords[pathObj] = password
            try:
                text = p3.p3_decrypt(fileContents, password.encode())
                fileIO = io.BytesIO(text)
                fileIO.name = fileName
                return (fileIO, True)
            except p3.CryptError:
                try:
                    del self.passwords[pathObj]
                except KeyError:
                    pass

    def decompressFile(self, fileObj):
        """Check for compression and decompress the fileObj if needed.

        Return a tuple of the file object and True if it was compressed.
        Arguments:
            fileObj -- the file object to check and decompress
        """
        prefix = fileObj.read(2)
        fileObj.seek(0)
        if prefix != b'\037\213':
            return (fileObj, False)
        try:
            newFileObj = gzip.GzipFile(fileobj=fileObj)
        except zlib.error:
            return (fileObj, False)
        newFileObj.name = fileObj.name
        return (newFileObj, True)

    def checkAutoSave(self, pathObj):
        """Check for presence of auto save file & prompt user.

        Return True if OK to contimue, False if aborting or already loaded.
        Arguments:
            pathObj -- the base path object to search for a backup
        """
        if not globalref.genOptions['AutoSaveMinutes']:
            return True
        basePath = pathObj
        pathObj = pathlib.Path(str(pathObj) + '~')
        if not pathObj.is_file():
            return True
        msgBox = QMessageBox(
            QMessageBox.Information, 'TreeLine',
            _('Backup file "{}" exists.\nA previous '
              'session may have crashed').format(pathObj),
            QMessageBox.NoButton, QApplication.activeWindow())
        restoreButton = msgBox.addButton(_('&Restore Backup'),
                                         QMessageBox.ApplyRole)
        deleteButton = msgBox.addButton(_('&Delete Backup'),
                                        QMessageBox.DestructiveRole)
        cancelButton = msgBox.addButton(_('&Cancel File Open'),
                                        QMessageBox.RejectRole)
        msgBox.exec_()
        if msgBox.clickedButton() == restoreButton:
            self.openFile(pathObj)
            if self.activeControl.filePathObj != pathObj:
                return False
            try:
                basePath.unlink()
                pathObj.rename(basePath)
            except OSError:
                QMessageBox.warning(
                    QApplication.activeWindow(), 'TreeLine',
                    _('Error - could not rename "{0}" to "{1}"').format(
                        pathObj, basePath))
                return False
            self.activeControl.filePathObj = basePath
            self.activeControl.updateWindowCaptions()
            self.recentFiles.removeItem(pathObj)
            self.recentFiles.addItem(basePath)
            return False
        elif msgBox.clickedButton() == deleteButton:
            try:
                pathObj.unlink()
            except OSError:
                QMessageBox.warning(
                    QApplication.activeWindow(), 'TreeLine',
                    _('Error - could not remove backup file {}').format(
                        pathObj))
        else:  # cancel button
            return False
        return True

    def createLocalControl(self,
                           pathObj=None,
                           treeStruct=None,
                           forceNewWindow=False):
        """Create a new local control object and add it to the list.

        Use an imported structure if given or open the file if path is given.
        Arguments:
            pathObj -- the path object or file object for the control to open
            treeStruct -- the imported structure to use
            forceNewWindow -- if True, use a new window regardless of option
        """
        self.creatingLocalControlFlag = True
        localControl = treelocalcontrol.TreeLocalControl(
            self.allActions, pathObj, treeStruct, forceNewWindow)
        localControl.controlActivated.connect(self.updateLocalControlRef)
        localControl.controlClosed.connect(self.removeLocalControlRef)
        self.localControls.append(localControl)
        self.updateLocalControlRef(localControl)
        self.creatingLocalControlFlag = False
        localControl.updateRightViews()
        localControl.updateCommandsAvail()

    def updateLocalControlRef(self, localControl):
        """Set the given local control as active.

        Called by signal from a window becoming active.
        Also updates non-modal dialogs.
        Arguments:
            localControl -- the new active local control
        """
        if localControl != self.activeControl:
            self.activeControl = localControl
            if self.configDialog and self.configDialog.isVisible():
                self.configDialog.setRefs(self.activeControl)

    def removeLocalControlRef(self, localControl):
        """Remove ref to local control based on a closing signal.

        Also do application exit clean ups if last control closing.
        Arguments:
            localControl -- the local control that is closing
        """
        try:
            self.localControls.remove(localControl)
        except ValueError:
            return  # skip for unreporducible bug - odd race condition?
        if globalref.genOptions['SaveTreeStates']:
            self.recentFiles.saveTreeState(localControl)
        if not self.localControls and not self.creatingLocalControlFlag:
            if globalref.genOptions['SaveWindowGeom']:
                localControl.windowList[0].saveWindowGeom()
            else:
                localControl.windowList[0].resetWindowGeom()
            self.recentFiles.writeItems()
            localControl.windowList[0].saveToolbarPosition()
            globalref.histOptions.writeFile()
            if self.trayIcon:
                self.trayIcon.hide()
            # stop listening for session connections
            try:
                self.serverSocket.close()
                del self.serverSocket
            except AttributeError:
                pass
        if self.localControls:
            # make sure a window is active (may not be focused), to avoid
            # bugs due to a deleted current window
            newControl = self.localControls[0]
            newControl.setActiveWin(newControl.windowList[0])
        localControl.deleteLater()

    def createTrayIcon(self):
        """Create a new system tray icon if not already created.
        """
        if QSystemTrayIcon.isSystemTrayAvailable:
            if not self.trayIcon:
                self.trayIcon = QSystemTrayIcon(qApp.windowIcon(), qApp)
                self.trayIcon.activated.connect(self.toggleTrayShow)
            self.trayIcon.show()

    def trayMinimize(self):
        """Minimize to tray based on window minimize signal.
        """
        if self.trayIcon and QSystemTrayIcon.isSystemTrayAvailable:
            # skip minimize to tray if not all windows minimized
            for control in self.localControls:
                for window in control.windowList:
                    if not window.isMinimized():
                        return
            for control in self.localControls:
                for window in control.windowList:
                    window.hide()
            self.isTrayMinimized = True

    def toggleTrayShow(self):
        """Toggle show and hide application based on system tray icon click.
        """
        if self.isTrayMinimized:
            for control in self.localControls:
                for window in control.windowList:
                    window.show()
                    window.showNormal()
            self.activeControl.activeWindow.treeView.setFocus()
        else:
            for control in self.localControls:
                for window in control.windowList:
                    window.hide()
        self.isTrayMinimized = not self.isTrayMinimized

    def updateConfigDialog(self):
        """Update the config dialog for changes if it exists.
        """
        if self.configDialog:
            self.configDialog.reset()

    def currentStatusBar(self):
        """Return the status bar from the current main window.
        """
        return self.activeControl.activeWindow.statusBar()

    def windowActions(self):
        """Return a list of window menu actions from each local control.
        """
        actions = []
        for control in self.localControls:
            actions.extend(
                control.windowActions(
                    len(actions) + 1, control == self.activeControl))
        return actions

    def updateActionsAvail(self, oldWidget, newWidget):
        """Update command availability based on focus changes.

        Arguments:
            oldWidget -- the previously focused widget
            newWidget -- the newly focused widget
        """
        self.allActions['FormatSelectAll'].setEnabled(
            hasattr(newWidget, 'selectAll')
            and not hasattr(newWidget, 'editTriggers'))

    def setupActions(self):
        """Add the actions for contols at the global level.
        """
        fileNewAct = QAction(_('&New...'),
                             self,
                             toolTip=_('New File'),
                             statusTip=_('Start a new file'))
        fileNewAct.triggered.connect(self.fileNew)
        self.allActions['FileNew'] = fileNewAct

        fileOpenAct = QAction(_('&Open...'),
                              self,
                              toolTip=_('Open File'),
                              statusTip=_('Open a file from disk'))
        fileOpenAct.triggered.connect(self.fileOpen)
        self.allActions['FileOpen'] = fileOpenAct

        fileSampleAct = QAction(_('Open Sa&mple...'),
                                self,
                                toolTip=_('Open Sample'),
                                statusTip=_('Open a sample file'))
        fileSampleAct.triggered.connect(self.fileOpenSample)
        self.allActions['FileOpenSample'] = fileSampleAct

        fileImportAct = QAction(_('&Import...'),
                                self,
                                statusTip=_('Open a non-TreeLine file'))
        fileImportAct.triggered.connect(self.fileImport)
        self.allActions['FileImport'] = fileImportAct

        fileQuitAct = QAction(_('&Quit'),
                              self,
                              statusTip=_('Exit the application'))
        fileQuitAct.triggered.connect(self.fileQuit)
        self.allActions['FileQuit'] = fileQuitAct

        dataConfigAct = QAction(
            _('&Configure Data Types...'),
            self,
            statusTip=_('Modify data types, fields & output lines'),
            checkable=True)
        dataConfigAct.triggered.connect(self.dataConfigDialog)
        self.allActions['DataConfigType'] = dataConfigAct

        dataVisualConfigAct = QAction(
            _('Show C&onfiguration Structure...'),
            self,
            statusTip=_('Show read-only visualization of type structure'))
        dataVisualConfigAct.triggered.connect(self.dataVisualConfig)
        self.allActions['DataVisualConfig'] = dataVisualConfigAct

        dataSortAct = QAction(_('Sor&t Nodes...'),
                              self,
                              statusTip=_('Define node sort operations'),
                              checkable=True)
        dataSortAct.triggered.connect(self.dataSortDialog)
        self.allActions['DataSortNodes'] = dataSortAct

        dataNumberingAct = QAction(_('Update &Numbering...'),
                                   self,
                                   statusTip=_('Update node numbering fields'),
                                   checkable=True)
        dataNumberingAct.triggered.connect(self.dataNumberingDialog)
        self.allActions['DataNumbering'] = dataNumberingAct

        toolsFindTextAct = QAction(
            _('&Find Text...'),
            self,
            statusTip=_('Find text in node titles & data'),
            checkable=True)
        toolsFindTextAct.triggered.connect(self.toolsFindTextDialog)
        self.allActions['ToolsFindText'] = toolsFindTextAct

        toolsFindConditionAct = QAction(
            _('&Conditional Find...'),
            self,
            statusTip=_('Use field conditions to find nodes'),
            checkable=True)
        toolsFindConditionAct.triggered.connect(self.toolsFindConditionDialog)
        self.allActions['ToolsFindCondition'] = toolsFindConditionAct

        toolsFindReplaceAct = QAction(
            _('Find and &Replace...'),
            self,
            statusTip=_('Replace text strings in node data'),
            checkable=True)
        toolsFindReplaceAct.triggered.connect(self.toolsFindReplaceDialog)
        self.allActions['ToolsFindReplace'] = toolsFindReplaceAct

        toolsFilterTextAct = QAction(
            _('&Text Filter...'),
            self,
            statusTip=_('Filter nodes to only show text matches'),
            checkable=True)
        toolsFilterTextAct.triggered.connect(self.toolsFilterTextDialog)
        self.allActions['ToolsFilterText'] = toolsFilterTextAct

        toolsFilterConditionAct = QAction(
            _('C&onditional Filter...'),
            self,
            statusTip=_('Use field conditions to filter nodes'),
            checkable=True)
        toolsFilterConditionAct.triggered.connect(
            self.toolsFilterConditionDialog)
        self.allActions['ToolsFilterCondition'] = toolsFilterConditionAct

        toolsGenOptionsAct = QAction(
            _('&General Options...'),
            self,
            statusTip=_('Set user preferences for all files'))
        toolsGenOptionsAct.triggered.connect(self.toolsGenOptions)
        self.allActions['ToolsGenOptions'] = toolsGenOptionsAct

        toolsShortcutAct = QAction(_('Set &Keyboard Shortcuts...'),
                                   self,
                                   statusTip=_('Customize keyboard commands'))
        toolsShortcutAct.triggered.connect(self.toolsCustomShortcuts)
        self.allActions['ToolsShortcuts'] = toolsShortcutAct

        toolsToolbarAct = QAction(_('C&ustomize Toolbars...'),
                                  self,
                                  statusTip=_('Customize toolbar buttons'))
        toolsToolbarAct.triggered.connect(self.toolsCustomToolbars)
        self.allActions['ToolsToolbars'] = toolsToolbarAct

        toolsFontsAct = QAction(
            _('Customize Fo&nts...'),
            self,
            statusTip=_('Customize fonts in various views'))
        toolsFontsAct.triggered.connect(self.toolsCustomFonts)
        self.allActions['ToolsFonts'] = toolsFontsAct

        toolsColorsAct = QAction(
            _('Custo&mize Colors...'),
            self,
            statusTip=_('Customize GUI colors and themes'))
        toolsColorsAct.triggered.connect(self.toolsCustomColors)
        self.allActions['ToolsColors'] = toolsColorsAct

        formatSelectAllAct = QAction(
            _('&Select All'),
            self,
            statusTip=_('Select all text in an editor'))
        formatSelectAllAct.setEnabled(False)
        formatSelectAllAct.triggered.connect(self.formatSelectAll)
        self.allActions['FormatSelectAll'] = formatSelectAllAct

        helpBasicAct = QAction(_('&Basic Usage...'),
                               self,
                               statusTip=_('Display basic usage instructions'))
        helpBasicAct.triggered.connect(self.helpViewBasic)
        self.allActions['HelpBasic'] = helpBasicAct

        helpFullAct = QAction(
            _('&Full Documentation...'),
            self,
            statusTip=_('Open a TreeLine file with full documentation'))
        helpFullAct.triggered.connect(self.helpViewFull)
        self.allActions['HelpFull'] = helpFullAct

        helpAboutAct = QAction(
            _('&About TreeLine...'),
            self,
            statusTip=_('Display version info about this program'))
        helpAboutAct.triggered.connect(self.helpAbout)
        self.allActions['HelpAbout'] = helpAboutAct

        for name, action in self.allActions.items():
            icon = globalref.toolIcons.getIcon(name.lower())
            if icon:
                action.setIcon(icon)
            key = globalref.keyboardOptions[name]
            if not key.isEmpty():
                action.setShortcut(key)

    def fileNew(self):
        """Start a new blank file.
        """
        if (globalref.genOptions['OpenNewWindow']
                or self.activeControl.checkSaveChanges()):
            searchPaths = self.findResourcePaths('templates', templatePath)
            if searchPaths:
                dialog = miscdialogs.TemplateFileDialog(
                    _('New File'), _('&Select Template'), searchPaths)
                if dialog.exec_() == QDialog.Accepted:
                    self.createLocalControl(dialog.selectedPath())
                    self.activeControl.filePathObj = None
                    self.activeControl.updateWindowCaptions()
                    self.activeControl.expandRootNodes()
            else:
                self.createLocalControl()
            self.activeControl.selectRootSpot()

    def fileOpen(self):
        """Prompt for a filename and open it.
        """
        if (globalref.genOptions['OpenNewWindow']
                or self.activeControl.checkSaveChanges()):
            filters = ';;'.join((globalref.fileFilters['trlnopen'],
                                 globalref.fileFilters['all']))
            fileName, selFilter = QFileDialog.getOpenFileName(
                QApplication.activeWindow(), _('TreeLine - Open File'),
                str(self.defaultPathObj(True)), filters)
            if fileName:
                self.openFile(pathlib.Path(fileName))

    def fileOpenSample(self):
        """Open a sample file from the doc directories.
        """
        if (globalref.genOptions['OpenNewWindow']
                or self.activeControl.checkSaveChanges()):
            searchPaths = self.findResourcePaths('samples', samplePath)
            dialog = miscdialogs.TemplateFileDialog(_('Open Sample File'),
                                                    _('&Select Sample'),
                                                    searchPaths, False)
            if dialog.exec_() == QDialog.Accepted:
                self.createLocalControl(dialog.selectedPath())
                name = dialog.selectedName() + '.trln'
                self.activeControl.filePathObj = pathlib.Path(name)
                self.activeControl.updateWindowCaptions()
                self.activeControl.expandRootNodes()
                self.activeControl.imported = True

    def fileImport(self):
        """Prompt for an import type, then a file to import.
        """
        importControl = imports.ImportControl()
        structure = importControl.interactiveImport()
        if structure:
            self.createLocalControl(importControl.pathObj, structure)
            if importControl.treeLineRootAttrib:
                self.activeControl.printData.readData(
                    importControl.treeLineRootAttrib)
            self.activeControl.imported = True

    def fileQuit(self):
        """Close all windows to exit the applications.
        """
        for control in self.localControls[:]:
            control.closeWindows()

    def dataConfigDialog(self, show):
        """Show or hide the non-modal data config dialog.

        Arguments:
            show -- true if dialog should be shown, false to hide it
        """
        if show:
            if not self.configDialog:
                self.configDialog = configdialog.ConfigDialog()
                dataConfigAct = self.allActions['DataConfigType']
                self.configDialog.dialogShown.connect(dataConfigAct.setChecked)
            self.configDialog.setRefs(self.activeControl, True)
            self.configDialog.show()
        else:
            self.configDialog.close()

    def dataVisualConfig(self):
        """Show a TreeLine file to visualize the config structure.
        """
        structure = (
            self.activeControl.structure.treeFormats.visualConfigStructure(
                str(self.activeControl.filePathObj)))
        self.createLocalControl(treeStruct=structure, forceNewWindow=True)
        self.activeControl.filePathObj = pathlib.Path('structure.trln')
        self.activeControl.updateWindowCaptions()
        self.activeControl.expandRootNodes()
        self.activeControl.imported = True
        win = self.activeControl.activeWindow
        win.rightTabs.setCurrentWidget(win.outputSplitter)

    def dataSortDialog(self, show):
        """Show or hide the non-modal data sort nodes dialog.

        Arguments:
            show -- true if dialog should be shown, false to hide it
        """
        if show:
            if not self.sortDialog:
                self.sortDialog = miscdialogs.SortDialog()
                dataSortAct = self.allActions['DataSortNodes']
                self.sortDialog.dialogShown.connect(dataSortAct.setChecked)
            self.sortDialog.show()
        else:
            self.sortDialog.close()

    def dataNumberingDialog(self, show):
        """Show or hide the non-modal update node numbering dialog.

        Arguments:
            show -- true if dialog should be shown, false to hide it
        """
        if show:
            if not self.numberingDialog:
                self.numberingDialog = miscdialogs.NumberingDialog()
                dataNumberingAct = self.allActions['DataNumbering']
                self.numberingDialog.dialogShown.connect(
                    dataNumberingAct.setChecked)
            self.numberingDialog.show()
            if not self.numberingDialog.checkForNumberingFields():
                self.numberingDialog.close()
        else:
            self.numberingDialog.close()

    def toolsFindTextDialog(self, show):
        """Show or hide the non-modal find text dialog.

        Arguments:
            show -- true if dialog should be shown
        """
        if show:
            if not self.findTextDialog:
                self.findTextDialog = miscdialogs.FindFilterDialog()
                toolsFindTextAct = self.allActions['ToolsFindText']
                self.findTextDialog.dialogShown.connect(
                    toolsFindTextAct.setChecked)
            self.findTextDialog.selectAllText()
            self.findTextDialog.show()
        else:
            self.findTextDialog.close()

    def toolsFindConditionDialog(self, show):
        """Show or hide the non-modal conditional find dialog.

        Arguments:
            show -- true if dialog should be shown
        """
        if show:
            if not self.findConditionDialog:
                dialogType = conditional.FindDialogType.findDialog
                self.findConditionDialog = (conditional.ConditionDialog(
                    dialogType, _('Conditional Find')))
                toolsFindConditionAct = self.allActions['ToolsFindCondition']
                (self.findConditionDialog.dialogShown.connect(
                    toolsFindConditionAct.setChecked))
            else:
                self.findConditionDialog.loadTypeNames()
            self.findConditionDialog.show()
        else:
            self.findConditionDialog.close()

    def toolsFindReplaceDialog(self, show):
        """Show or hide the non-modal find and replace text dialog.

        Arguments:
            show -- true if dialog should be shown
        """
        if show:
            if not self.findReplaceDialog:
                self.findReplaceDialog = miscdialogs.FindReplaceDialog()
                toolsFindReplaceAct = self.allActions['ToolsFindReplace']
                self.findReplaceDialog.dialogShown.connect(
                    toolsFindReplaceAct.setChecked)
            else:
                self.findReplaceDialog.loadTypeNames()
            self.findReplaceDialog.show()
        else:
            self.findReplaceDialog.close()

    def toolsFilterTextDialog(self, show):
        """Show or hide the non-modal filter text dialog.

        Arguments:
            show -- true if dialog should be shown
        """
        if show:
            if not self.filterTextDialog:
                self.filterTextDialog = miscdialogs.FindFilterDialog(True)
                toolsFilterTextAct = self.allActions['ToolsFilterText']
                self.filterTextDialog.dialogShown.connect(
                    toolsFilterTextAct.setChecked)
            self.filterTextDialog.selectAllText()
            self.filterTextDialog.show()
        else:
            self.filterTextDialog.close()

    def toolsFilterConditionDialog(self, show):
        """Show or hide the non-modal conditional filter dialog.

        Arguments:
            show -- true if dialog should be shown
        """
        if show:
            if not self.filterConditionDialog:
                dialogType = conditional.FindDialogType.filterDialog
                self.filterConditionDialog = (conditional.ConditionDialog(
                    dialogType, _('Conditional Filter')))
                toolsFilterConditionAct = (
                    self.allActions['ToolsFilterCondition'])
                (self.filterConditionDialog.dialogShown.connect(
                    toolsFilterConditionAct.setChecked))
            else:
                self.filterConditionDialog.loadTypeNames()
            self.filterConditionDialog.show()
        else:
            self.filterConditionDialog.close()

    def toolsGenOptions(self):
        """Set general user preferences for all files.
        """
        oldAutoSaveMinutes = globalref.genOptions['AutoSaveMinutes']
        dialog = options.OptionDialog(globalref.genOptions,
                                      QApplication.activeWindow())
        dialog.setWindowTitle(_('General Options'))
        if (dialog.exec_() == QDialog.Accepted
                and globalref.genOptions.modified):
            globalref.genOptions.writeFile()
            self.recentFiles.updateOptions()
            if globalref.genOptions['MinToSysTray']:
                self.createTrayIcon()
            elif self.trayIcon:
                self.trayIcon.hide()
            autoSaveMinutes = globalref.genOptions['AutoSaveMinutes']
            for control in self.localControls:
                for window in control.windowList:
                    window.updateWinGenOptions()
                control.structure.undoList.setNumLevels()
                control.updateAll(False)
                if autoSaveMinutes != oldAutoSaveMinutes:
                    control.resetAutoSave()

    def toolsCustomShortcuts(self):
        """Show dialog to customize keyboard commands.
        """
        actions = self.activeControl.activeWindow.allActions
        dialog = miscdialogs.CustomShortcutsDialog(actions,
                                                   QApplication.activeWindow())
        dialog.exec_()

    def toolsCustomToolbars(self):
        """Show dialog to customize toolbar buttons.
        """
        actions = self.activeControl.activeWindow.allActions
        dialog = miscdialogs.CustomToolbarDialog(actions, self.updateToolbars,
                                                 QApplication.activeWindow())
        dialog.exec_()

    def updateToolbars(self):
        """Update toolbars after changes in custom toolbar dialog.
        """
        for control in self.localControls:
            for window in control.windowList:
                window.setupToolbars()

    def toolsCustomFonts(self):
        """Show dialog to customize fonts in various views.
        """
        dialog = miscdialogs.CustomFontDialog(QApplication.activeWindow())
        dialog.updateRequired.connect(self.updateCustomFonts)
        dialog.exec_()

    def toolsCustomColors(self):
        """Show dialog to customize GUI colors ans themes.
        """
        self.colorSet.showDialog(QApplication.activeWindow())

    def updateCustomFonts(self):
        """Update fonts in all windows based on a dialog signal.
        """
        self.updateAppFont()
        for control in self.localControls:
            for window in control.windowList:
                window.updateFonts()
            control.printData.setDefaultFont()
        for control in self.localControls:
            control.updateAll(False)

    def updateAppFont(self):
        """Update application default font from settings.
        """
        appFont = QFont(self.systemFont)
        appFontName = globalref.miscOptions['AppFont']
        if appFontName:
            appFont.fromString(appFontName)
        QApplication.setFont(appFont)

    def formatSelectAll(self):
        """Select all text in any currently focused editor.
        """
        try:
            QApplication.focusWidget().selectAll()
        except AttributeError:
            pass

    def helpViewBasic(self):
        """Display basic usage instructions.
        """
        if not self.basicHelpView:
            path = self.findResourceFile('basichelp.html', 'doc', docPath)
            if not path:
                QMessageBox.warning(QApplication.activeWindow(), 'TreeLine',
                                    _('Error - basic help file not found'))
                return
            self.basicHelpView = helpview.HelpView(path,
                                                   _('TreeLine Basic Usage'),
                                                   globalref.toolIcons)
        self.basicHelpView.show()

    def helpViewFull(self):
        """Open a TreeLine file with full documentation.
        """
        path = self.findResourceFile('documentation.trln', 'doc', docPath)
        if not path:
            QMessageBox.warning(QApplication.activeWindow(), 'TreeLine',
                                _('Error - documentation file not found'))
            return
        self.createLocalControl(path, forceNewWindow=True)
        self.activeControl.filePathObj = pathlib.Path('documentation.trln')
        self.activeControl.updateWindowCaptions()
        self.activeControl.expandRootNodes()
        self.activeControl.imported = True
        win = self.activeControl.activeWindow
        win.rightTabs.setCurrentWidget(win.outputSplitter)

    def helpAbout(self):
        """ Display version info about this program.
        """
        pyVersion = '.'.join([repr(num) for num in sys.version_info[:3]])
        textLines = [
            _('TreeLine version {0}').format(__version__),
            _('written by {0}').format(__author__), '',
            _('Library versions:'), '   Python:  {0}'.format(pyVersion),
            '   Qt:  {0}'.format(qVersion()),
            '   PyQt:  {0}'.format(PYQT_VERSION_STR),
            '   OS:  {0}'.format(platform.platform())
        ]
        dialog = miscdialogs.AboutDialog('TreeLine', textLines,
                                         QApplication.windowIcon(),
                                         QApplication.activeWindow())
        dialog.exec_()
class MainWindow(QMainWindow):

    """Voice Changer main window."""

    def __init__(self, parent=None):
        super(MainWindow, self).__init__()
        self.statusBar().showMessage("Move Dial to Deform Microphone Voice !.")
        self.setWindowTitle(__doc__)
        self.setMinimumSize(240, 240)
        self.setMaximumSize(480, 480)
        self.resize(self.minimumSize())
        self.setWindowIcon(QIcon.fromTheme("audio-input-microphone"))
        self.tray = QSystemTrayIcon(self)
        self.center()
        QShortcut("Ctrl+q", self, activated=lambda: self.close())
        self.menuBar().addMenu("&File").addAction("Quit", lambda: exit())
        self.menuBar().addMenu("Sound").addAction(
            "STOP !", lambda: call("killall rec", shell=True)
        )
        windowMenu = self.menuBar().addMenu("&Window")
        windowMenu.addAction("Hide", lambda: self.hide())
        windowMenu.addAction("Minimize", lambda: self.showMinimized())
        windowMenu.addAction("Maximize", lambda: self.showMaximized())
        windowMenu.addAction("Restore", lambda: self.showNormal())
        windowMenu.addAction("FullScreen", lambda: self.showFullScreen())
        windowMenu.addAction("Center", lambda: self.center())
        windowMenu.addAction("Top-Left", lambda: self.move(0, 0))
        windowMenu.addAction("To Mouse", lambda: self.move_to_mouse_position())
        # widgets
        group0 = QGroupBox("Voice Deformation")
        self.setCentralWidget(group0)
        self.process = QProcess(self)
        self.process.error.connect(
            lambda: self.statusBar().showMessage("Info: Process Killed", 5000)
        )
        self.control = QDial()
        self.control.setRange(-10, 20)
        self.control.setSingleStep(5)
        self.control.setValue(0)
        self.control.setCursor(QCursor(Qt.OpenHandCursor))
        self.control.sliderPressed.connect(
            lambda: self.control.setCursor(QCursor(Qt.ClosedHandCursor))
        )
        self.control.sliderReleased.connect(
            lambda: self.control.setCursor(QCursor(Qt.OpenHandCursor))
        )
        self.control.valueChanged.connect(
            lambda: self.control.setToolTip(f"<b>{self.control.value()}")
        )
        self.control.valueChanged.connect(
            lambda: self.statusBar().showMessage(
                f"Voice deformation: {self.control.value()}", 5000
            )
        )
        self.control.valueChanged.connect(self.run)
        self.control.valueChanged.connect(lambda: self.process.kill())
        # Graphic effect
        self.glow = QGraphicsDropShadowEffect(self)
        self.glow.setOffset(0)
        self.glow.setBlurRadius(99)
        self.glow.setColor(QColor(99, 255, 255))
        self.control.setGraphicsEffect(self.glow)
        self.glow.setEnabled(False)
        # Timer to start
        self.slider_timer = QTimer(self)
        self.slider_timer.setSingleShot(True)
        self.slider_timer.timeout.connect(self.on_slider_timer_timeout)
        # an icon and set focus
        QLabel(self.control).setPixmap(
            QIcon.fromTheme("audio-input-microphone").pixmap(32)
        )
        self.control.setFocus()
        QVBoxLayout(group0).addWidget(self.control)
        self.menu = QMenu(__doc__)
        self.menu.addAction(__doc__).setDisabled(True)
        self.menu.setIcon(self.windowIcon())
        self.menu.addSeparator()
        self.menu.addAction(
            "Show / Hide",
            lambda: self.hide() if self.isVisible() else self.showNormal(),
        )
        self.menu.addAction("STOP !", lambda: call("killall rec", shell=True))
        self.menu.addSeparator()
        self.menu.addAction("Quit", lambda: exit())
        self.tray.setContextMenu(self.menu)
        self.make_trayicon()

    def run(self):
        """Run/Stop the QTimer."""
        if self.slider_timer.isActive():
            self.slider_timer.stop()
        self.glow.setEnabled(True)
        call("killall rec ; killall play", shell=True)
        self.slider_timer.start(3000)

    def on_slider_timer_timeout(self):
        """Run subprocess to deform voice."""
        self.glow.setEnabled(False)
        value = int(self.control.value()) * 100
        command = f'play -q -V0 "|rec -q -V0 -n -d -R riaa bend pitch {value} "'
        print(f"Voice Deformation Value: {value}")
        print(f"Voice Deformation Command: {command}")
        self.process.start(command)
        if self.isVisible():
            self.statusBar().showMessage("Minimizing to System TrayIcon", 3000)
            print("Minimizing Main Window to System TrayIcon now...")
            sleep(3)
            self.hide()

    def center(self):
        """Center Window on the Current Screen,with Multi-Monitor support."""
        window_geometry = self.frameGeometry()
        mousepointer_position = QApplication.desktop().cursor().pos()
        screen = QApplication.desktop().screenNumber(mousepointer_position)
        centerPoint = QApplication.desktop().screenGeometry(screen).center()
        window_geometry.moveCenter(centerPoint)
        self.move(window_geometry.topLeft())

    def move_to_mouse_position(self):
        """Center the Window on the Current Mouse position."""
        window_geometry = self.frameGeometry()
        window_geometry.moveCenter(QApplication.desktop().cursor().pos())
        self.move(window_geometry.topLeft())

    def make_trayicon(self):
        """Make a Tray Icon."""
        if self.windowIcon() and __doc__:
            self.tray.setIcon(self.windowIcon())
            self.tray.setToolTip(__doc__)
            self.tray.activated.connect(
                lambda: self.hide() if self.isVisible() else self.showNormal()
            )
            return self.tray.show()
Beispiel #35
0
class QtApplication(QApplication, Application):
    """Application subclass that provides a Qt application object."""

    pluginsLoaded = Signal()
    applicationRunning = Signal()

    def __init__(self, tray_icon_name: str = None, **kwargs) -> None:
        plugin_path = ""
        if sys.platform == "win32":
            if hasattr(sys, "frozen"):
                plugin_path = os.path.join(
                    os.path.dirname(os.path.abspath(sys.executable)), "PyQt5",
                    "plugins")
                Logger.log("i", "Adding QT5 plugin path: %s", plugin_path)
                QCoreApplication.addLibraryPath(plugin_path)
            else:
                import site
                for sitepackage_dir in site.getsitepackages():
                    QCoreApplication.addLibraryPath(
                        os.path.join(sitepackage_dir, "PyQt5", "plugins"))
        elif sys.platform == "darwin":
            plugin_path = os.path.join(self.getInstallPrefix(), "Resources",
                                       "plugins")

        if plugin_path:
            Logger.log("i", "Adding QT5 plugin path: %s", plugin_path)
            QCoreApplication.addLibraryPath(plugin_path)

        # use Qt Quick Scene Graph "basic" render loop
        os.environ["QSG_RENDER_LOOP"] = "basic"

        super().__init__(sys.argv, **kwargs)  # type: ignore

        self._qml_import_paths = []  #type: List[str]
        self._main_qml = "main.qml"  #type: str
        self._qml_engine = None  #type: Optional[QQmlApplicationEngine]
        self._main_window = None  #type: Optional[MainWindow]
        self._tray_icon_name = tray_icon_name  #type: Optional[str]
        self._tray_icon = None  #type: Optional[str]
        self._tray_icon_widget = None  #type: Optional[QSystemTrayIcon]
        self._theme = None  #type: Optional[Theme]
        self._renderer = None  #type: Optional[QtRenderer]

        self._job_queue = None  #type: Optional[JobQueue]
        self._version_upgrade_manager = None  #type: Optional[VersionUpgradeManager]

        self._is_shutting_down = False  #type: bool

        self._recent_files = []  #type: List[QUrl]

        self._configuration_error_message = None  #type: Optional[ConfigurationErrorMessage]

        self._http_network_request_manager = HttpRequestManager(parent=self)

        #Metadata required for the file dialogues.
        self.setOrganizationDomain("https://ultimaker.com/")
        self.setOrganizationName("Ultimaker B.V.")

    def addCommandLineOptions(self) -> None:
        super().addCommandLineOptions()
        # This flag is used by QApplication. We don't process it.
        self._cli_parser.add_argument(
            "-qmljsdebugger", help="For Qt's QML debugger compatibility")

    def initialize(self) -> None:
        super().initialize()

        preferences = Application.getInstance().getPreferences()
        preferences.addPreference("view/force_empty_shader_cache", False)
        preferences.addPreference("view/opengl_version_detect",
                                  OpenGLContext.OpenGlVersionDetect.Autodetect)

        # Read preferences here (upgrade won't work) to get:
        #  - The language in use, so the splash window can be shown in the correct language.
        #  - The OpenGL 'force' parameters.
        try:
            preferences_filename = Resources.getPath(Resources.Preferences,
                                                     self._app_name + ".cfg")
            self._preferences.readFromFile(preferences_filename)
        except FileNotFoundError:
            Logger.log(
                "i",
                "Preferences file not found, ignore and use default language '%s'",
                self._default_language)

        # Initialize the package manager to remove and install scheduled packages.
        self._package_manager = self._package_manager_class(self, parent=self)

        self._mesh_file_handler = MeshFileHandler(self)  #type: MeshFileHandler
        self._workspace_file_handler = WorkspaceFileHandler(
            self)  #type: WorkspaceFileHandler

        # Remove this and you will get Windows 95 style for all widgets if you are using Qt 5.10+
        self.setStyle("fusion")

        if preferences.getValue("view/force_empty_shader_cache"):
            self.setAttribute(Qt.AA_DisableShaderDiskCache)
        self.setAttribute(Qt.AA_UseDesktopOpenGL)
        if preferences.getValue(
                "view/opengl_version_detect"
        ) != OpenGLContext.OpenGlVersionDetect.ForceModern:
            major_version, minor_version, profile = OpenGLContext.detectBestOpenGLVersion(
                preferences.getValue("view/opengl_version_detect") ==
                OpenGLContext.OpenGlVersionDetect.ForceLegacy)
        else:
            Logger.info(
                "Force 'modern' OpenGL (4.1 core) -- overrides 'force legacy opengl' preference."
            )
            major_version, minor_version, profile = (
                4, 1, QSurfaceFormat.CoreProfile)

        if major_version is None or minor_version is None or profile is None:
            Logger.log(
                "e",
                "Startup failed because OpenGL version probing has failed: tried to create a 2.0 and 4.1 context. Exiting"
            )
            if not self.getIsHeadLess():
                QMessageBox.critical(
                    None, "Failed to probe OpenGL",
                    "Could not probe OpenGL. This program requires OpenGL 2.0 or higher. Please check your video card drivers."
                )
            sys.exit(1)
        else:
            opengl_version_str = OpenGLContext.versionAsText(
                major_version, minor_version, profile)
            Logger.log("d",
                       "Detected most suitable OpenGL context version: %s",
                       opengl_version_str)
        if not self.getIsHeadLess():
            OpenGLContext.setDefaultFormat(major_version,
                                           minor_version,
                                           profile=profile)

        self._qml_import_paths.append(
            os.path.join(os.path.dirname(sys.executable), "qml"))
        self._qml_import_paths.append(
            os.path.join(self.getInstallPrefix(), "Resources", "qml"))

        Logger.log("i", "Initializing job queue ...")
        self._job_queue = JobQueue()
        self._job_queue.jobFinished.connect(self._onJobFinished)

        Logger.log("i", "Initializing version upgrade manager ...")
        self._version_upgrade_manager = VersionUpgradeManager(self)

    def startSplashWindowPhase(self) -> None:
        super().startSplashWindowPhase()
        i18n_catalog = i18nCatalog("uranium")
        self.showSplashMessage(
            i18n_catalog.i18nc("@info:progress",
                               "Initializing package manager..."))
        self._package_manager.initialize()

        signal.signal(signal.SIGINT, signal.SIG_DFL)
        # This is done here as a lot of plugins require a correct gl context. If you want to change the framework,
        # these checks need to be done in your <framework>Application.py class __init__().

        self._configuration_error_message = ConfigurationErrorMessage(
            self,
            i18n_catalog.i18nc("@info:status",
                               "Your configuration seems to be corrupt."),
            lifetime=0,
            title=i18n_catalog.i18nc("@info:title", "Configuration errors"))
        # Remove, install, and then loading plugins
        self.showSplashMessage(
            i18n_catalog.i18nc("@info:progress", "Loading plugins..."))
        # Remove and install the plugins that have been scheduled
        self._plugin_registry.initializeBeforePluginsAreLoaded()
        self._loadPlugins()
        self._plugin_registry.checkRequiredPlugins(self.getRequiredPlugins())
        self.pluginsLoaded.emit()

        self.showSplashMessage(
            i18n_catalog.i18nc("@info:progress", "Updating configuration..."))
        with self._container_registry.lockFile():
            VersionUpgradeManager.getInstance().upgrade()

        # Load preferences again because before we have loaded the plugins, we don't have the upgrade routine for
        # the preferences file. Now that we have, load the preferences file again so it can be upgraded and loaded.
        self.showSplashMessage(
            i18n_catalog.i18nc("@info:progress", "Loading preferences..."))
        try:
            preferences_filename = Resources.getPath(Resources.Preferences,
                                                     self._app_name + ".cfg")
            with open(preferences_filename, "r", encoding="utf-8") as f:
                serialized = f.read()
            # This performs the upgrade for Preferences
            self._preferences.deserialize(serialized)
            self._preferences.setValue("general/plugins_to_remove", "")
            self._preferences.writeToFile(preferences_filename)
        except (FileNotFoundError, UnicodeDecodeError):
            Logger.log(
                "i",
                "The preferences file cannot be found or it is corrupted, so we will use default values"
            )

        self.processEvents()
        # Force the configuration file to be written again since the list of plugins to remove maybe changed
        try:
            self._preferences_filename = Resources.getPath(
                Resources.Preferences, self._app_name + ".cfg")
            self._preferences.readFromFile(self._preferences_filename)
        except FileNotFoundError:
            Logger.log(
                "i",
                "The preferences file '%s' cannot be found, will use default values",
                self._preferences_filename)
            self._preferences_filename = Resources.getStoragePath(
                Resources.Preferences, self._app_name + ".cfg")
        Logger.info("Completed loading preferences.")

        # FIXME: This is done here because we now use "plugins.json" to manage plugins instead of the Preferences file,
        # but the PluginRegistry will still import data from the Preferences files if present, such as disabled plugins,
        # so we need to reset those values AFTER the Preferences file is loaded.
        self._plugin_registry.initializeAfterPluginsAreLoaded()

        # Check if we have just updated from an older version
        self._preferences.addPreference("general/last_run_version", "")
        last_run_version_str = self._preferences.getValue(
            "general/last_run_version")
        if not last_run_version_str:
            last_run_version_str = self._version
        last_run_version = Version(last_run_version_str)
        current_version = Version(self._version)
        if last_run_version < current_version:
            self._just_updated_from_old_version = True
        self._preferences.setValue("general/last_run_version",
                                   str(current_version))
        self._preferences.writeToFile(self._preferences_filename)

        # Preferences: recent files
        self._preferences.addPreference("%s/recent_files" % self._app_name, "")
        file_names = self._preferences.getValue("%s/recent_files" %
                                                self._app_name).split(";")
        for file_name in file_names:
            if not os.path.isfile(file_name):
                continue
            self._recent_files.append(QUrl.fromLocalFile(file_name))

        if not self.getIsHeadLess():
            # Initialize System tray icon and make it invisible because it is used only to show pop up messages
            self._tray_icon = None
            if self._tray_icon_name:
                try:
                    self._tray_icon = QIcon(
                        Resources.getPath(Resources.Images,
                                          self._tray_icon_name))
                    self._tray_icon_widget = QSystemTrayIcon(self._tray_icon)
                    self._tray_icon_widget.setVisible(False)
                    Logger.info("Created system tray icon.")
                except FileNotFoundError:
                    Logger.log("w", "Could not find the icon %s",
                               self._tray_icon_name)

    def initializeEngine(self) -> None:
        # TODO: Document native/qml import trickery
        self._qml_engine = QQmlApplicationEngine(self)
        self.processEvents()
        self._qml_engine.setOutputWarningsToStandardError(False)
        self._qml_engine.warnings.connect(self.__onQmlWarning)

        for path in self._qml_import_paths:
            self._qml_engine.addImportPath(path)

        if not hasattr(sys, "frozen"):
            self._qml_engine.addImportPath(
                os.path.join(os.path.dirname(__file__), "qml"))

        self._qml_engine.rootContext().setContextProperty(
            "QT_VERSION_STR", QT_VERSION_STR)
        self.processEvents()
        self._qml_engine.rootContext().setContextProperty(
            "screenScaleFactor", self._screenScaleFactor())

        self.registerObjects(self._qml_engine)

        Bindings.register()

        # Preload theme. The theme will be loaded on first use, which will incur a ~0.1s freeze on the MainThread.
        # Do it here, while the splash screen is shown. Also makes this freeze explicit and traceable.
        self.getTheme()
        self.processEvents()

        i18n_catalog = i18nCatalog("uranium")
        self.showSplashMessage(
            i18n_catalog.i18nc("@info:progress", "Loading UI..."))
        self._qml_engine.load(self._main_qml)
        self.engineCreatedSignal.emit()

    recentFilesChanged = pyqtSignal()

    @pyqtProperty("QVariantList", notify=recentFilesChanged)
    def recentFiles(self) -> List[QUrl]:
        return self._recent_files

    fileProvidersChanged = pyqtSignal()

    @pyqtProperty("QVariantList", notify=fileProvidersChanged)
    def fileProviders(self) -> List[FileProvider]:
        return self.getFileProviders()

    def _onJobFinished(self, job: Job) -> None:
        if isinstance(job, WriteFileJob) and not job.getResult():
            return

        if isinstance(job, (ReadMeshJob, ReadFileJob,
                            WriteFileJob)) and job.getAddToRecentFiles():
            self.addFileToRecentFiles(job.getFileName())

    def addFileToRecentFiles(self, file_name: str) -> None:
        file_path = QUrl.fromLocalFile(file_name)

        if file_path in self._recent_files:
            self._recent_files.remove(file_path)

        self._recent_files.insert(0, file_path)
        if len(self._recent_files) > 10:
            del self._recent_files[10]

        pref = ""
        for path in self._recent_files:
            pref += path.toLocalFile() + ";"

        self.getPreferences().setValue(
            "%s/recent_files" % self.getApplicationName(), pref)
        self.recentFilesChanged.emit()

    def run(self) -> None:
        super().run()

    def hideMessage(self, message: Message) -> None:
        with self._message_lock:
            if message in self._visible_messages:
                message.hide(
                    send_signal=False
                )  # we're in handling hideMessageSignal so we don't want to resend it
                self._visible_messages.remove(message)
                self.visibleMessageRemoved.emit(message)

    def showMessage(self, message: Message) -> None:
        with self._message_lock:
            if message not in self._visible_messages:
                self._visible_messages.append(message)
                message.setLifetimeTimer(QTimer())
                message.setInactivityTimer(QTimer())
                self.visibleMessageAdded.emit(message)

        # also show toast message when the main window is minimized
        self.showToastMessage(self._app_name, message.getText())

    def _onMainWindowStateChanged(self, window_state: int) -> None:
        if self._tray_icon and self._tray_icon_widget:
            visible = window_state == Qt.WindowMinimized
            self._tray_icon_widget.setVisible(visible)

    # Show toast message using System tray widget.
    def showToastMessage(self, title: str, message: str) -> None:
        if self.checkWindowMinimizedState() and self._tray_icon_widget:
            # NOTE: Qt 5.8 don't support custom icon for the system tray messages, but Qt 5.9 does.
            #       We should use the custom icon when we switch to Qt 5.9
            self._tray_icon_widget.showMessage(title, message)

    def setMainQml(self, path: str) -> None:
        self._main_qml = path

    def exec_(self, *args: Any, **kwargs: Any) -> None:
        self.applicationRunning.emit()
        super().exec_(*args, **kwargs)

    @pyqtSlot()
    def reloadQML(self) -> None:
        # only reload when it is a release build
        if not self.getIsDebugMode():
            return
        if self._qml_engine and self._theme:
            self._qml_engine.clearComponentCache()
            self._theme.reload()
            self._qml_engine.load(self._main_qml)
            # Hide the window. For some reason we can't close it yet. This needs to be done in the onComponentCompleted.
            for obj in self._qml_engine.rootObjects():
                if obj != self._qml_engine.rootObjects()[-1]:
                    obj.hide()

    @pyqtSlot()
    def purgeWindows(self) -> None:
        # Close all root objects except the last one.
        # Should only be called by onComponentCompleted of the mainWindow.
        if self._qml_engine:
            for obj in self._qml_engine.rootObjects():
                if obj != self._qml_engine.rootObjects()[-1]:
                    obj.close()

    @pyqtSlot("QList<QQmlError>")
    def __onQmlWarning(self, warnings: List[QQmlError]) -> None:
        for warning in warnings:
            Logger.log("w", warning.toString())

    engineCreatedSignal = Signal()

    def isShuttingDown(self) -> bool:
        return self._is_shutting_down

    def registerObjects(
        self, engine
    ) -> None:  #type: ignore #Don't type engine, because the type depends on the platform you're running on so it always gives an error somewhere.
        engine.rootContext().setContextProperty("PluginRegistry",
                                                PluginRegistry.getInstance())

    def getRenderer(self) -> QtRenderer:
        if not self._renderer:
            self._renderer = QtRenderer()

        return cast(QtRenderer, self._renderer)

    mainWindowChanged = Signal()

    def getMainWindow(self) -> Optional[MainWindow]:
        return self._main_window

    def setMainWindow(self, window: MainWindow) -> None:
        if window != self._main_window:
            if self._main_window is not None:
                self._main_window.windowStateChanged.disconnect(
                    self._onMainWindowStateChanged)

            self._main_window = window
            if self._main_window is not None:
                self._main_window.windowStateChanged.connect(
                    self._onMainWindowStateChanged)
            self.mainWindowChanged.emit()

    def setVisible(self, visible: bool) -> None:
        if self._main_window is not None:
            self._main_window.visible = visible

    @property
    def isVisible(self) -> bool:
        if self._main_window is not None:
            return self._main_window.visible  #type: ignore #MyPy doesn't realise that self._main_window cannot be None here.
        return False

    def getTheme(self) -> Optional[Theme]:
        if self._theme is None:
            if self._qml_engine is None:
                Logger.log(
                    "e",
                    "The theme cannot be accessed before the engine is initialised"
                )
                return None

            self._theme = UM.Qt.Bindings.Theme.Theme.getInstance(
                self._qml_engine)
        return self._theme

    #   Handle a function that should be called later.
    def functionEvent(self, event: QEvent) -> None:
        e = _QtFunctionEvent(event)
        QCoreApplication.postEvent(self, e)

    #   Handle Qt events
    def event(self, event: QEvent) -> bool:
        if event.type() == _QtFunctionEvent.QtFunctionEvent:
            event._function_event.call()
            return True

        return super().event(event)

    def windowClosed(self, save_data: bool = True) -> None:
        Logger.log("d", "Shutting down %s", self.getApplicationName())
        self._is_shutting_down = True

        # garbage collect tray icon so it gets properly closed before the application is closed
        self._tray_icon_widget = None

        if save_data:
            try:
                self.savePreferences()
            except Exception as e:
                Logger.log("e", "Exception while saving preferences: %s",
                           repr(e))

        try:
            self.applicationShuttingDown.emit()
        except Exception as e:
            Logger.log("e", "Exception while emitting shutdown signal: %s",
                       repr(e))

        try:
            self.getBackend().close()
        except Exception as e:
            Logger.log("e", "Exception while closing backend: %s", repr(e))

        if self._tray_icon_widget:
            self._tray_icon_widget.deleteLater()

        self.quit()

    def checkWindowMinimizedState(self) -> bool:
        if self._main_window is not None and self._main_window.windowState(
        ) == Qt.WindowMinimized:
            return True
        else:
            return False

    @pyqtSlot(result="QObject*")
    def getBackend(self) -> Backend:
        """Get the backend of the application (the program that does the heavy lifting).

        The backend is also a QObject, which can be used from qml.
        """

        return self._backend

    @pyqtProperty("QVariant", constant=True)
    def backend(self) -> Backend:
        """Property used to expose the backend

        It is made static as the backend is not supposed to change during runtime.
        This makes the connection between backend and QML more reliable than the pyqtSlot above.
        :returns: Backend :type{Backend}
        """

        return self.getBackend()

    splash = None  # type: Optional[QSplashScreen]
    """Create a class variable so we can manage the splash in the CrashHandler dialog when the Application instance
    is not yet created, e.g. when an error occurs during the initialization
    """

    def createSplash(self) -> None:
        if not self.getIsHeadLess():
            try:
                QtApplication.splash = self._createSplashScreen()
            except FileNotFoundError:
                QtApplication.splash = None
            else:
                if QtApplication.splash:
                    QtApplication.splash.show()
                    self.processEvents()

    def showSplashMessage(self, message: str) -> None:
        """Display text on the splash screen."""

        if not QtApplication.splash:
            self.createSplash()

        if QtApplication.splash:
            self.processEvents(
            )  # Process events from previous loading phase before updating the message
            QtApplication.splash.showMessage(
                message,
                Qt.AlignHCenter | Qt.AlignVCenter)  # Now update the message
            self.processEvents()  # And make sure it is immediately visible
        elif self.getIsHeadLess():
            Logger.log("d", message)

    def closeSplash(self) -> None:
        """Close the splash screen after the application has started."""

        if QtApplication.splash:
            QtApplication.splash.close()
            QtApplication.splash = None

    def createQmlComponent(
        self,
        qml_file_path: str,
        context_properties: Dict[str,
                                 "QObject"] = None) -> Optional["QObject"]:
        """Create a QML component from a qml file.
        :param qml_file_path:: The absolute file path to the root qml file.
        :param context_properties:: Optional dictionary containing the properties that will be set on the context of the
        qml instance before creation.
        :return: None in case the creation failed (qml error), else it returns the qml instance.
        :note If the creation fails, this function will ensure any errors are logged to the logging service.
        """

        if self._qml_engine is None:  # Protect in case the engine was not initialized yet
            return None
        path = QUrl.fromLocalFile(qml_file_path)
        component = QQmlComponent(self._qml_engine, path)
        result_context = QQmlContext(
            self._qml_engine.rootContext()
        )  #type: ignore #MyPy doens't realise that self._qml_engine can't be None here.
        if context_properties is not None:
            for name, value in context_properties.items():
                result_context.setContextProperty(name, value)
        result = component.create(result_context)
        for err in component.errors():
            Logger.log("e", str(err.toString()))
        if result is None:
            return None

        # We need to store the context with the qml object, else the context gets garbage collected and the qml objects
        # no longer function correctly/application crashes.
        result.attached_context = result_context
        return result

    @pyqtSlot()
    def deleteAll(self, only_selectable=True) -> None:
        """Delete all nodes containing mesh data in the scene.
        :param only_selectable:. Set this to False to delete objects from all build plates
        """

        self.getController().deleteAllNodesWithMeshData(only_selectable)

    @pyqtSlot()
    def resetWorkspace(self) -> None:
        self._workspace_metadata_storage.clear()
        self.deleteAll()
        self.workspaceLoaded.emit("")

    def getMeshFileHandler(self) -> MeshFileHandler:
        """Get the MeshFileHandler of this application."""

        return self._mesh_file_handler

    def getWorkspaceFileHandler(self) -> WorkspaceFileHandler:
        return self._workspace_file_handler

    @pyqtSlot(result=QObject)
    def getPackageManager(self) -> PackageManager:
        return self._package_manager

    def getHttpRequestManager(self) -> "HttpRequestManager":
        return self._http_network_request_manager

    @classmethod
    def getInstance(cls, *args, **kwargs) -> "QtApplication":
        """Gets the instance of this application.

        This is just to further specify the type of Application.getInstance().
        :return: The instance of this application.
        """

        return cast(QtApplication, super().getInstance(**kwargs))

    def _createSplashScreen(self) -> QSplashScreen:
        return QSplashScreen(
            QPixmap(
                Resources.getPath(Resources.Images,
                                  self.getApplicationName() + ".png")))

    def _screenScaleFactor(self) -> float:
        # OSX handles sizes of dialogs behind our backs, but other platforms need
        # to know about the device pixel ratio
        if sys.platform == "darwin":
            return 1.0
        else:
            # determine a device pixel ratio from font metrics, using the same logic as UM.Theme
            fontPixelRatio = QFontMetrics(
                QCoreApplication.instance().font()).ascent() / 11
            # round the font pixel ratio to quarters
            fontPixelRatio = int(fontPixelRatio * 4) / 4
            return fontPixelRatio

    @pyqtProperty(str, constant=True)
    def applicationDisplayName(self) -> str:
        return self.getApplicationDisplayName()
Beispiel #36
0
class ElectrumGui(BaseElectrumGui, Logger):

    network_dialog: Optional['NetworkDialog']
    lightning_dialog: Optional['LightningDialog']
    watchtower_dialog: Optional['WatchtowerDialog']

    @profiler
    def __init__(self, *, config: 'SimpleConfig', daemon: 'Daemon',
                 plugins: 'Plugins'):
        set_language(config.get('language', get_default_language()))
        BaseElectrumGui.__init__(self,
                                 config=config,
                                 daemon=daemon,
                                 plugins=plugins)
        Logger.__init__(self)
        self.logger.info(
            f"Qt GUI starting up... Qt={QtCore.QT_VERSION_STR}, PyQt={QtCore.PYQT_VERSION_STR}"
        )
        # Uncomment this call to verify objects are being properly
        # GC-ed when windows are closed
        #network.add_jobs([DebugMem([Abstract_Wallet, SPV, Synchronizer,
        #                            ElectrumWindow], interval=5)])
        QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads)
        if hasattr(QtCore.Qt, "AA_ShareOpenGLContexts"):
            QtCore.QCoreApplication.setAttribute(
                QtCore.Qt.AA_ShareOpenGLContexts)
        if hasattr(QGuiApplication, 'setDesktopFileName'):
            QGuiApplication.setDesktopFileName('electrum-grlc.desktop')
        self.gui_thread = threading.current_thread()
        self.windows = []  # type: List[ElectrumWindow]
        self.efilter = OpenFileEventFilter(self.windows)
        self.app = QElectrumApplication(sys.argv)
        self.app.installEventFilter(self.efilter)
        self.app.setWindowIcon(read_QIcon("electrum-grlc.png"))
        self._cleaned_up = False
        # timer
        self.timer = QTimer(self.app)
        self.timer.setSingleShot(False)
        self.timer.setInterval(500)  # msec

        self.network_dialog = None
        self.lightning_dialog = None
        self.watchtower_dialog = None
        self.network_updated_signal_obj = QNetworkUpdatedSignalObject()
        self._num_wizards_in_progress = 0
        self._num_wizards_lock = threading.Lock()
        self.dark_icon = self.config.get("dark_icon", False)
        self.tray = None
        self._init_tray()
        self.app.new_window_signal.connect(self.start_new_window)
        self.app.quit_signal.connect(self.app.quit, Qt.QueuedConnection)
        self.set_dark_theme_if_needed()
        run_hook('init_qt', self)

    def _init_tray(self):
        self.tray = QSystemTrayIcon(self.tray_icon(), None)
        self.tray.setToolTip('Electrum-GRLC')
        self.tray.activated.connect(self.tray_activated)
        self.build_tray_menu()
        self.tray.show()

    def set_dark_theme_if_needed(self):
        use_dark_theme = self.config.get('qt_gui_color_theme',
                                         'default') == 'dark'
        if use_dark_theme:
            try:
                import qdarkstyle
                self.app.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5())
            except BaseException as e:
                use_dark_theme = False
                self.logger.warning(f'Error setting dark theme: {repr(e)}')
        # Apply any necessary stylesheet patches
        patch_qt_stylesheet(use_dark_theme=use_dark_theme)
        # Even if we ourselves don't set the dark theme,
        # the OS/window manager/etc might set *a dark theme*.
        # Hence, try to choose colors accordingly:
        ColorScheme.update_from_widget(QWidget(), force_dark=use_dark_theme)

    def build_tray_menu(self):
        if not self.tray:
            return
        # Avoid immediate GC of old menu when window closed via its action
        if self.tray.contextMenu() is None:
            m = QMenu()
            self.tray.setContextMenu(m)
        else:
            m = self.tray.contextMenu()
            m.clear()
        network = self.daemon.network
        m.addAction(_("Network"), self.show_network_dialog)
        if network and network.lngossip:
            m.addAction(_("Lightning Network"), self.show_lightning_dialog)
        if network and network.local_watchtower:
            m.addAction(_("Local Watchtower"), self.show_watchtower_dialog)
        for window in self.windows:
            name = window.wallet.basename()
            submenu = m.addMenu(name)
            submenu.addAction(_("Show/Hide"), window.show_or_hide)
            submenu.addAction(_("Close"), window.close)
        m.addAction(_("Dark/Light"), self.toggle_tray_icon)
        m.addSeparator()
        m.addAction(_("Exit Electrum"), self.app.quit)

    def tray_icon(self):
        if self.dark_icon:
            return read_QIcon('electrum_dark_icon.png')
        else:
            return read_QIcon('electrum_light_icon.png')

    def toggle_tray_icon(self):
        if not self.tray:
            return
        self.dark_icon = not self.dark_icon
        self.config.set_key("dark_icon", self.dark_icon, True)
        self.tray.setIcon(self.tray_icon())

    def tray_activated(self, reason):
        if reason == QSystemTrayIcon.DoubleClick:
            if all([w.is_hidden() for w in self.windows]):
                for w in self.windows:
                    w.bring_to_top()
            else:
                for w in self.windows:
                    w.hide()

    def _cleanup_before_exit(self):
        if self._cleaned_up:
            return
        self._cleaned_up = True
        self.app.new_window_signal.disconnect()
        self.efilter = None
        # If there are still some open windows, try to clean them up.
        for window in list(self.windows):
            window.close()
            window.clean_up()
        if self.network_dialog:
            self.network_dialog.close()
            self.network_dialog.clean_up()
            self.network_dialog = None
        self.network_updated_signal_obj = None
        if self.lightning_dialog:
            self.lightning_dialog.close()
            self.lightning_dialog = None
        if self.watchtower_dialog:
            self.watchtower_dialog.close()
            self.watchtower_dialog = None
        # Shut down the timer cleanly
        self.timer.stop()
        self.timer = None
        # clipboard persistence. see http://www.mail-archive.com/[email protected]/msg17328.html
        event = QtCore.QEvent(QtCore.QEvent.Clipboard)
        self.app.sendEvent(self.app.clipboard(), event)
        if self.tray:
            self.tray.hide()
            self.tray.deleteLater()
            self.tray = None

    def _maybe_quit_if_no_windows_open(self) -> None:
        """Check if there are any open windows and decide whether we should quit."""
        # keep daemon running after close
        if self.config.get('daemon'):
            return
        # check if a wizard is in progress
        with self._num_wizards_lock:
            if self._num_wizards_in_progress > 0 or len(self.windows) > 0:
                return
        self.app.quit()

    def new_window(self, path, uri=None):
        # Use a signal as can be called from daemon thread
        self.app.new_window_signal.emit(path, uri)

    def show_lightning_dialog(self):
        if not self.daemon.network.has_channel_db():
            return
        if not self.lightning_dialog:
            self.lightning_dialog = LightningDialog(self)
        self.lightning_dialog.bring_to_top()

    def show_watchtower_dialog(self):
        if not self.watchtower_dialog:
            self.watchtower_dialog = WatchtowerDialog(self)
        self.watchtower_dialog.bring_to_top()

    def show_network_dialog(self):
        if self.network_dialog:
            self.network_dialog.on_update()
            self.network_dialog.show()
            self.network_dialog.raise_()
            return
        self.network_dialog = NetworkDialog(
            network=self.daemon.network,
            config=self.config,
            network_updated_signal_obj=self.network_updated_signal_obj)
        self.network_dialog.show()

    def _create_window_for_wallet(self, wallet):
        w = ElectrumWindow(self, wallet)
        self.windows.append(w)
        self.build_tray_menu()
        w.warn_if_testnet()
        w.warn_if_watching_only()
        return w

    def count_wizards_in_progress(func):
        def wrapper(self: 'ElectrumGui', *args, **kwargs):
            with self._num_wizards_lock:
                self._num_wizards_in_progress += 1
            try:
                return func(self, *args, **kwargs)
            finally:
                with self._num_wizards_lock:
                    self._num_wizards_in_progress -= 1
                self._maybe_quit_if_no_windows_open()

        return wrapper

    @count_wizards_in_progress
    def start_new_window(self,
                         path,
                         uri,
                         *,
                         app_is_starting=False) -> Optional[ElectrumWindow]:
        '''Raises the window for the wallet if it is open.  Otherwise
        opens the wallet and creates a new window for it'''
        wallet = None
        try:
            wallet = self.daemon.load_wallet(path, None)
        except Exception as e:
            self.logger.exception('')
            custom_message_box(icon=QMessageBox.Warning,
                               parent=None,
                               title=_('Error'),
                               text=_('Cannot load wallet') + ' (1):\n' +
                               repr(e))
            # if app is starting, still let wizard to appear
            if not app_is_starting:
                return
        if not wallet:
            try:
                wallet = self._start_wizard_to_select_or_create_wallet(path)
            except (WalletFileException, BitcoinException) as e:
                self.logger.exception('')
                custom_message_box(icon=QMessageBox.Warning,
                                   parent=None,
                                   title=_('Error'),
                                   text=_('Cannot load wallet') + ' (2):\n' +
                                   repr(e))
        if not wallet:
            return
        # create or raise window
        try:
            for window in self.windows:
                if window.wallet.storage.path == wallet.storage.path:
                    break
            else:
                window = self._create_window_for_wallet(wallet)
        except Exception as e:
            self.logger.exception('')
            custom_message_box(icon=QMessageBox.Warning,
                               parent=None,
                               title=_('Error'),
                               text=_('Cannot create window for wallet') +
                               ':\n' + repr(e))
            if app_is_starting:
                wallet_dir = os.path.dirname(path)
                path = os.path.join(wallet_dir,
                                    get_new_wallet_name(wallet_dir))
                self.start_new_window(path, uri)
            return
        if uri:
            window.pay_to_URI(uri)
        window.bring_to_top()
        window.setWindowState(window.windowState() & ~QtCore.Qt.WindowMinimized
                              | QtCore.Qt.WindowActive)

        window.activateWindow()
        return window

    def _start_wizard_to_select_or_create_wallet(
            self, path) -> Optional[Abstract_Wallet]:
        wizard = InstallWizard(self.config,
                               self.app,
                               self.plugins,
                               gui_object=self)
        try:
            path, storage = wizard.select_storage(path, self.daemon.get_wallet)
            # storage is None if file does not exist
            if storage is None:
                wizard.path = path  # needed by trustedcoin plugin
                wizard.run('new')
                storage, db = wizard.create_storage(path)
            else:
                db = WalletDB(storage.read(), manual_upgrades=False)
                wizard.run_upgrades(storage, db)
        except (UserCancelled, GoBack):
            return
        except WalletAlreadyOpenInMemory as e:
            return e.wallet
        finally:
            wizard.terminate()
        # return if wallet creation is not complete
        if storage is None or db.get_action():
            return
        wallet = Wallet(db, storage, config=self.config)
        wallet.start_network(self.daemon.network)
        self.daemon.add_wallet(wallet)
        return wallet

    def close_window(self, window: ElectrumWindow):
        if window in self.windows:
            self.windows.remove(window)
        self.build_tray_menu()
        # save wallet path of last open window
        if not self.windows:
            self.config.save_last_wallet(window.wallet)
        run_hook('on_close_window', window)
        self.daemon.stop_wallet(window.wallet.storage.path)

    def init_network(self):
        # Show network dialog if config does not exist
        if self.daemon.network:
            if self.config.get('auto_connect') is None:
                wizard = InstallWizard(self.config,
                                       self.app,
                                       self.plugins,
                                       gui_object=self)
                wizard.init_network(self.daemon.network)
                wizard.terminate()

    def main(self):
        # setup Ctrl-C handling and tear-down code first, so that user can easily exit whenever
        self.app.setQuitOnLastWindowClosed(
            False)  # so _we_ can decide whether to quit
        self.app.lastWindowClosed.connect(self._maybe_quit_if_no_windows_open)
        self.app.aboutToQuit.connect(self._cleanup_before_exit)
        signal.signal(signal.SIGINT, lambda *args: self.app.quit())
        # hook for crash reporter
        Exception_Hook.maybe_setup(config=self.config)
        # first-start network-setup
        try:
            self.init_network()
        except UserCancelled:
            return
        except GoBack:
            return
        except Exception as e:
            self.logger.exception('')
            return
        # start wizard to select/create wallet
        self.timer.start()
        path = self.config.get_wallet_path(use_gui_last_wallet=True)
        try:
            if not self.start_new_window(
                    path, self.config.get('url'), app_is_starting=True):
                return
        except Exception as e:
            self.logger.error(
                "error loading wallet (or creating window for it)")
            send_exception_to_crash_reporter(e)
            # Let Qt event loop start properly so that crash reporter window can appear.
            # We will shutdown when the user closes that window, via lastWindowClosed signal.
        # main loop
        self.logger.info("starting Qt main loop")
        self.app.exec_()
        # on some platforms the exec_ call may not return, so use _cleanup_before_exit

    def stop(self):
        self.logger.info('closing GUI')
        self.app.quit_signal.emit()
Beispiel #37
0
 def _init_tray(self):
     self.tray = QSystemTrayIcon(self.tray_icon(), None)
     self.tray.setToolTip('Electrum-GRLC')
     self.tray.activated.connect(self.tray_activated)
     self.build_tray_menu()
     self.tray.show()
Beispiel #38
0
    def __init__(self, tray_icon_name=None, **kwargs):
        plugin_path = ""
        if sys.platform == "win32":
            if hasattr(sys, "frozen"):
                plugin_path = os.path.join(
                    os.path.dirname(os.path.abspath(sys.executable)), "PyQt5",
                    "plugins")
                Logger.log("i", "Adding QT5 plugin path: %s" % (plugin_path))
                QCoreApplication.addLibraryPath(plugin_path)
            else:
                import site
                for sitepackage_dir in site.getsitepackages():
                    QCoreApplication.addLibraryPath(
                        os.path.join(sitepackage_dir, "PyQt5", "plugins"))
        elif sys.platform == "darwin":
            plugin_path = os.path.join(Application.getInstallPrefix(),
                                       "Resources", "plugins")

        if plugin_path:
            Logger.log("i", "Adding QT5 plugin path: %s" % (plugin_path))
            QCoreApplication.addLibraryPath(plugin_path)

        os.environ["QSG_RENDER_LOOP"] = "basic"

        super().__init__(sys.argv, **kwargs)

        self.setAttribute(Qt.AA_UseDesktopOpenGL)
        major_version, minor_version, profile = OpenGLContext.detectBestOpenGLVersion(
        )

        if major_version is None and minor_version is None and profile is None:
            Logger.log(
                "e",
                "Startup failed because OpenGL version probing has failed: tried to create a 2.0 and 4.1 context. Exiting"
            )
            QMessageBox.critical(
                None, "Failed to probe OpenGL",
                "Could not probe OpenGL. This program requires OpenGL 2.0 or higher. Please check your video card drivers."
            )
            sys.exit(1)
        else:
            Logger.log(
                "d", "Detected most suitable OpenGL context version: %s" %
                (OpenGLContext.versionAsText(major_version, minor_version,
                                             profile)))
        OpenGLContext.setDefaultFormat(major_version,
                                       minor_version,
                                       profile=profile)

        self._plugins_loaded = False  # Used to determine when it's safe to use the plug-ins.
        self._main_qml = "main.qml"
        self._engine = None
        self._renderer = None
        self._main_window = None
        self._theme = None

        self._shutting_down = False
        self._qml_import_paths = []
        self._qml_import_paths.append(
            os.path.join(os.path.dirname(sys.executable), "qml"))
        self._qml_import_paths.append(
            os.path.join(Application.getInstallPrefix(), "Resources", "qml"))

        self.parseCommandLine()
        Logger.log("i", "Command line arguments: %s",
                   self._parsed_command_line)

        self._splash = None

        signal.signal(signal.SIGINT, signal.SIG_DFL)
        # This is done here as a lot of plugins require a correct gl context. If you want to change the framework,
        # these checks need to be done in your <framework>Application.py class __init__().

        i18n_catalog = i18nCatalog("uranium")

        self.showSplashMessage(
            i18n_catalog.i18nc("@info:progress", "Loading plugins..."))
        self._loadPlugins()
        self._plugin_registry.checkRequiredPlugins(self.getRequiredPlugins())
        self.pluginsLoaded.emit()

        self.showSplashMessage(
            i18n_catalog.i18nc("@info:progress", "Updating configuration..."))
        upgraded = UM.VersionUpgradeManager.VersionUpgradeManager.getInstance(
        ).upgrade()
        if upgraded:
            # Preferences might have changed. Load them again.
            # Note that the language can't be updated, so that will always revert to English.
            preferences = Preferences.getInstance()
            try:
                preferences.readFromFile(
                    Resources.getPath(Resources.Preferences,
                                      self._application_name + ".cfg"))
            except FileNotFoundError:
                pass

        self.showSplashMessage(
            i18n_catalog.i18nc("@info:progress", "Loading preferences..."))
        try:
            file_name = Resources.getPath(Resources.Preferences,
                                          self.getApplicationName() + ".cfg")
            Preferences.getInstance().readFromFile(file_name)
        except FileNotFoundError:
            pass

        self.getApplicationName()

        Preferences.getInstance().addPreference(
            "%s/recent_files" % self.getApplicationName(), "")

        self._recent_files = []
        file_names = Preferences.getInstance().getValue(
            "%s/recent_files" % self.getApplicationName()).split(";")
        for file_name in file_names:
            if not os.path.isfile(file_name):
                continue

            self._recent_files.append(QUrl.fromLocalFile(file_name))

        JobQueue.getInstance().jobFinished.connect(self._onJobFinished)

        # Initialize System tray icon and make it invisible because it is used only to show pop up messages
        self._tray_icon = None
        self._tray_icon_widget = None
        if tray_icon_name:
            self._tray_icon = QIcon(
                Resources.getPath(Resources.Images, tray_icon_name))
            self._tray_icon_widget = QSystemTrayIcon(self._tray_icon)
            self._tray_icon_widget.setVisible(False)
Beispiel #39
0
class QtApplication(QApplication, Application):
    pluginsLoaded = Signal()
    applicationRunning = Signal()

    def __init__(self, tray_icon_name=None, **kwargs):
        plugin_path = ""
        if sys.platform == "win32":
            if hasattr(sys, "frozen"):
                plugin_path = os.path.join(
                    os.path.dirname(os.path.abspath(sys.executable)), "PyQt5",
                    "plugins")
                Logger.log("i", "Adding QT5 plugin path: %s" % (plugin_path))
                QCoreApplication.addLibraryPath(plugin_path)
            else:
                import site
                for sitepackage_dir in site.getsitepackages():
                    QCoreApplication.addLibraryPath(
                        os.path.join(sitepackage_dir, "PyQt5", "plugins"))
        elif sys.platform == "darwin":
            plugin_path = os.path.join(Application.getInstallPrefix(),
                                       "Resources", "plugins")

        if plugin_path:
            Logger.log("i", "Adding QT5 plugin path: %s" % (plugin_path))
            QCoreApplication.addLibraryPath(plugin_path)

        os.environ["QSG_RENDER_LOOP"] = "basic"

        super().__init__(sys.argv, **kwargs)

        self.setAttribute(Qt.AA_UseDesktopOpenGL)
        major_version, minor_version, profile = OpenGLContext.detectBestOpenGLVersion(
        )

        if major_version is None and minor_version is None and profile is None:
            Logger.log(
                "e",
                "Startup failed because OpenGL version probing has failed: tried to create a 2.0 and 4.1 context. Exiting"
            )
            QMessageBox.critical(
                None, "Failed to probe OpenGL",
                "Could not probe OpenGL. This program requires OpenGL 2.0 or higher. Please check your video card drivers."
            )
            sys.exit(1)
        else:
            Logger.log(
                "d", "Detected most suitable OpenGL context version: %s" %
                (OpenGLContext.versionAsText(major_version, minor_version,
                                             profile)))
        OpenGLContext.setDefaultFormat(major_version,
                                       minor_version,
                                       profile=profile)

        self._plugins_loaded = False  # Used to determine when it's safe to use the plug-ins.
        self._main_qml = "main.qml"
        self._engine = None
        self._renderer = None
        self._main_window = None
        self._theme = None

        self._shutting_down = False
        self._qml_import_paths = []
        self._qml_import_paths.append(
            os.path.join(os.path.dirname(sys.executable), "qml"))
        self._qml_import_paths.append(
            os.path.join(Application.getInstallPrefix(), "Resources", "qml"))

        self.parseCommandLine()
        Logger.log("i", "Command line arguments: %s",
                   self._parsed_command_line)

        self._splash = None

        signal.signal(signal.SIGINT, signal.SIG_DFL)
        # This is done here as a lot of plugins require a correct gl context. If you want to change the framework,
        # these checks need to be done in your <framework>Application.py class __init__().

        i18n_catalog = i18nCatalog("uranium")

        self.showSplashMessage(
            i18n_catalog.i18nc("@info:progress", "Loading plugins..."))
        self._loadPlugins()
        self._plugin_registry.checkRequiredPlugins(self.getRequiredPlugins())
        self.pluginsLoaded.emit()

        self.showSplashMessage(
            i18n_catalog.i18nc("@info:progress", "Updating configuration..."))
        upgraded = UM.VersionUpgradeManager.VersionUpgradeManager.getInstance(
        ).upgrade()
        if upgraded:
            # Preferences might have changed. Load them again.
            # Note that the language can't be updated, so that will always revert to English.
            preferences = Preferences.getInstance()
            try:
                preferences.readFromFile(
                    Resources.getPath(Resources.Preferences,
                                      self._application_name + ".cfg"))
            except FileNotFoundError:
                pass

        self.showSplashMessage(
            i18n_catalog.i18nc("@info:progress", "Loading preferences..."))
        try:
            file_name = Resources.getPath(Resources.Preferences,
                                          self.getApplicationName() + ".cfg")
            Preferences.getInstance().readFromFile(file_name)
        except FileNotFoundError:
            pass

        self.getApplicationName()

        Preferences.getInstance().addPreference(
            "%s/recent_files" % self.getApplicationName(), "")

        self._recent_files = []
        file_names = Preferences.getInstance().getValue(
            "%s/recent_files" % self.getApplicationName()).split(";")
        for file_name in file_names:
            if not os.path.isfile(file_name):
                continue

            self._recent_files.append(QUrl.fromLocalFile(file_name))

        JobQueue.getInstance().jobFinished.connect(self._onJobFinished)

        # Initialize System tray icon and make it invisible because it is used only to show pop up messages
        self._tray_icon = None
        self._tray_icon_widget = None
        if tray_icon_name:
            self._tray_icon = QIcon(
                Resources.getPath(Resources.Images, tray_icon_name))
            self._tray_icon_widget = QSystemTrayIcon(self._tray_icon)
            self._tray_icon_widget.setVisible(False)

    recentFilesChanged = pyqtSignal()

    @pyqtProperty("QVariantList", notify=recentFilesChanged)
    def recentFiles(self):
        return self._recent_files

    def _onJobFinished(self, job):
        if (not isinstance(job, ReadMeshJob)
                and not isinstance(job, ReadFileJob)) or not job.getResult():
            return

        f = QUrl.fromLocalFile(job.getFileName())
        if f in self._recent_files:
            self._recent_files.remove(f)

        self._recent_files.insert(0, f)
        if len(self._recent_files) > 10:
            del self._recent_files[10]

        pref = ""
        for path in self._recent_files:
            pref += path.toLocalFile() + ";"

        Preferences.getInstance().setValue(
            "%s/recent_files" % self.getApplicationName(), pref)
        self.recentFilesChanged.emit()

    def run(self):
        pass

    def hideMessage(self, message):
        with self._message_lock:
            if message in self._visible_messages:
                self._visible_messages.remove(message)
                self.visibleMessageRemoved.emit(message)

    def showMessage(self, message):
        with self._message_lock:
            if message not in self._visible_messages:
                self._visible_messages.append(message)
                message.setLifetimeTimer(QTimer())
                message.setInactivityTimer(QTimer())
                self.visibleMessageAdded.emit(message)

        # also show toast message when the main window is minimized
        self.showToastMessage(self._application_name, message.getText())

    def _onMainWindowStateChanged(self, window_state):
        if self._tray_icon:
            visible = window_state == Qt.WindowMinimized
            self._tray_icon_widget.setVisible(visible)

    # Show toast message using System tray widget.
    def showToastMessage(self, title: str, message: str):
        if self.checkWindowMinimizedState() and self._tray_icon_widget:
            # NOTE: Qt 5.8 don't support custom icon for the system tray messages, but Qt 5.9 does.
            #       We should use the custom icon when we switch to Qt 5.9
            self._tray_icon_widget.showMessage(title, message)

    def setMainQml(self, path):
        self._main_qml = path

    def initializeEngine(self):
        # TODO: Document native/qml import trickery
        Bindings.register()

        self._engine = QQmlApplicationEngine()
        self._engine.setOutputWarningsToStandardError(False)
        self._engine.warnings.connect(self.__onQmlWarning)

        for path in self._qml_import_paths:
            self._engine.addImportPath(path)

        if not hasattr(sys, "frozen"):
            self._engine.addImportPath(
                os.path.join(os.path.dirname(__file__), "qml"))

        self._engine.rootContext().setContextProperty("QT_VERSION_STR",
                                                      QT_VERSION_STR)
        self._engine.rootContext().setContextProperty(
            "screenScaleFactor", self._screenScaleFactor())

        self.registerObjects(self._engine)

        self._engine.load(self._main_qml)
        self.engineCreatedSignal.emit()

    def exec_(self, *args, **kwargs):
        self.applicationRunning.emit()
        super().exec_(*args, **kwargs)

    @pyqtSlot()
    def reloadQML(self):
        # only reload when it is a release build
        if not self.getIsDebugMode():
            return
        self._engine.clearComponentCache()
        self._theme.reload()
        self._engine.load(self._main_qml)
        # Hide the window. For some reason we can't close it yet. This needs to be done in the onComponentCompleted.
        for obj in self._engine.rootObjects():
            if obj != self._engine.rootObjects()[-1]:
                obj.hide()

    @pyqtSlot()
    def purgeWindows(self):
        # Close all root objects except the last one.
        # Should only be called by onComponentCompleted of the mainWindow.
        for obj in self._engine.rootObjects():
            if obj != self._engine.rootObjects()[-1]:
                obj.close()

    @pyqtSlot("QList<QQmlError>")
    def __onQmlWarning(self, warnings):
        for warning in warnings:
            Logger.log("w", warning.toString())

    engineCreatedSignal = Signal()

    def isShuttingDown(self):
        return self._shutting_down

    def registerObjects(self, engine):
        engine.rootContext().setContextProperty("PluginRegistry",
                                                PluginRegistry.getInstance())

    def getRenderer(self):
        if not self._renderer:
            self._renderer = QtRenderer()

        return self._renderer

    @classmethod
    def addCommandLineOptions(self, parser, parsed_command_line={}):
        super().addCommandLineOptions(parser,
                                      parsed_command_line=parsed_command_line)
        parser.add_argument(
            "--disable-textures",
            dest="disable-textures",
            action="store_true",
            default=False,
            help=
            "Disable Qt texture loading as a workaround for certain crashes.")
        parser.add_argument("-qmljsdebugger",
                            help="For Qt's QML debugger compatibility")

    mainWindowChanged = Signal()

    def getMainWindow(self):
        return self._main_window

    def setMainWindow(self, window):
        if window != self._main_window:
            if self._main_window is not None:
                self._main_window.windowStateChanged.disconnect(
                    self._onMainWindowStateChanged)

            self._main_window = window
            if self._main_window is not None:
                self._main_window.windowStateChanged.connect(
                    self._onMainWindowStateChanged)

            self.mainWindowChanged.emit()

    def setVisible(self, visible):
        if self._engine is None:
            self.initializeEngine()

        if self._main_window is not None:
            self._main_window.visible = visible

    @property
    def isVisible(self):
        if self._main_window is not None:
            return self._main_window.visible

    def getTheme(self):
        if self._theme is None:
            if self._engine is None:
                Logger.log(
                    "e",
                    "The theme cannot be accessed before the engine is initialised"
                )
                return None

            self._theme = UM.Qt.Bindings.Theme.Theme.getInstance(self._engine)
        return self._theme

    #   Handle a function that should be called later.
    def functionEvent(self, event):
        e = _QtFunctionEvent(event)
        QCoreApplication.postEvent(self, e)

    #   Handle Qt events
    def event(self, event):
        if event.type() == _QtFunctionEvent.QtFunctionEvent:
            event._function_event.call()
            return True

        return super().event(event)

    def windowClosed(self):
        Logger.log("d", "Shutting down %s", self.getApplicationName())
        self._shutting_down = True

        try:
            Preferences.getInstance().writeToFile(
                Resources.getStoragePath(Resources.Preferences,
                                         self.getApplicationName() + ".cfg"))
        except Exception as e:
            Logger.log("e", "Exception while saving preferences: %s", repr(e))

        try:
            self.applicationShuttingDown.emit()
        except Exception as e:
            Logger.log("e", "Exception while emitting shutdown signal: %s",
                       repr(e))

        try:
            self.getBackend().close()
        except Exception as e:
            Logger.log("e", "Exception while closing backend: %s", repr(e))

        self.quit()

    def checkWindowMinimizedState(self):
        if self._main_window is not None and self._main_window.windowState(
        ) == Qt.WindowMinimized:
            return True
        else:
            return False

    ##  Get the backend of the application (the program that does the heavy lifting).
    #   The backend is also a QObject, which can be used from qml.
    #   \returns Backend \type{Backend}
    @pyqtSlot(result="QObject*")
    def getBackend(self):
        return self._backend

    ##  Property used to expose the backend
    #   It is made static as the backend is not supposed to change during runtime.
    #   This makes the connection between backend and QML more reliable than the pyqtSlot above.
    #   \returns Backend \type{Backend}
    @pyqtProperty("QVariant", constant=True)
    def backend(self):
        return self.getBackend()

    ##  Load a Qt translation catalog.
    #
    #   This method will locate, load and install a Qt message catalog that can be used
    #   by Qt's translation system, like qsTr() in QML files.
    #
    #   \param file_name The file name to load, without extension. It will be searched for in
    #                    the i18nLocation Resources directory. If it can not be found a warning
    #                    will be logged but no error will be thrown.
    #   \param language The language to load translations for. This can be any valid language code
    #                   or 'default' in which case the language is looked up based on system locale.
    #                   If the specified language can not be found, this method will fall back to
    #                   loading the english translations file.
    #
    #   \note When `language` is `default`, the language to load can be changed with the
    #         environment variable "LANGUAGE".
    def loadQtTranslation(self, file_name, language="default"):
        # TODO Add support for specifying a language from preferences
        path = None
        if language == "default":
            path = self._getDefaultLanguage(file_name)
        else:
            path = Resources.getPath(Resources.i18n, language, "LC_MESSAGES",
                                     file_name + ".qm")

        # If all else fails, fall back to english.
        if not path:
            Logger.log(
                "w",
                "Could not find any translations matching {0} for file {1}, falling back to english"
                .format(language, file_name))
            try:
                path = Resources.getPath(Resources.i18n, "en_US",
                                         "LC_MESSAGES", file_name + ".qm")
            except FileNotFoundError:
                Logger.log(
                    "w",
                    "Could not find English translations for file {0}. Switching to developer english."
                    .format(file_name))
                return

        translator = QTranslator()
        if not translator.load(path):
            Logger.log("e", "Unable to load translations %s", file_name)
            return

        # Store a reference to the translator.
        # This prevents the translator from being destroyed before Qt has a chance to use it.
        self._translators[file_name] = translator

        # Finally, install the translator so Qt can use it.
        self.installTranslator(translator)

    def createSplash(self):
        if not self.getCommandLineOption("headless"):
            try:
                self._splash = self._createSplashScreen()
            except FileNotFoundError:
                self._splash = None
            else:
                if self._splash:
                    self._splash.show()
                    self.processEvents()

    ##  Display text on the splash screen.
    def showSplashMessage(self, message):
        if not self._splash:
            self.createSplash()

        if self._splash:
            self._splash.showMessage(message,
                                     Qt.AlignHCenter | Qt.AlignVCenter)
            self.processEvents()
        elif self.getCommandLineOption("headless"):
            Logger.log("d", message)

    ##  Close the splash screen after the application has started.
    def closeSplash(self):
        if self._splash:
            self._splash.close()
            self._splash = None

    ## Create a QML component from a qml file.
    #  \param qml_file_path: The absolute file path to the root qml file.
    #  \param context_properties: Optional dictionary containing the properties that will be set on the context of the
    #                              qml instance before creation.
    #  \return None in case the creation failed (qml error), else it returns the qml instance.
    #  \note If the creation fails, this function will ensure any errors are logged to the logging service.
    def createQmlComponent(
        self,
        qml_file_path: str,
        context_properties: Dict[str,
                                 "QObject"] = None) -> Optional["QObject"]:
        path = QUrl.fromLocalFile(qml_file_path)
        component = QQmlComponent(self._engine, path)
        result_context = QQmlContext(self._engine.rootContext())
        if context_properties is not None:
            for name, value in context_properties.items():
                result_context.setContextProperty(name, value)
        result = component.create(result_context)
        for err in component.errors():
            Logger.log("e", str(err.toString()))
        if result is None:
            return None

        # We need to store the context with the qml object, else the context gets garbage collected and the qml objects
        # no longer function correctly/application crashes.
        result.attached_context = result_context
        return result

    def _createSplashScreen(self):
        return QSplashScreen(
            QPixmap(
                Resources.getPath(Resources.Images,
                                  self.getApplicationName() + ".png")))

    def _screenScaleFactor(self):
        # OSX handles sizes of dialogs behind our backs, but other platforms need
        # to know about the device pixel ratio
        if sys.platform == "darwin":
            return 1.0
        else:
            # determine a device pixel ratio from font metrics, using the same logic as UM.Theme
            fontPixelRatio = QFontMetrics(
                QCoreApplication.instance().font()).ascent() / 11
            # round the font pixel ratio to quarters
            fontPixelRatio = int(fontPixelRatio * 4) / 4
            return fontPixelRatio

    def _getDefaultLanguage(self, file_name):
        # If we have a language override set in the environment, try and use that.
        lang = os.getenv("URANIUM_LANGUAGE")
        if lang:
            try:
                return Resources.getPath(Resources.i18n, lang, "LC_MESSAGES",
                                         file_name + ".qm")
            except FileNotFoundError:
                pass

        # Else, try and get the current language from preferences
        lang = Preferences.getInstance().getValue("general/language")
        if lang:
            try:
                return Resources.getPath(Resources.i18n, lang, "LC_MESSAGES",
                                         file_name + ".qm")
            except FileNotFoundError:
                pass

        # If none of those are set, try to use the environment's LANGUAGE variable.
        lang = os.getenv("LANGUAGE")
        if lang:
            try:
                return Resources.getPath(Resources.i18n, lang, "LC_MESSAGES",
                                         file_name + ".qm")
            except FileNotFoundError:
                pass

        # If looking up the language from the enviroment or preferences fails, try and use Qt's system locale instead.
        locale = QLocale.system()

        # First, try and find a directory for any of the provided languages
        for lang in locale.uiLanguages():
            try:
                return Resources.getPath(Resources.i18n, lang, "LC_MESSAGES",
                                         file_name + ".qm")
            except FileNotFoundError:
                pass

        # If that fails, see if we can extract a language code from the
        # preferred language, regardless of the country code. This will turn
        # "en-GB" into "en" for example.
        lang = locale.uiLanguages()[0]
        lang = lang[0:lang.find("-")]
        for subdirectory in os.path.listdir(Resources.getPath(Resources.i18n)):
            if subdirectory == "en_7S":  #Never automatically go to Pirate.
                continue
            if not os.path.isdir(
                    Resources.getPath(Resources.i18n, subdirectory)):
                continue
            if subdirectory.startswith(
                    lang +
                    "_"):  #Only match the language code, not the country code.
                return Resources.getPath(Resources.i18n, lang, "LC_MESSAGES",
                                         file_name + ".qm")

        return None
Beispiel #40
0
class Window(QMainWindow, Ui_MainWindow):
    def __init__(self):
        super().__init__()
        self.setupUi(self)
        self.Tray()
        self.Listadd()
        self.step = 0
        self.loop = 1
        self.flag = self.listtag = self.fulltag = True
        self.player = vlc.MediaPlayer()
        qr = self.frameGeometry()
        cp = QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.topLeft())

    def Tray(self):
        self.tp = QSystemTrayIcon(self)
        self.tp.setIcon(QIcon(idirC))
        self.tp.activated.connect(self.Activated)
        self.tp.setToolTip('CPlayer')
        tpMenu = QMenu()
        a1 = QAction((QIcon(idirC)), '显示主页面', self, triggered=(self.Showmain))
        a2 = QAction((QIcon(idirC)), '隐藏主页面', self, triggered=(self.Min))
        a3 = QAction((QIcon(idirabout)), '关于', self, triggered=(self.About))
        a4 = QAction((QIcon(idirexit)), '退出', self, triggered=(self.Quit))
        tpMenu.addAction(a1)
        tpMenu.addAction(a2)
        tpMenu.addAction(a3)
        tpMenu.addAction(a4)
        self.tp.setContextMenu(tpMenu)
        self.tp.show()

    def closeEvent(self, event):
        event.ignore()
        self.hide()

    def Activated(self, reason):
        if reason == QSystemTrayIcon.MiddleClick:
            self.Min()
        else:
            if reason == QSystemTrayIcon.Trigger:
                self.Showmain()

    def Showmain(self):
        self.showNormal()
        self.activateWindow()

    def Min(self):
        self.hide()

    def About(self):
        QMessageBox.information(
            self, '关于',
            '作者:cnzb\nGithub:https://github.com/cnzbpy/simplepy\nGitee:https://gitee.com/cnzbpy/simplepy'
        )

    def Quit(self):
        self.tp = None
        app.exit()

    def resizeEvent(self, event):
        self.ratio()

    def keyPressEvent(self, event):
        if event.key() == Qt.Key_P:
            self.Listhide()
        if event.key() == Qt.Key_T:
            self.Fastback()
        if event.key() == Qt.Key_L:
            self.Loop()
        if event.key() == Qt.Key_Space:
            self.Play()
        if event.key() == Qt.Key_S:
            self.Stop()
        if event.key() == Qt.Key_F:
            self.Full()
        if event.key() == Qt.Key_J:
            self.Fastforward()
        if event.key() == Qt.Key_M:
            self.Mute()
        if event.key() == Qt.Key_A:
            self.svolume.setValue(self.svolume.value() + 1)
        if event.key() == Qt.Key_R:
            self.svolume.setValue(self.svolume.value() - 1)

    def eventFilter(self, sender, event):
        if (event.type() == event.ChildRemoved):
            self.Moved()
        return False

    def Listmenu(self, position):
        lm = QMenu()
        addact = QAction("添加到播放列表", self, triggered=self.Add)
        removeact = QAction("从播放列表移除", self, triggered=self.Remove)
        renameact = QAction('重命名', self, triggered=self.Rename)
        clearact = QAction('清空播放列表', self, triggered=self.Clear)
        saveact = QAction('保存当前播放列表', self, triggered=self.Saved)
        lm.addAction(addact)
        if self.list.itemAt(position):
            lm.addAction(removeact)
            lm.addAction(renameact)
        lm.addAction(clearact)
        lm.addAction(saveact)
        lm.exec_(self.list.mapToGlobal(position))

    def Listadd(self):
        self.l = []
        self.list.installEventFilter(self)
        if os.path.isfile('CPlayerlist.txt'):
            with open('CPlayerlist.txt', 'rb') as f:
                playencode = (chardet.detect(f.read()))['encoding']
            with open('CPlayerlist.txt', encoding=playencode,
                      errors='ignore') as f:
                for i in f:
                    i = i.strip()
                    name = i[0:i.find(',')]
                    filelist = i[i.find(',') + 1:len(i)]
                    self.list.addItem(name)
                    self.l.append(filelist)

    def Add(self):
        filelists, _ = QFileDialog.getOpenFileNames(self, '添加到播放列表', '.',
                                                    '媒体文件(*)')
        for filelist in filelists:
            name = filelist[filelist.rfind('/') + 1:filelist.rfind('.')]
            self.list.addItem(name)
            self.l.append(filelist)

    def Remove(self):
        ltmp = []
        for i in self.list.selectedIndexes():
            ltmp.append(i.row())
        ltmp.sort(reverse=True)
        for j in ltmp:
            self.list.takeItem(j)
            self.l.pop(j)

    def Rename(self):
        item = self.list.item(self.list.currentRow())
        item.setFlags(item.flags() | Qt.ItemIsEditable)
        self.list.editItem(item)

    def Clear(self):
        self.l = []
        self.list.clear()
        if os.path.isfile('CPlayerlist.txt'):
            os.remove('CPlayerlist.txt')

    def Drag(self):
        self.tmp1 = []
        self.tmp2 = self.l[:]
        for i in range(self.list.count()):
            self.tmp1.append(self.list.item(i).text())

    def Moved(self):
        for i in range(self.list.count()):
            if self.list.item(i).text() == self.tmp1[i]:
                continue
            else:
                self.l[i] = self.tmp2[self.tmp1.index(
                    self.list.item(i).text())]

    def Saved(self):
        with open('CPlayerlist.txt', 'w') as f:
            for i in range(self.list.count()):
                f.write('%s,%s\n' % (self.list.item(i).text(), self.l[i]))
        QMessageBox.information(self, '保存', '播放列表保存成功!')

    def Listhide(self):
        if self.listtag:
            self.frame.hide()
            self.listtag = False
        else:
            self.frame.show()
            self.listtag = True
        self.ratio()

    def ratio(self):
        QApplication.processEvents()
        self.player.video_set_aspect_ratio(
            '%s:%s' % (self.lmedia.width(), self.lmedia.height()))

    def Loop(self):
        if self.loop == 0:
            self.loop = 1
            self.bloop.setIcon(QIcon(idirwithloop))
            self.bloop.setToolTip('循环播放,快捷键“l”')
        else:
            self.loop = 0
            self.bloop.setIcon(QIcon(idirwithoutloop))
            self.bloop.setToolTip('取消循环,快捷键“l”')

    def set_window(self, winid):
        if platform.system() == 'Windows':
            self.player.set_hwnd(winid)
        elif platform.system() == 'Linux':
            self.player.set_xwindow(winid)
        else:
            self.player.set_nsobject(winid)

    def Play(self):
        if self.flag:
            try:
                self.playitem = self.l[self.list.currentRow()]
                self.player.set_mrl("%s" % self.playitem)
                self.set_window(int(self.lmedia.winId()))
                self.ratio()
                self.player.play()
                self.timer = QTimer()
                self.timer.start(100)
                self.timer.timeout.connect(self.Show)
                self.steptimer = QTimer()
                self.steptimer.start(1000)
                self.steptimer.timeout.connect(self.Step)
                self.flag = False
                self.bplay.setIcon(QIcon(idirpause))
                self.bplay.setToolTip('暂停,快捷键“Space”')
            except:
                QMessageBox.warning(self, '错误', '找不到要播放的文件!')
        else:
            if self.l[self.list.currentRow()] == self.playitem:
                if self.player.is_playing():
                    self.player.pause()
                    self.steptimer.stop()
                    self.bplay.setIcon(QIcon(idirplay))
                    self.bplay.setToolTip('播放,快捷键“Space”')
                else:
                    self.player.play()
                    self.steptimer.start()
                    self.bplay.setIcon(QIcon(idirpause))
                    self.bplay.setToolTip('暂停,快捷键“Space”')
            else:
                self.playitem = self.l[self.list.currentRow()]
                self.step = 0
                self.stime.setValue(0)
                self.player.set_mrl("%s" % self.playitem)
                self.player.play()
                self.timer.start()
                self.steptimer.start()
                self.bplay.setIcon(QIcon(idirpause))
                self.bplay.setToolTip('暂停,快捷键“Space”')

    def Show(self):
        self.mediatime = self.player.get_length() / 1000
        self.stime.setMaximum(int(self.mediatime))
        mediamin, mediasec = divmod(self.mediatime, 60)
        mediahour, mediamin = divmod(mediamin, 60)
        playmin, playsec = divmod(self.step, 60)
        playhour, playmin = divmod(playmin, 60)
        self.ltime.setText(
            '%02d:%02d:%02d/%02d:%02d:%02d' %
            (playhour, playmin, playsec, mediahour, mediamin, mediasec))

    def Stop(self):
        if self.flag == False:
            Thread(target=self.Threadstop, daemon=True).start()
            self.timer.stop()
            self.steptimer.stop()
            self.step = 0
            self.loop = 1
            self.flag = True
            self.stime.setValue(0)
            self.ltime.setText('')
            self.bplay.setIcon(QIcon(idirplay))
            self.bplay.setToolTip('播放,快捷键“Space”')

    def Threadstop(self):
        self.player.stop()

    def Full(self):
        if self.fulltag:
            self.frame.hide()
            self.frame_2.hide()
            self.showFullScreen()
            self.bfull.setIcon(QIcon(idirexitfullscreen))
            self.bfull.setToolTip('退出全屏,快捷键“f”')
            self.fulltag = False
        else:
            self.frame.show()
            self.frame_2.show()
            self.showNormal()
            self.bfull.setIcon(QIcon(idirexpandfullscreen))
            self.bfull.setToolTip('全屏,快捷键“f”')
            self.fulltag = True

    def Curvol(self):
        self.curvol = self.svolume.value()

    def Mute(self):
        if self.flag == False:
            if self.player.audio_get_volume() != 0:
                self.player.audio_set_volume(0)
                self.bmute.setIcon(QIcon(idirwithoutvolume))
                self.bmute.setToolTip('取消静音,快捷键“m”')
                self.tag = False
            else:
                if self.svolume.value() != 0:
                    self.player.audio_set_volume(self.svolume.value())
                else:
                    self.player.audio_set_volume(self.curvol)
                    self.svolume.setValue(self.curvol)
                self.bmute.setIcon(QIcon(idirwithvolume))
                self.bmute.setToolTip('静音,快捷键“m”')
                self.tag = True

    def Volume(self):
        if self.flag == False:
            if self.svolume.value() == 0:
                self.bmute.setIcon(QIcon(idirwithoutvolume))
                self.bmute.setToolTip('取消静音,快捷键“m”')
            else:
                self.bmute.setIcon(QIcon(idirwithvolume))
                self.bmute.setToolTip('静音,快捷键“m”')
            self.player.audio_set_volume(self.svolume.value())

    def Step(self):
        if self.step >= int(self.mediatime):
            self.step = int(self.mediatime)
            if self.loop == 0:
                self.step = 0
                self.stime.setValue(0)
                self.flag = True
                self.Play()
            else:
                if not self.player.is_playing(
                ) and self.player.get_state != vlc.State.Paused:
                    self.Stop()
        else:
            self.step += 1
            self.stime.setValue(self.step)

    def Slidechanged(self):
        self.step = self.stime.value()

    def Slidemoved(self):
        if self.flag == False:
            self.player.set_position(self.step / int(self.mediatime))

    def Fastforward(self):
        if self.flag == False:
            self.step += 10
            if self.step >= int(self.mediatime):
                self.stime.setValue(int(self.mediatime))
            self.stime.setValue(self.step)
            self.player.set_position(self.step / int(self.mediatime))

    def Fastback(self):
        if self.flag == False:
            self.step -= 10
            if self.step <= 0:
                self.step = 0
                self.stime.setValue(0)
            self.stime.setValue(self.step)
            self.player.set_position(self.step / int(self.mediatime))
    def __init__(self):
        super(PVPNApplet, self).__init__()
        self.country_codes = country_codes              # Keep a list of country codes

        # Init QSystemTrayIcon
        self.tray_icon = QSystemTrayIcon(self)
        self.tray_icon.setIcon(QIcon('icons/16x16/protonvpn-disconnected.png'))

        # Init libnotify
        Notify.init('ProtonVPN')

        # Refresh server list, store the resulting servers so we can populate the menu
        self.servers = self.update_available_servers()

        # Menu actions
        connect_fastest_action = QAction('Connect fastest', self)
        reconnect_action = QAction('Reconnect', self)
        disconnect_action = QAction('Disconnect', self)
        status_action = QAction('Status', self)
        connect_fastest_sc_action = QAction('Secure Core', self)
        connect_fastest_p2p_action = QAction('P2P', self)
        connect_fastest_tor_action = QAction('Tor', self)
        connect_random_action = QAction('Random', self)
        show_protonvpn_applet_version_action = QAction('About ProtonVPN-Applet', self)
        show_protonvpn_version_action = QAction('About ProtonVPN', self)
        quit_action = QAction('Exit', self)
        self.show_notifications_action = QAction('Show Notifications')
        self.show_notifications_action.setCheckable(True)
        self.show_notifications_action.setChecked(False)

        # Triggers
        quit_action.triggered.connect(qApp.quit)
        connect_fastest_action.triggered.connect(self.connect_fastest)
        disconnect_action.triggered.connect(self.disconnect_vpn)
        status_action.triggered.connect(self.status_vpn)
        show_protonvpn_applet_version_action.triggered.connect(self.show_protonvpn_applet_version)
        show_protonvpn_version_action.triggered.connect(self.get_protonvpn_version)
        connect_fastest_sc_action.triggered.connect(self.connect_fastest_sc)
        connect_fastest_p2p_action.triggered.connect(self.connect_fastest_p2p)
        connect_fastest_tor_action.triggered.connect(self.connect_fastest_tor)
        connect_random_action.triggered.connect(self.connect_random)
        reconnect_action.triggered.connect(self.reconnect_vpn)

        # Generate connection menu for specific countries
        connect_country_actions = []
        for country_name in self.get_available_countries(self.servers):

            # Get the ISO-3166 Alpha-2 country code
            country_name_to_code = {v: k for k, v in country_codes.country_codes.items()}
            country_code = country_name_to_code[country_name]

            # Dynamically create functions for connecting to each country; each function just passes its respective
            # country code to `self.connect_fastest_cc()`
            setattr(self, f'connect_fastest_{country_code}', functools.partial(self.connect_fastest_cc, country_code))

            # Generate an action for each country; set up the trigger; append to actions list
            country_action = QAction(f'{country_name}', self)
            country_action.triggered.connect(getattr(self, f'connect_fastest_{country_code}'))
            connect_country_actions.append(country_action)

        # Create a scrollable country connection menu
        connect_country_menu = QMenu("Country...", self)
        connect_country_menu.setStyleSheet('QMenu  { menu-scrollable: 1; }')
        connect_country_menu.addActions(connect_country_actions)

        # Generate connection menu
        connection_menu = QMenu("Other connections...", self)
        connection_menu.addMenu(connect_country_menu)
        connection_menu.addAction(connect_fastest_sc_action)
        connection_menu.addAction(connect_fastest_p2p_action)
        connection_menu.addAction(connect_fastest_tor_action)
        connection_menu.addAction(connect_random_action)

        # Draw menu
        tray_menu = QMenu()
        tray_menu.addAction(connect_fastest_action)
        tray_menu.addAction(reconnect_action)
        tray_menu.addMenu(connection_menu)
        tray_menu.addAction(disconnect_action)
        tray_menu.addAction(status_action)
        tray_menu.addSeparator()
        tray_menu.addAction(self.show_notifications_action)
        tray_menu.addAction(show_protonvpn_applet_version_action)
        tray_menu.addAction(show_protonvpn_version_action)
        tray_menu.addAction(quit_action)
        self.tray_icon.setContextMenu(tray_menu)
        self.tray_icon.show()

        # Polling thread
        self.start_polling()
Beispiel #42
0
class MainWindow(QMainWindow):
    def __init__(self):
        QMainWindow.__init__(self)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        self.ui.actionAdd_camera.triggered.connect(self.handle_add_camera_triggered)
        self.ui.actionOpen_dataset.triggered.connect(self.handle_open_dataset_triggered)
        self.ui.actionSave_dataset.triggered.connect(self.handle_save_dataset_triggered)
        self.ui.actionClose_dataset.triggered.connect(self.handle_close_dataset_triggered)
        self.ui.actionExport_to_JSON.triggered.connect(self.handle_export_to_json_triggered)

        self.startup_page = QWidget()
        self.startup_page_ui = Ui_StartupPage()
        self.startup_page_ui.setupUi(self.startup_page)

        self.startup_page_ui.addCamera_button.setDefaultAction(self.ui.actionAdd_camera)

        self.startup_page_ui.openDataset_button.setDefaultAction(self.ui.actionOpen_dataset)
        self.startup_page_ui.openDataset_button.setMenu(None)
        self.startup_page_ui.openDataset_button.setArrowType(Qt.NoArrow)

        self.startup_page_ui.label_recent_datasets_image_placeholder.setPixmap(
            QPixmap(':/icons/document-open-recent.svg'))
        self.startup_page_ui.label_recent_cameras_image_placeholder.setPixmap(
            QPixmap(':/icons/document-open-recent.svg'))

        self.startup_dataset_buttons: List[QToolButton] = []
        self.startup_camera_buttons: List[QToolButton] = []

        self.state = self.get_config_state()

        self.recent_datasets: List[Path] = []
        self.recent_datasets_menu = QMenu()
        self.recent_datasets_menu.setToolTipsVisible(True)
        self.recent_dataset_qactions = []

        for ds in self.state['recent_datasets']:
            self.add_dataset_entry_to_recent(Path(ds))

        if len(self.recent_datasets) == 0:
            self.startup_page_ui.verticalLayout_recentDatasets.addWidget(self.startup_page_ui.label_noRecentDatasets)
            self.startup_page_ui.label_noRecentDatasets.setAlignment(Qt.AlignHCenter)
            self.startup_page_ui.label_noRecentDatasets.show()

        self.recent_datasets_menu.triggered.connect(lambda action: self.open_dataset(Path(action.toolTip())))

        self.ui.actionOpen_dataset.setMenu(self.recent_datasets_menu)

        self.recent_cameras: List[Path] = []
        self.recent_cameras_menu = QMenu()
        self.recent_cameras_menu.setToolTipsVisible(True)
        self.recent_cameras_qactions: Dict[str, QAction] = {}


        for cam in self.state['recent_cameras']:
            self.add_camera_entry_to_recent(Path(cam))

        if len(self.recent_cameras) == 0:
            self.startup_page_ui.verticalLayout_recentCameras.addWidget(self.startup_page_ui.label_noRecentCameras)
            self.startup_page_ui.label_noRecentCameras.setAlignment(Qt.AlignHCenter)
            self.startup_page_ui.label_noRecentCameras.show()

        self.recent_cameras_menu.triggered.connect(self.handle_add_recent_camera_triggered)
        self.ui.actionAdd_camera.setMenu(self.recent_cameras_menu)

        self.dataset = Dataset()
        self.processing_widget = CameraProcessingWidget()
        self.processing_widget.set_dataset(self.dataset)
        self.processing_widget.camera_loaded.connect(self.handle_camera_added)
        self.processing_widget.processing_started.connect(self.show_progress_bar)
        self.processing_widget.processing_updated.connect(self.update_progress_bar)
        self.processing_widget.processing_stopped.connect(self.hide_progress_bar)
        self.processing_widget.stick_verification_needed.connect(self.notify_user)
        self.processing_widget.no_cameras_open.connect(self.handle_no_cameras_open)

        self.ui.stackedWidget.removeWidget(self.ui.page)
        self.ui.stackedWidget.removeWidget(self.ui.page_2)

        self.ui.stackedWidget.addWidget(self.startup_page)
        self.ui.stackedWidget.addWidget(self.processing_widget)
        self.ui.stackedWidget.setCurrentIndex(0)
        self.setCentralWidget(self.ui.stackedWidget)
        self.ui.toolBar.hide()

        self.progress_bar = QProgressBar()
        self.progress_bar.hide()

        skipped_q_indicator = QPixmap(24, 24)
        skipped_q_indicator.fill(QColor(150, 150, 150))
        skipped_label = QLabel()
        skipped_label.setPixmap(skipped_q_indicator)

        bad_q_indicator = QPixmap(24, 24)
        bad_q_indicator.fill(QColor(200, 0, 0))
        bad_label = QLabel()
        bad_label.setPixmap(bad_q_indicator)
        ok_q_indicator = QPixmap(24, 24)
        ok_q_indicator.fill(QColor(200, 100, 0))
        ok_label = QLabel()
        ok_label.setPixmap(ok_q_indicator)
        good_q_indicator = QPixmap(24, 24)
        good_q_indicator.fill(QColor(100, 200, 0))
        good_label = QLabel()
        good_label.setPixmap(good_q_indicator)

        status_box = QHBoxLayout()
        indicator_box = QHBoxLayout()

        self.statusBar().hide()
        indicator_box.addWidget(QLabel("Stick visibility:\t"))
        indicator_box.addWidget(skipped_label)
        indicator_box.addWidget(QLabel(" Skipped  "))
        indicator_box.addWidget(bad_label)
        indicator_box.addWidget(QLabel(" Bad  "))
        indicator_box.addWidget(ok_label)
        indicator_box.addWidget(QLabel(" OK  "))
        indicator_box.addWidget(good_label)
        indicator_box.addWidget(QLabel(" Good  "))
        status_box.addItem(indicator_box)
        status_box.addWidget(self.progress_bar)
        status_box.setAlignment(indicator_box, Qt.AlignLeft)
        status_box.setSpacing(100)
        status_widget = QWidget()
        status_widget.setLayout(status_box)
        self.statusBar().addPermanentWidget(status_widget, 1)

        self.progress_bar.setFormat("%v / %m")
        self.sys_tray = QSystemTrayIcon(QIcon(':icons/snowflake.svg'))
        self.sys_tray.show()

        self.thread_pool = QThreadPool()

    def handle_save_dataset_triggered(self, checked: bool):
        if self.dataset.path == Path("."):
            file_dialog = QFileDialog(self)
            file_dialog.setWindowTitle("Save dataset")
            file_dialog.setFileMode(QFileDialog.AnyFile)
            file_dialog.setNameFilter("*.json")
            file_dialog.setAcceptMode(QFileDialog.AcceptSave)
            if file_dialog.exec_():
                file_path = Path(file_dialog.selectedFiles()[0])
                self.dataset.save_as(file_path)
                self.add_dataset_entry_to_recent(file_path)
                self.save_state()
        else:
            self.dataset.save()

    def handle_add_camera_triggered(self, checked: bool):
        file_dialog = QFileDialog(self)
        file_dialog.setFileMode(QFileDialog.Directory)
        if file_dialog.exec_():
            if self.dataset.add_camera(Path(file_dialog.selectedFiles()[0])):
                self.add_camera_entry_to_recent(Path(file_dialog.selectedFiles()[0]))

    def handle_open_dataset_triggered(self, checked: bool):
        file_dialog = QFileDialog(self)
        file_dialog.setNameFilter("*.json")
        file_dialog.setFileMode(QFileDialog.AnyFile)
        file_dialog.setWindowTitle("Open dataset file")
        if file_dialog.exec_():
            file_path = Path(file_dialog.selectedFiles()[0])
            if not self.open_dataset(file_path):
                return
            self.setWindowTitle(str(self.dataset.path))
            self.add_dataset_entry_to_recent(file_path)
            self.ui.actionOpen_dataset.setMenu(self.recent_datasets_menu)
            self.save_state()

    def handle_add_recent_camera_triggered(self, action: QAction):
        if self.dataset.add_camera(Path(action.toolTip())):
            self.add_camera_entry_to_recent(Path(action.toolTip()))
        else:
            QMessageBox.critical(self, "Failed to open camera folder", f'Could not open {action.toolTip()}')

    def connect_dataset_signals(self):
        self.dataset.camera_added.connect(self.handle_camera_added)

    def save_state(self):
        try:
            with open(Path(sys.argv[0]).parent / 'state.json', 'w') as f:
                self.state['recent_datasets'] = list(reversed(list(map(lambda p: str(p), self.recent_datasets))))
                self.state['recent_cameras'] = list(reversed(list(map(lambda p: str(p), self.recent_cameras))))
                json.dump(self.state, f)
        except PermissionError:
            pass

    def add_camera_entry_to_recent(self, p: Path):
        if not self.startup_page_ui.label_noRecentCameras.isHidden():
            self.startup_page_ui.label_noRecentCameras.hide()
            self.startup_page_ui.verticalLayout_recentCameras.removeWidget(self.startup_page_ui.label_noRecentCameras)
        if p not in self.recent_cameras:
            self.recent_cameras.insert(0, p)
            action = QAction(str(p.name))
            action.setToolTip(str(p))
            self.recent_cameras_qactions[str(p)] = action
            if len(self.recent_cameras_menu.actions()) > 0:
                self.recent_cameras_menu.insertAction(self.recent_cameras_menu.actions()[0], action)
            else:
                self.recent_cameras_menu.addAction(action)
            btn = QToolButton()
            btn.setDefaultAction(action)
            btn.setStyleSheet('font-size: 12pt')
            btn.setText(str(p.name))
            btn.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
            self.startup_camera_buttons.insert(0, btn)
            self.startup_page_ui.label_noRecentCameras.hide()
            self.startup_page_ui.verticalLayout_recentCameras.insertWidget(0, btn)
        else:
            action_idx = self.recent_cameras_menu.actions().index(self.recent_cameras_qactions[str(p)])
            btn = self.startup_page_ui.verticalLayout_recentCameras.itemAt(action_idx).widget() #self.startup_page_ui.verticalLayout_recentCameras.removeItem(btn)
            self.startup_page_ui.verticalLayout_recentCameras.removeWidget(btn)
            self.startup_page_ui.verticalLayout_recentCameras.insertWidget(0, btn)
            self.recent_cameras.remove(p)
            self.recent_cameras.insert(0, p)
            self.recent_cameras_menu.clear()
            for i in range(self.startup_page_ui.verticalLayout_recentCameras.count()):
                btn: QToolButton = self.startup_page_ui.verticalLayout_recentCameras.itemAt(i).widget()
                self.recent_cameras_menu.addAction(btn.defaultAction())
        if len(self.recent_cameras) > 10:
            self.remove_camera_entry_from_recent(self.recent_cameras[-1])

    def add_dataset_entry_to_recent(self, p: Path):
        if not self.startup_page_ui.label_noRecentDatasets.isHidden():
            self.startup_page_ui.label_noRecentDatasets.hide()
            self.startup_page_ui.verticalLayout_recentDatasets.removeWidget(self.startup_page_ui.label_noRecentDatasets)
        if p not in self.recent_datasets:
            self.recent_datasets.insert(0, p)
            action = QAction(str(p.name))
            action.setToolTip(str(p))
            self.recent_dataset_qactions.insert(0, action)
            if len(self.recent_datasets_menu.actions()) > 0:
                self.recent_datasets_menu.insertAction(self.recent_datasets_menu.actions()[0], action)
            else:
                self.recent_datasets_menu.addAction(action)
            btn = QToolButton()
            btn.setDefaultAction(action)
            btn.setStyleSheet('font-size: 12pt')
            btn.setText(str(p))
            btn.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
            self.startup_page_ui.verticalLayout_recentDatasets.insertWidget(0, btn)
            self.startup_dataset_buttons.append(btn)
        if len(self.recent_datasets) > 10:
            self.remove_dataset_entry_from_recent(self.recent_datasets[-1])

    def remove_dataset_entry_from_recent(self, path: Path):
        actions = list(filter(lambda action: action.toolTip() == str(path), self.recent_dataset_qactions))
        if len(actions) > 0:
            action = actions[0]
            btn = list(filter(lambda btn_: btn_.defaultAction() == action, self.startup_dataset_buttons))[0]
            self.startup_dataset_buttons.remove(btn)
            self.startup_page_ui.verticalLayout_recentDatasets.removeWidget(btn)
            self.recent_dataset_qactions.remove(action)
            self.recent_datasets_menu.removeAction(action)
            self.recent_datasets.remove(path)
            btn.setVisible(False)
            btn.deleteLater()
        if len(self.recent_datasets) == 0:
            self.startup_page_ui.verticalLayout_recentDatasets.addWidget(self.startup_page_ui.label_noRecentDatasets)
            self.startup_page_ui.label_noRecentDatasets.setAlignment(Qt.AlignHCenter)
            self.startup_page_ui.label_noRecentDatasets.show()

    def remove_camera_entry_from_recent(self, path: Path):
        action = self.recent_cameras_qactions[str(path)]
        btn = list(filter(lambda btn_: btn_.defaultAction() == action, self.startup_camera_buttons))[0]
        self.startup_camera_buttons.remove(btn)
        self.startup_page_ui.verticalLayout_recentCameras.removeWidget(btn)
        del self.recent_cameras_qactions[str(path)]
        self.recent_cameras_menu.removeAction(action)
        self.recent_cameras.remove(path)
        btn.setVisible(False)
        btn.deleteLater()
        if len(self.recent_cameras) == 0:
            self.startup_page_ui.verticalLayout_recentCameras.addWidget(self.startup_page_ui.label_noRecentCameras)
            self.startup_page_ui.label_noRecentCameras.setAlignment(Qt.AlignHCenter)
            self.startup_page_ui.label_noRecentCameras.show()

    def open_dataset(self, path: Path) -> bool:
        if len(self.dataset.cameras) > 0:
            self.dataset.save()
        if not os.access(path, mode=os.F_OK):
            self.remove_dataset_entry_from_recent(path)
            msg_box = QMessageBox(QMessageBox.Warning, 'File not found', f'The file {str(path)} does not exist.',
                                  QMessageBox.Open | QMessageBox.Close)
            if msg_box.exec_() == QMessageBox.Open:
                self.ui.actionOpen_dataset.trigger()
            else:
                msg_box.close()
            return False
        elif not os.access(path, mode=os.W_OK):
            self.remove_dataset_entry_from_recent(path)
            msg_box = QMessageBox(QMessageBox.Warning, 'Permission denied', f'The application can\'t modify the '
                                                                            f'file {str(path)}', QMessageBox.Close)
            msg_box.exec_()
            return False

        self.dataset = Dataset()
        self.processing_widget.set_dataset(self.dataset)
        if not self.dataset.load_from(path):
            self.processing_widget.cleanup()
            self.ui.stackedWidget.setCurrentIndex(0)
            return False
        return True

    @staticmethod
    def get_config_state():
        try:
            with open(Path(sys.argv[0]).parent / 'state.json', 'r') as f:
                state = json.load(f)
                if sorted(list(state.keys())) != ['first_time_startup', 'recent_cameras', 'recent_datasets']:
                    raise AttributeError
                return state
        except (FileNotFoundError, AttributeError) as _:
            return {
                'first_time_startup': True,
                'recent_cameras': [],
                'recent_datasets': [],
            }

    def closeEvent(self, event: QCloseEvent) -> None:
        if self.processing_widget is not None:
            self.processing_widget.cleanup()
        self.save_state()
        super().closeEvent(event)

    def handle_close_dataset_triggered(self):
        self.dataset.save()
        self.processing_widget.cleanup()
        self.dataset = Dataset()
        self.processing_widget.set_dataset(self.dataset)
        self.ui.stackedWidget.setCurrentIndex(0)
        self.ui.toolBar.hide()
        self.statusBar().hide()

    def handle_camera_added(self):
        self.ui.stackedWidget.setCurrentIndex(1)
        self.ui.toolBar.show()
        self.statusBar().show()

    def show_progress_bar(self, cam_name: str, photo_count: int):
        self.statusBar().showMessage(f'Processing camera {cam_name}', 0)
        self.progress_bar.setMinimum(0)
        self.progress_bar.setMaximum(photo_count)
        self.progress_bar.setValue(0)
        self.progress_bar.show()

    def update_progress_bar(self, value: int):
        self.progress_bar.setValue(value)

    def hide_progress_bar(self, val: int):
        self.statusBar().clearMessage()
        self.progress_bar.hide()

    def notify_user(self, camera_name: str):
        self.sys_tray.showMessage("Verification needed",
                                  f'Camera {camera_name} requires verification of stick positions',
                                  QSystemTrayIcon.NoIcon, 5000)

    def handle_no_cameras_open(self):
        self.ui.stackedWidget.setCurrentIndex(0)
        self.ui.toolBar.hide()
        self.statusBar().hide()

    def handle_export_to_json_triggered(self):
        file_dialog = QFileDialog(self)
        file_dialog.setWindowTitle("Save measurements")
        file_dialog.setFileMode(QFileDialog.AnyFile)
        file_dialog.setNameFilter("*.json")
        file_dialog.setAcceptMode(QFileDialog.AcceptSave)
        if file_dialog.exec_():
            file_path = Path(file_dialog.selectedFiles()[0])
            dat = self.processing_widget.dataset.get_json_data()
            with open(file_path, "w") as f:
                f.write(dat)
class PVPNApplet(QMainWindow):
    """Main applet body
    """
    tray_icon = None
    polling = True
    previous_status = None
    #auth = 'pkexec'
    auth = 'sudo'

    # Override the class constructor
    def __init__(self):
        super(PVPNApplet, self).__init__()
        self.country_codes = country_codes              # Keep a list of country codes

        # Init QSystemTrayIcon
        self.tray_icon = QSystemTrayIcon(self)
        self.tray_icon.setIcon(QIcon('icons/16x16/protonvpn-disconnected.png'))

        # Init libnotify
        Notify.init('ProtonVPN')

        # Refresh server list, store the resulting servers so we can populate the menu
        self.servers = self.update_available_servers()

        # Menu actions
        connect_fastest_action = QAction('Connect fastest', self)
        reconnect_action = QAction('Reconnect', self)
        disconnect_action = QAction('Disconnect', self)
        status_action = QAction('Status', self)
        connect_fastest_sc_action = QAction('Secure Core', self)
        connect_fastest_p2p_action = QAction('P2P', self)
        connect_fastest_tor_action = QAction('Tor', self)
        connect_random_action = QAction('Random', self)
        show_protonvpn_applet_version_action = QAction('About ProtonVPN-Applet', self)
        show_protonvpn_version_action = QAction('About ProtonVPN', self)
        quit_action = QAction('Exit', self)
        self.show_notifications_action = QAction('Show Notifications')
        self.show_notifications_action.setCheckable(True)
        self.show_notifications_action.setChecked(False)

        # Triggers
        quit_action.triggered.connect(qApp.quit)
        connect_fastest_action.triggered.connect(self.connect_fastest)
        disconnect_action.triggered.connect(self.disconnect_vpn)
        status_action.triggered.connect(self.status_vpn)
        show_protonvpn_applet_version_action.triggered.connect(self.show_protonvpn_applet_version)
        show_protonvpn_version_action.triggered.connect(self.get_protonvpn_version)
        connect_fastest_sc_action.triggered.connect(self.connect_fastest_sc)
        connect_fastest_p2p_action.triggered.connect(self.connect_fastest_p2p)
        connect_fastest_tor_action.triggered.connect(self.connect_fastest_tor)
        connect_random_action.triggered.connect(self.connect_random)
        reconnect_action.triggered.connect(self.reconnect_vpn)

        # Generate connection menu for specific countries
        connect_country_actions = []
        for country_name in self.get_available_countries(self.servers):

            # Get the ISO-3166 Alpha-2 country code
            country_name_to_code = {v: k for k, v in country_codes.country_codes.items()}
            country_code = country_name_to_code[country_name]

            # Dynamically create functions for connecting to each country; each function just passes its respective
            # country code to `self.connect_fastest_cc()`
            setattr(self, f'connect_fastest_{country_code}', functools.partial(self.connect_fastest_cc, country_code))

            # Generate an action for each country; set up the trigger; append to actions list
            country_action = QAction(f'{country_name}', self)
            country_action.triggered.connect(getattr(self, f'connect_fastest_{country_code}'))
            connect_country_actions.append(country_action)

        # Create a scrollable country connection menu
        connect_country_menu = QMenu("Country...", self)
        connect_country_menu.setStyleSheet('QMenu  { menu-scrollable: 1; }')
        connect_country_menu.addActions(connect_country_actions)

        # Generate connection menu
        connection_menu = QMenu("Other connections...", self)
        connection_menu.addMenu(connect_country_menu)
        connection_menu.addAction(connect_fastest_sc_action)
        connection_menu.addAction(connect_fastest_p2p_action)
        connection_menu.addAction(connect_fastest_tor_action)
        connection_menu.addAction(connect_random_action)

        # Draw menu
        tray_menu = QMenu()
        tray_menu.addAction(connect_fastest_action)
        tray_menu.addAction(reconnect_action)
        tray_menu.addMenu(connection_menu)
        tray_menu.addAction(disconnect_action)
        tray_menu.addAction(status_action)
        tray_menu.addSeparator()
        tray_menu.addAction(self.show_notifications_action)
        tray_menu.addAction(show_protonvpn_applet_version_action)
        tray_menu.addAction(show_protonvpn_version_action)
        tray_menu.addAction(quit_action)
        self.tray_icon.setContextMenu(tray_menu)
        self.tray_icon.show()

        # Polling thread
        self.start_polling()

    def is_polling(self):
        return self.polling

    def kill_polling(self):
        self.polling = False

    def start_polling(self):
        self.polling = True
        self.polling_thread = Polling(self)
        self.polling_thread.start()

    def _connect_vpn(self, command):
        self.kill_polling()
        connect_thread = ConnectVPN(self, command)
        connect_thread.finished.connect(self.start_polling)
        connect_thread.start()

    def connect_fastest(self):
        self._connect_vpn(VPNCommand.connect_fastest.value)

    def connect_fastest_p2p(self):
        self._connect_vpn(VPNCommand.connect_fastest_p2p.value)

    def connect_fastest_sc(self):
        self._connect_vpn(VPNCommand.connect_fastest_sc.value)

    def connect_fastest_cc(self, cc):
        command = VPNCommand.connect_fastest_cc.value + f' {cc}'
        self._connect_vpn(command)

    def connect_fastest_tor(self):
        self._connect_vpn(VPNCommand.connect_fastest_tor.value)

    def connect_random(self):
        self._connect_vpn(VPNCommand.connect_random.value)

    def disconnect_vpn(self):
        disconnect_thread = DisconnectVPN(self)
        disconnect_thread.start()

    def status_vpn(self):
        status_thread = CheckStatus(self)
        status_thread.start()

    def reconnect_vpn(self):
        reconnect_thread = ReconnectVPN(self)
        reconnect_thread.start()

    # Override closeEvent to intercept the window closing event
    def closeEvent(self, event):
        event.ignore()
        self.hide()

    def show_notifications(self):
        return self.show_notifications_action.isChecked()

    def show_protonvpn_applet_version(self):
        """Show the protonvpn-applet version.
        """

        name = '© 2020 Dónal Murray'
        email = '*****@*****.**'
        github = 'https://github.com/seadanda/protonvpn-applet'

        info = [f'<center>Version: {PROTONVPN_APPLET_VERSION}',
                f'{name}',
                f"<a href='{email}'>{email}</a>",
                f"<a href='{github}'>{github}</a></center>"]

        centered_text = f'<center>{"<br>".join(info)}</center>'

        QMessageBox.information(self, 'protonvpn-applet', centered_text)

    def get_protonvpn_version(self):
        """Start the CheckProtonVPNVersion thread; when it gets the version, it will call `self.show_protonvpn_version`
        """
        print('called get_protonvpn_version')
        check_protonvpn_version_thread = CheckProtonVPNVersion(self)
        check_protonvpn_version_thread.protonvpn_version_ready.connect(self.show_protonvpn_version)
        check_protonvpn_version_thread.start()

    def show_protonvpn_version(self, version):
        """
        Show the ProtonVPN version in a QMessageBox.

        Parameters
        ----------
        version : str
            Version number to be shown.
        """
        print('called show_protonvpn_version')
        QMessageBox.information(self, 'ProtonVPN Version', f'Version: {version}')

    def update_available_servers(self):
        utils.pull_server_data()
        return utils.get_servers()

    @staticmethod
    def get_available_countries(servers):
        return sorted(list({utils.get_country_name(server['ExitCountry']) for server in servers}))
Beispiel #44
0
    def __init__(self, parent=None):
        super().__init__()

        self.threads = []

        self.ignoreQuit = True
        self.columnWidth = 100
        self.numRow = 20
        self.numColumn = 2

        self.validDate = ["mon", "tues", "wed", "thurs", "fri",
                          "sat", "sun"]
        self.AM = "am"
        self.PM = "pm"

        self.data_path = os.path.abspath("UserData/GalaData.json")
        self.icon_path = os.path.abspath("Icon/orange.png")

        self.trayMenu = QMenu(self)
        self.trayMenu.addAction("Open", self.open_)
        self.trayMenu.addAction("Hide", self.hide)
        self.trayMenu.addAction("Quit", self.quit)

        self.tray = QSystemTrayIcon(QIcon(self.icon_path), self)
        self.tray.setContextMenu(self.trayMenu)
        self.tray.activated.connect(self.onClickEvent)
        self.tray.show()

        self.firstHeader = "Time"
        self.secondHeader = "Description"

        self.table = QTableWidget(self)
        self.table.setRowCount(self.numRow)
        self.table.setColumnCount(self.numColumn)
        self.table.setHorizontalHeaderLabels([self.firstHeader,
                                              self.secondHeader])
        # self.table.setColumnWidth(0, self.columnWidth)
        # self.table.setColumnWidth(1, self.columnWidth)

        self.tableScrollW = self.table.verticalScrollBar().sizeHint().width()
        self.tableHeaderW = self.table.horizontalHeader().length()
        self.tableVertHeaderW = self.table.verticalHeader().width()
        self.tableFrameW = self.table.frameWidth() * 2
        self.tableWidth = (self.tableScrollW
                           + self.tableHeaderW + self.tableFrameW)
        self.table.setFixedWidth(self.tableWidth)

        self.table.verticalHeader().hide()

        self.header = self.table.horizontalHeader()
        self.header.setSectionResizeMode(0, QHeaderView.Interactive)
        self.header.setSectionResizeMode(1, QHeaderView.Stretch)
        self.headerMidPoint = self.header.length() / 2
        self.header.setMinimumSectionSize(self.headerMidPoint * 0.10)
        self.header.setMaximumSectionSize(self.headerMidPoint * 1.90)

        self.saveButton = self.createButton("Save", self.saveButtonClick)
        self.galaButton = self.createButton("Gala", self.galaButtonClick)
        self.loadButton = self.createButton("Load", self.loadButtonClick)
        self.infoButton = self.createButton("Info", self.infoButtonClick)
        # self.checkButton = self.createButton("Check", self.checkButtonClick)
        self.clearButton = self.createButton("Clear", self.clearButtonClick)

        layout = QGridLayout(self)
        layout.addWidget(self.table, 0, 0, 1, 6)
        layout.addWidget(self.loadButton, 1, 0)
        layout.addWidget(self.saveButton, 1, 1)
        layout.addWidget(self.clearButton, 1, 2)
        # layout.addWidget(self.checkButton, 1, 3)
        layout.addWidget(self.infoButton, 1, 4)
        layout.addWidget(self.galaButton, 1, 5)
        # only vertical resize allowed
        # layout.setSizeConstraint(QLayout.SetFixedSize)
        self.setLayout(layout)

        self.autoLoad()  # load user data

        height = self.table.verticalHeader().width() * 20
        width = self.sizeHint().width()
        self.resize(width, height)
        self.setWindowIcon(QIcon(self.icon_path))
        self.setFixedWidth(width)
        self.setWindowTitle("Gala")
 def __init__(self, parent=None):
     super(MainWindow, self).__init__()
     self.statusBar().showMessage("Move Dial to Deform Microphone Voice !.")
     self.setWindowTitle(__doc__)
     self.setMinimumSize(240, 240)
     self.setMaximumSize(480, 480)
     self.resize(self.minimumSize())
     self.setWindowIcon(QIcon.fromTheme("audio-input-microphone"))
     self.tray = QSystemTrayIcon(self)
     self.center()
     QShortcut("Ctrl+q", self, activated=lambda: self.close())
     self.menuBar().addMenu("&File").addAction("Quit", lambda: exit())
     self.menuBar().addMenu("Sound").addAction(
         "STOP !", lambda: call("killall rec", shell=True)
     )
     windowMenu = self.menuBar().addMenu("&Window")
     windowMenu.addAction("Hide", lambda: self.hide())
     windowMenu.addAction("Minimize", lambda: self.showMinimized())
     windowMenu.addAction("Maximize", lambda: self.showMaximized())
     windowMenu.addAction("Restore", lambda: self.showNormal())
     windowMenu.addAction("FullScreen", lambda: self.showFullScreen())
     windowMenu.addAction("Center", lambda: self.center())
     windowMenu.addAction("Top-Left", lambda: self.move(0, 0))
     windowMenu.addAction("To Mouse", lambda: self.move_to_mouse_position())
     # widgets
     group0 = QGroupBox("Voice Deformation")
     self.setCentralWidget(group0)
     self.process = QProcess(self)
     self.process.error.connect(
         lambda: self.statusBar().showMessage("Info: Process Killed", 5000)
     )
     self.control = QDial()
     self.control.setRange(-10, 20)
     self.control.setSingleStep(5)
     self.control.setValue(0)
     self.control.setCursor(QCursor(Qt.OpenHandCursor))
     self.control.sliderPressed.connect(
         lambda: self.control.setCursor(QCursor(Qt.ClosedHandCursor))
     )
     self.control.sliderReleased.connect(
         lambda: self.control.setCursor(QCursor(Qt.OpenHandCursor))
     )
     self.control.valueChanged.connect(
         lambda: self.control.setToolTip(f"<b>{self.control.value()}")
     )
     self.control.valueChanged.connect(
         lambda: self.statusBar().showMessage(
             f"Voice deformation: {self.control.value()}", 5000
         )
     )
     self.control.valueChanged.connect(self.run)
     self.control.valueChanged.connect(lambda: self.process.kill())
     # Graphic effect
     self.glow = QGraphicsDropShadowEffect(self)
     self.glow.setOffset(0)
     self.glow.setBlurRadius(99)
     self.glow.setColor(QColor(99, 255, 255))
     self.control.setGraphicsEffect(self.glow)
     self.glow.setEnabled(False)
     # Timer to start
     self.slider_timer = QTimer(self)
     self.slider_timer.setSingleShot(True)
     self.slider_timer.timeout.connect(self.on_slider_timer_timeout)
     # an icon and set focus
     QLabel(self.control).setPixmap(
         QIcon.fromTheme("audio-input-microphone").pixmap(32)
     )
     self.control.setFocus()
     QVBoxLayout(group0).addWidget(self.control)
     self.menu = QMenu(__doc__)
     self.menu.addAction(__doc__).setDisabled(True)
     self.menu.setIcon(self.windowIcon())
     self.menu.addSeparator()
     self.menu.addAction(
         "Show / Hide",
         lambda: self.hide() if self.isVisible() else self.showNormal(),
     )
     self.menu.addAction("STOP !", lambda: call("killall rec", shell=True))
     self.menu.addSeparator()
     self.menu.addAction("Quit", lambda: exit())
     self.tray.setContextMenu(self.menu)
     self.make_trayicon()
Beispiel #46
0
class Gala(QWidget):
    """ Main window that holds the main layout """

    MAX_TIME = 604800  # self.maxTime == Monday 12:00 am or 0 seconds

    def __init__(self, parent=None):
        super().__init__()

        self.threads = []

        self.ignoreQuit = True
        self.columnWidth = 100
        self.numRow = 20
        self.numColumn = 2

        self.validDate = ["mon", "tues", "wed", "thurs", "fri",
                          "sat", "sun"]
        self.AM = "am"
        self.PM = "pm"

        self.data_path = os.path.abspath("UserData/GalaData.json")
        self.icon_path = os.path.abspath("Icon/orange.png")

        self.trayMenu = QMenu(self)
        self.trayMenu.addAction("Open", self.open_)
        self.trayMenu.addAction("Hide", self.hide)
        self.trayMenu.addAction("Quit", self.quit)

        self.tray = QSystemTrayIcon(QIcon(self.icon_path), self)
        self.tray.setContextMenu(self.trayMenu)
        self.tray.activated.connect(self.onClickEvent)
        self.tray.show()

        self.firstHeader = "Time"
        self.secondHeader = "Description"

        self.table = QTableWidget(self)
        self.table.setRowCount(self.numRow)
        self.table.setColumnCount(self.numColumn)
        self.table.setHorizontalHeaderLabels([self.firstHeader,
                                              self.secondHeader])
        # self.table.setColumnWidth(0, self.columnWidth)
        # self.table.setColumnWidth(1, self.columnWidth)

        self.tableScrollW = self.table.verticalScrollBar().sizeHint().width()
        self.tableHeaderW = self.table.horizontalHeader().length()
        self.tableVertHeaderW = self.table.verticalHeader().width()
        self.tableFrameW = self.table.frameWidth() * 2
        self.tableWidth = (self.tableScrollW
                           + self.tableHeaderW + self.tableFrameW)
        self.table.setFixedWidth(self.tableWidth)

        self.table.verticalHeader().hide()

        self.header = self.table.horizontalHeader()
        self.header.setSectionResizeMode(0, QHeaderView.Interactive)
        self.header.setSectionResizeMode(1, QHeaderView.Stretch)
        self.headerMidPoint = self.header.length() / 2
        self.header.setMinimumSectionSize(self.headerMidPoint * 0.10)
        self.header.setMaximumSectionSize(self.headerMidPoint * 1.90)

        self.saveButton = self.createButton("Save", self.saveButtonClick)
        self.galaButton = self.createButton("Gala", self.galaButtonClick)
        self.loadButton = self.createButton("Load", self.loadButtonClick)
        self.infoButton = self.createButton("Info", self.infoButtonClick)
        # self.checkButton = self.createButton("Check", self.checkButtonClick)
        self.clearButton = self.createButton("Clear", self.clearButtonClick)

        layout = QGridLayout(self)
        layout.addWidget(self.table, 0, 0, 1, 6)
        layout.addWidget(self.loadButton, 1, 0)
        layout.addWidget(self.saveButton, 1, 1)
        layout.addWidget(self.clearButton, 1, 2)
        # layout.addWidget(self.checkButton, 1, 3)
        layout.addWidget(self.infoButton, 1, 4)
        layout.addWidget(self.galaButton, 1, 5)
        # only vertical resize allowed
        # layout.setSizeConstraint(QLayout.SetFixedSize)
        self.setLayout(layout)

        self.autoLoad()  # load user data

        height = self.table.verticalHeader().width() * 20
        width = self.sizeHint().width()
        self.resize(width, height)
        self.setWindowIcon(QIcon(self.icon_path))
        self.setFixedWidth(width)
        self.setWindowTitle("Gala")

    def autoLoad(self):
        self.load()

    def createButton(self, text, func):
        btn = QToolButton()
        btn.setText(text)
        btn.clicked.connect(func)
        return btn

    def onClickEvent(self, event):
        self.open_()

    def closeEvent(self, closeEvent):
        if self.ignoreQuit:
            closeEvent.ignore()
            self.hide()
        else:
            QCoreApplication.exit()

    def hideEvent(self, hideEvent):
        self.hide()

    def galaButtonClick(self):
        if self.validTimes(msgHint="Failed to start.") == True:
            for i in range(0, len(self.threads)):
                self.threads[i].stop()

            self.hide()

            now = Gala.timeNow()
            minTime = None
            num = None
            for row in range(0, self.numRow):
                txt = self.table.item(row, 0).text()
                if txt == "": continue

                end = Gala.parseTime(txt)
                end = Gala.normalizeTime(end[0], end[1], end[2])
                if end < now:
                    deltaTime = Gala.MAX_TIME - abs(end - now)
                else:
                    deltaTime = end - now

                if minTime is None or deltaTime < minTime:
                    minTime = deltaTime
                    num = row

            galaThread = GalaThread(self.table.item(num, 0).text(),
                                    self.table.item(num, 1).text())
            galaThread.signal.connect(self.delivMsg)
            galaThread.start()

            self.threads.append(galaThread)

    def saveButtonClick(self):
        self.setFocus()

        if self.validTimes(msgHint="Failed to save.") == True:
            os.makedirs("UserData", exist_ok=True)
            with open(self.data_path, 'w') as f:
                data = self.convertTableToJson()
                f.write(data)
                f.close()

    def loadButtonClick(self):
        self.load()

    def infoButtonClick(self):
        ex = GalaPopup("Examples",
                       "Tues 1:00 pm | Fri 3:00 pm | Sat 8:30 am\n\n"
                       "Valid days\n"
                       "Mon | Tues | Wed | Thurs | Fri | Sat | Sun\n\n"
                       "Valid times\n"
                       "12:00 am ... 11:59 pm")
        ex.setWindowTitle("Info")
        ex.exec_()

    def checkButtonClick(self):
        pass

    def clearButtonClick(self):
        self.clearTable()

    def load(self):
        self.loadJsonToTable(self.data_path)

    def open_(self):
        self.setVisible(True)
        self.raise_()

    def quit(self):
        self.ignoreQuit = False
        self.close()

    def hide(self):
        self.setVisible(False)

    @staticmethod
    def timeNow():
        now = datetime.datetime.now()
        nowTime = Gala.normalizeTime(now.weekday(), now.hour, now.minute)
        return nowTime

    @staticmethod
    def parseTime(text):
        text = text.split()

        weekday = text[0].lower()
        weekday = Weekday[weekday].value

        time = text[1].split(':')
        hour = int(time[0])
        min_ = int(time[1])

        amPM = text[2].lower()
        if amPM == "pm" and hour in range(1, 12):
            hour += 12
        elif hour is 12 and amPM is "am":
            hour -= 12

        return [weekday, hour, min_]

    @staticmethod
    def normalizeTime(day, hour, min_):
        """
         days      = [Mon...Sun] = [0...6]
         hours     = [0...24]
         minutes   = [0...60]
         Normalize to/with Monday 00:00 (12:00 am) as 0 or self.maxNorm
         in seconds.
         Example: Thursday 14:00 (2:00 pm) = 396,000 seconds from 0
        """
        day = day * 24 * 60 * 60
        hour = hour * 60 * 60
        min_ = min_ * 60
        normTime = day + hour + min_

        return normTime

    def delivMsg(self, timeMsg, msg):
        msgBox = GalaPopup("Gala delivery", timeMsg, msg)
        msgBox.exec_()

    def errTimeMsg(self, row, msgHint=""):
        errMsg = self.table.item(row, 0).text()
        err = GalaPopup(msgHint,
                        "Invalid time at row " + str(row + 1) + ":\n\n" +
                        errMsg)
        err.setWindowTitle("Invalid time")
        err.exec_()

    def validTimes(self, msgHint=""):
        """ Validate time
        Assume (or enforce) time format as "DATE TIME AM/PM".
        Example (from string to an array): ["Tues", "11:00", "am"]
        """
        # TODO More strict time check. i.e right now Tues 0:00 pm is okay...
        # maybe more checks or simplify some steps?
        for row in range(0, self.numRow):
            galaTime = self.table.item(row, 0)

            if galaTime is None or galaTime.text() is "": continue

            galaTime = galaTime.text().split()
            if len(galaTime) != 3:
                self.errTimeMsg(row, msgHint)
                return False

            date = galaTime[0]
            time = galaTime[1]
            am_pm = galaTime[2]

            if self.isDate(date) and self.isTime(time) and self.isAmPm(am_pm):
                continue
            else:
                self.errTimeMsg(row, msgHint)
                return False

        return True

    def isDate(self, date):
        date = date.lower()
        if date in self.validDate:
            return True
        return False

    def isTime(self, time):
        time = time.split(':')
        if len(time) != 2:
            return False

        hour = int(time[0])
        minute = int(time[1])
        hourRange = lambda: range(1, 13)
        minuteRange = lambda: range(0, 61)

        if hour in hourRange() and minute in minuteRange():
            return True
        else:
            return False

    def isAmPm(self, am_pm):
        if am_pm.lower() == self.AM or am_pm.lower() == self.PM:
            return True
        else:
            return False

    def clearTable(self):
        for row in range(0, self.numRow):
            for col in range(0, self.numColumn):
                g = QTableWidgetItem("")
                self.table.setItem(row, col, g)

    def convertTableToJson(self):
        items = []
        for row in range(0, self.numRow):
            item = {}
            item["row"] = row

            for col in range(0, self.numColumn):
                tableItem = self.table.item(row, col)
                if tableItem is None:
                    text = None
                else:
                    text = tableItem.text()

                if col == 0:
                    item["time"] = text
                elif col == 1:
                    item["description"] = text

            items.append(item)

        galaItems = {"gala_items": items}
        jsonString = json.dumps(galaItems, indent=4)
        return jsonString

    def loadJsonToTable(self, path):
        if not os.path.isfile(path):
            return 0

        galaData = open(path).read()
        galaData = json.loads(galaData)

        for i in range(0, len(galaData["gala_items"])):
            row = galaData["gala_items"][i]["row"]
            time = galaData["gala_items"][i]["time"]
            info = galaData["gala_items"][i]["description"]

            self.table.setItem(row, 0, QTableWidgetItem(time))
            self.table.setItem(row, 1, QTableWidgetItem(info))

    def convertTableToDict(self):
        jobArr = []
        for row in range(0, self.numRow):
            newJob = {}
            for col in range(0, self.numColumn):
                if col == 1:
                    newJob["time"] = self.table.item(row, col)
                elif col == 2:
                    newJob["description"] = self.table.item(row, col)
            jobArr.append(newJob)
        return jobArr
Beispiel #47
0

queue = Queue()
image_queue = ImageQueue()

# creates the interactions object
interactions = Interactions(sp, token_info, sp_oauth, exit_app, queue)

# UI
ui = Ui(interactions)

# Create icon
icon = QIcon(f"{ASSETS_DIR}img{sep}logo_small.png")

# Create tray
tray = QSystemTrayIcon()
tray.setIcon(icon)
tray.setVisible(True)
tray.setToolTip("Spotlightify")

# Create menu
menu = QMenu()
open_ui = QAction("Open")
open_ui.triggered.connect(show_ui)
menu.addAction(open_ui)

exit_ = QAction("Exit")
exit_.triggered.connect(exit_app)
menu.addAction(exit_)

listener_thread = Thread(target=listener, daemon=True, args=(open_ui,))
Beispiel #48
0
class ElectrumGui(Logger):
    @profiler
    def __init__(self, config, daemon, plugins):
        set_language(config.get('language', get_default_language()))
        Logger.__init__(self)
        # Uncomment this call to verify objects are being properly
        # GC-ed when windows are closed
        #network.add_jobs([DebugMem([Abstract_Wallet, SPV, Synchronizer,
        #                            ElectrumWindow], interval=5)])
        QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads)
        if hasattr(QtCore.Qt, "AA_ShareOpenGLContexts"):
            QtCore.QCoreApplication.setAttribute(
                QtCore.Qt.AA_ShareOpenGLContexts)
        if hasattr(QGuiApplication, 'setDesktopFileName'):
            QGuiApplication.setDesktopFileName('electrum.desktop')
        self.gui_thread = threading.current_thread()
        self.config = config
        self.daemon = daemon
        self.plugins = plugins
        self.windows = []
        self.efilter = OpenFileEventFilter(self.windows)
        self.app = QElectrumApplication(sys.argv)
        self.app.installEventFilter(self.efilter)
        self.app.setWindowIcon(read_QIcon("electrum.png"))
        # timer
        self.timer = QTimer(self.app)
        self.timer.setSingleShot(False)
        self.timer.setInterval(500)  # msec

        self.nd = None
        self.network_updated_signal_obj = QNetworkUpdatedSignalObject()
        self._num_wizards_in_progress = 0
        self._num_wizards_lock = threading.Lock()
        # init tray
        self.dark_icon = self.config.get("dark_icon", False)
        self.tray = QSystemTrayIcon(self.tray_icon(), None)
        self.tray.setToolTip('Electrum')
        self.tray.activated.connect(self.tray_activated)
        self.build_tray_menu()
        self.tray.show()
        self.app.new_window_signal.connect(self.start_new_window)
        self.set_dark_theme_if_needed()
        run_hook('init_qt', self)

    def set_dark_theme_if_needed(self):
        use_dark_theme = self.config.get('qt_gui_color_theme',
                                         'default') == 'dark'
        if use_dark_theme:
            try:
                import qdarkstyle
                self.app.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5())
            except BaseException as e:
                use_dark_theme = False
                self.logger.warning(f'Error setting dark theme: {repr(e)}')
        # Apply any necessary stylesheet patches
        patch_qt_stylesheet(use_dark_theme=use_dark_theme)
        # Even if we ourselves don't set the dark theme,
        # the OS/window manager/etc might set *a dark theme*.
        # Hence, try to choose colors accordingly:
        ColorScheme.update_from_widget(QWidget(), force_dark=use_dark_theme)

    def build_tray_menu(self):
        # Avoid immediate GC of old menu when window closed via its action
        if self.tray.contextMenu() is None:
            m = QMenu()
            self.tray.setContextMenu(m)
        else:
            m = self.tray.contextMenu()
            m.clear()
        for window in self.windows:
            submenu = m.addMenu(window.wallet.basename())
            submenu.addAction(_("Show/Hide"), window.show_or_hide)
            submenu.addAction(_("Close"), window.close)
        m.addAction(_("Dark/Light"), self.toggle_tray_icon)
        m.addSeparator()
        m.addAction(_("Exit Electrum"), self.close)

    def tray_icon(self):
        if self.dark_icon:
            return read_QIcon('electrum_dark_icon.png')
        else:
            return read_QIcon('electrum_light_icon.png')

    def toggle_tray_icon(self):
        self.dark_icon = not self.dark_icon
        self.config.set_key("dark_icon", self.dark_icon, True)
        self.tray.setIcon(self.tray_icon())

    def tray_activated(self, reason):
        if reason == QSystemTrayIcon.DoubleClick:
            if all([w.is_hidden() for w in self.windows]):
                for w in self.windows:
                    w.bring_to_top()
            else:
                for w in self.windows:
                    w.hide()

    def close(self):
        for window in self.windows:
            window.close()

    def new_window(self, path, uri=None):
        # Use a signal as can be called from daemon thread
        self.app.new_window_signal.emit(path, uri)

    def show_network_dialog(self, parent):
        if not self.daemon.network:
            parent.show_warning(_(
                'You are using Electrum in offline mode; restart Electrum if you want to get connected'
            ),
                                title=_('Offline'))
            return
        if self.nd:
            self.nd.on_update()
            self.nd.show()
            self.nd.raise_()
            return
        self.nd = NetworkDialog(self.daemon.network, self.config,
                                self.network_updated_signal_obj)
        self.nd.show()

    def _create_window_for_wallet(self, wallet):
        w = ElectrumWindow(self, wallet)
        self.windows.append(w)
        self.build_tray_menu()
        # FIXME: Remove in favour of the load_wallet hook
        run_hook('on_new_window', w)
        w.warn_if_testnet()
        w.warn_if_watching_only()
        return w

    def count_wizards_in_progress(func):
        def wrapper(self: 'ElectrumGui', *args, **kwargs):
            with self._num_wizards_lock:
                self._num_wizards_in_progress += 1
            try:
                return func(self, *args, **kwargs)
            finally:
                with self._num_wizards_lock:
                    self._num_wizards_in_progress -= 1

        return wrapper

    @count_wizards_in_progress
    def start_new_window(self, path, uri, *, app_is_starting=False):
        '''Raises the window for the wallet if it is open.  Otherwise
        opens the wallet and creates a new window for it'''
        wallet = None
        try:
            wallet = self.daemon.load_wallet(path, None)
        except BaseException as e:
            self.logger.exception('')
            custom_message_box(icon=QMessageBox.Warning,
                               parent=None,
                               title=_('Error'),
                               text=_('Cannot load wallet') + ' (1):\n' +
                               str(e))
            # if app is starting, still let wizard to appear
            if not app_is_starting:
                return
        if not wallet:
            try:
                wallet = self._start_wizard_to_select_or_create_wallet(path)
            except (WalletFileException, BitcoinException) as e:
                self.logger.exception('')
                custom_message_box(icon=QMessageBox.Warning,
                                   parent=None,
                                   title=_('Error'),
                                   text=_('Cannot load wallet') + ' (2):\n' +
                                   str(e))
        if not wallet:
            return
        # create or raise window
        try:
            for window in self.windows:
                if window.wallet.storage.path == wallet.storage.path:
                    break
            else:
                window = self._create_window_for_wallet(wallet)
        except BaseException as e:
            self.logger.exception('')
            custom_message_box(icon=QMessageBox.Warning,
                               parent=None,
                               title=_('Error'),
                               text=_('Cannot create window for wallet') +
                               ':\n' + str(e))
            if app_is_starting:
                wallet_dir = os.path.dirname(path)
                path = os.path.join(wallet_dir,
                                    get_new_wallet_name(wallet_dir))
                self.start_new_window(path, uri)
            return
        if uri:
            window.pay_to_URI(uri)
        window.bring_to_top()
        window.setWindowState(window.windowState() & ~QtCore.Qt.WindowMinimized
                              | QtCore.Qt.WindowActive)

        window.activateWindow()
        return window

    def _start_wizard_to_select_or_create_wallet(
            self, path) -> Optional[Abstract_Wallet]:
        wizard = InstallWizard(self.config, self.app, self.plugins)
        try:
            path, storage = wizard.select_storage(path, self.daemon.get_wallet)
            # storage is None if file does not exist
            if storage is None:
                wizard.path = path  # needed by trustedcoin plugin
                wizard.run('new')
                storage = wizard.create_storage(path)
            else:
                wizard.run_upgrades(storage)
        except (UserCancelled, GoBack):
            return
        except WalletAlreadyOpenInMemory as e:
            return e.wallet
        finally:
            wizard.terminate()
        # return if wallet creation is not complete
        if storage is None or storage.get_action():
            return
        wallet = Wallet(storage)
        wallet.start_network(self.daemon.network)
        self.daemon.add_wallet(wallet)
        return wallet

    def close_window(self, window: ElectrumWindow):
        if window in self.windows:
            self.windows.remove(window)
        self.build_tray_menu()
        # save wallet path of last open window
        if not self.windows:
            self.config.save_last_wallet(window.wallet)
        run_hook('on_close_window', window)
        self.daemon.stop_wallet(window.wallet.storage.path)

    def init_network(self):
        # Show network dialog if config does not exist
        if self.daemon.network:
            if self.config.get('auto_connect') is None:
                wizard = InstallWizard(self.config, self.app, self.plugins)
                wizard.init_network(self.daemon.network)
                wizard.terminate()

    def main(self):
        try:
            self.init_network()
        except UserCancelled:
            return
        except GoBack:
            return
        except BaseException as e:
            self.logger.exception('')
            return
        self.timer.start()
        self.config.open_last_wallet()
        path = self.config.get_wallet_path()
        if not self.start_new_window(
                path, self.config.get('url'), app_is_starting=True):
            return
        signal.signal(signal.SIGINT, lambda *args: self.app.quit())

        def quit_after_last_window():
            # keep daemon running after close
            if self.config.get('daemon'):
                return
            # check if a wizard is in progress
            with self._num_wizards_lock:
                if self._num_wizards_in_progress > 0 or len(self.windows) > 0:
                    return
            self.app.quit()

        self.app.setQuitOnLastWindowClosed(
            False)  # so _we_ can decide whether to quit
        self.app.lastWindowClosed.connect(quit_after_last_window)

        def clean_up():
            # Shut down the timer cleanly
            self.timer.stop()
            # clipboard persistence. see http://www.mail-archive.com/[email protected]/msg17328.html
            event = QtCore.QEvent(QtCore.QEvent.Clipboard)
            self.app.sendEvent(self.app.clipboard(), event)
            self.tray.hide()

        self.app.aboutToQuit.connect(clean_up)

        # main loop
        self.app.exec_()
        # on some platforms the exec_ call may not return, so use clean_up()

    def stop(self):
        self.logger.info('closing GUI')
        self.app.quit()
Beispiel #49
0
class TriblerWindow(QMainWindow):
    resize_event = pyqtSignal()
    escape_pressed = pyqtSignal()
    received_search_completions = pyqtSignal(object)

    def on_exception(self, *exc_info):
        if self.exception_handler_called:
            # We only show one feedback dialog, even when there are two consecutive exceptions.
            return

        self.exception_handler_called = True

        if self.tray_icon:
            try:
                self.tray_icon.deleteLater()
            except RuntimeError:
                # The tray icon might have already been removed when unloading Qt.
                # This is due to the C code actually being asynchronous.
                logging.debug(
                    "Tray icon already removed, no further deletion necessary."
                )
            self.tray_icon = None

        # Stop the download loop
        self.downloads_page.stop_loading_downloads()

        # Add info about whether we are stopping Tribler or not
        os.environ['TRIBLER_SHUTTING_DOWN'] = str(
            self.core_manager.shutting_down)

        if not self.core_manager.shutting_down:
            self.core_manager.stop(stop_app_on_shutdown=False)

        self.setHidden(True)

        if self.debug_window:
            self.debug_window.setHidden(True)

        exception_text = "".join(traceback.format_exception(*exc_info))
        logging.error(exception_text)

        dialog = FeedbackDialog(
            self, exception_text,
            self.core_manager.events_manager.tribler_version, self.start_time)
        dialog.show()

    def __init__(self, core_args=None, core_env=None):
        QMainWindow.__init__(self)

        QCoreApplication.setOrganizationDomain("nl")
        QCoreApplication.setOrganizationName("TUDelft")
        QCoreApplication.setApplicationName("Tribler")
        QCoreApplication.setAttribute(Qt.AA_UseHighDpiPixmaps)

        self.gui_settings = QSettings()
        api_port = int(
            get_gui_setting(self.gui_settings, "api_port", DEFAULT_API_PORT))
        dispatcher.update_worker_settings(port=api_port)

        self.navigation_stack = []
        self.tribler_started = False
        self.tribler_settings = None
        self.debug_window = None
        self.core_manager = CoreManager(api_port)
        self.pending_requests = {}
        self.pending_uri_requests = []
        self.download_uri = None
        self.dialog = None
        self.new_version_dialog = None
        self.start_download_dialog_active = False
        self.request_mgr = None
        self.search_request_mgr = None
        self.search_suggestion_mgr = None
        self.selected_torrent_files = []
        self.vlc_available = True
        self.has_search_results = False
        self.last_search_query = None
        self.last_search_time = None
        self.start_time = time.time()
        self.exception_handler_called = False
        self.token_refresh_timer = None

        sys.excepthook = self.on_exception

        uic.loadUi(get_ui_file_path('mainwindow.ui'), self)
        TriblerRequestManager.window = self
        self.tribler_status_bar.hide()

        # Load dynamic widgets
        uic.loadUi(get_ui_file_path('torrent_channel_list_container.ui'),
                   self.channel_page_container)
        self.channel_torrents_list = self.channel_page_container.items_list
        self.channel_torrents_detail_widget = self.channel_page_container.details_tab_widget
        self.channel_torrents_detail_widget.initialize_details_widget()
        self.channel_torrents_list.itemClicked.connect(
            self.channel_page.clicked_item)

        uic.loadUi(get_ui_file_path('torrent_channel_list_container.ui'),
                   self.search_page_container)
        self.search_results_list = self.search_page_container.items_list
        self.search_torrents_detail_widget = self.search_page_container.details_tab_widget
        self.search_torrents_detail_widget.initialize_details_widget()
        self.search_results_list.itemClicked.connect(
            self.on_channel_item_click)
        self.search_results_list.itemClicked.connect(
            self.search_results_page.clicked_item)
        self.token_balance_widget.mouseReleaseEvent = self.on_token_balance_click

        def on_state_update(new_state):
            self.loading_text_label.setText(new_state)

        self.core_manager.core_state_update.connect(on_state_update)

        self.magnet_handler = MagnetHandler(self.window)
        QDesktopServices.setUrlHandler("magnet", self.magnet_handler,
                                       "on_open_magnet_link")

        self.debug_pane_shortcut = QShortcut(QKeySequence("Ctrl+d"), self)
        self.debug_pane_shortcut.activated.connect(
            self.clicked_menu_button_debug)

        # Remove the focus rect on OS X
        for widget in self.findChildren(QLineEdit) + self.findChildren(
                QListWidget) + self.findChildren(QTreeWidget):
            widget.setAttribute(Qt.WA_MacShowFocusRect, 0)

        self.menu_buttons = [
            self.left_menu_button_home, self.left_menu_button_search,
            self.left_menu_button_my_channel,
            self.left_menu_button_subscriptions,
            self.left_menu_button_video_player,
            self.left_menu_button_downloads, self.left_menu_button_discovered
        ]

        self.video_player_page.initialize_player()
        self.search_results_page.initialize_search_results_page()
        self.settings_page.initialize_settings_page()
        self.subscribed_channels_page.initialize()
        self.edit_channel_page.initialize_edit_channel_page()
        self.downloads_page.initialize_downloads_page()
        self.home_page.initialize_home_page()
        self.loading_page.initialize_loading_page()
        self.discovering_page.initialize_discovering_page()
        self.discovered_page.initialize_discovered_page()
        self.trust_page.initialize_trust_page()

        self.stackedWidget.setCurrentIndex(PAGE_LOADING)

        # Create the system tray icon
        if QSystemTrayIcon.isSystemTrayAvailable():
            self.tray_icon = QSystemTrayIcon()
            use_monochrome_icon = get_gui_setting(self.gui_settings,
                                                  "use_monochrome_icon",
                                                  False,
                                                  is_bool=True)
            self.update_tray_icon(use_monochrome_icon)

            # Create the tray icon menu
            menu = self.create_add_torrent_menu()
            show_downloads_action = QAction('Show downloads', self)
            show_downloads_action.triggered.connect(
                self.clicked_menu_button_downloads)
            token_balance_action = QAction('Show token balance', self)
            token_balance_action.triggered.connect(
                lambda: self.on_token_balance_click(None))
            quit_action = QAction('Quit Tribler', self)
            quit_action.triggered.connect(self.close_tribler)
            menu.addSeparator()
            menu.addAction(show_downloads_action)
            menu.addAction(token_balance_action)
            menu.addSeparator()
            menu.addAction(quit_action)
            self.tray_icon.setContextMenu(menu)
        else:
            self.tray_icon = None

        self.hide_left_menu_playlist()
        self.left_menu_button_debug.setHidden(True)
        self.top_menu_button.setHidden(True)
        self.left_menu.setHidden(True)
        self.token_balance_widget.setHidden(True)
        self.settings_button.setHidden(True)
        self.add_torrent_button.setHidden(True)
        self.top_search_bar.setHidden(True)

        # Set various icons
        self.top_menu_button.setIcon(QIcon(get_image_path('menu.png')))

        self.search_completion_model = QStringListModel()
        completer = QCompleter()
        completer.setModel(self.search_completion_model)
        completer.setCompletionMode(QCompleter.UnfilteredPopupCompletion)
        self.item_delegate = QStyledItemDelegate()
        completer.popup().setItemDelegate(self.item_delegate)
        completer.popup().setStyleSheet("""
        QListView {
            background-color: #404040;
        }

        QListView::item {
            color: #D0D0D0;
            padding-top: 5px;
            padding-bottom: 5px;
        }

        QListView::item:hover {
            background-color: #707070;
        }
        """)
        self.top_search_bar.setCompleter(completer)

        # Toggle debug if developer mode is enabled
        self.window().left_menu_button_debug.setHidden(not get_gui_setting(
            self.gui_settings, "debug", False, is_bool=True))

        # Start Tribler
        self.core_manager.start(core_args=core_args, core_env=core_env)

        self.core_manager.events_manager.received_search_result_channel.connect(
            self.search_results_page.received_search_result_channel)
        self.core_manager.events_manager.received_search_result_torrent.connect(
            self.search_results_page.received_search_result_torrent)
        self.core_manager.events_manager.torrent_finished.connect(
            self.on_torrent_finished)
        self.core_manager.events_manager.new_version_available.connect(
            self.on_new_version_available)
        self.core_manager.events_manager.tribler_started.connect(
            self.on_tribler_started)
        self.core_manager.events_manager.events_started.connect(
            self.on_events_started)
        self.core_manager.events_manager.low_storage_signal.connect(
            self.on_low_storage)

        # Install signal handler for ctrl+c events
        def sigint_handler(*_):
            self.close_tribler()

        signal.signal(signal.SIGINT, sigint_handler)

        self.installEventFilter(self.video_player_page)

        # Resize the window according to the settings
        center = QApplication.desktop().availableGeometry(self).center()
        pos = self.gui_settings.value(
            "pos",
            QPoint(center.x() - self.width() * 0.5,
                   center.y() - self.height() * 0.5))
        size = self.gui_settings.value("size", self.size())

        self.move(pos)
        self.resize(size)

        self.show()

    def update_tray_icon(self, use_monochrome_icon):
        if not QSystemTrayIcon.isSystemTrayAvailable():
            return

        if use_monochrome_icon:
            self.tray_icon.setIcon(
                QIcon(QPixmap(get_image_path('monochrome_tribler.png'))))
        else:
            self.tray_icon.setIcon(
                QIcon(QPixmap(get_image_path('tribler.png'))))
        self.tray_icon.show()

    def on_low_storage(self):
        """
        Dealing with low storage space available. First stop the downloads and the core manager and ask user to user to
        make free space.
        :return:
        """
        self.downloads_page.stop_loading_downloads()
        self.core_manager.stop(False)
        close_dialog = ConfirmationDialog(
            self.window(), "<b>CRITICAL ERROR</b>",
            "You are running low on disk space (<100MB). Please make sure to have "
            "sufficient free space available and restart Tribler again.",
            [("Close Tribler", BUTTON_TYPE_NORMAL)])
        close_dialog.button_clicked.connect(lambda _: self.close_tribler())
        close_dialog.show()

    def on_torrent_finished(self, torrent_info):
        if self.tray_icon:
            self.window().tray_icon.showMessage(
                "Download finished",
                "Download of %s has finished." % torrent_info["name"])

    def show_loading_screen(self):
        self.top_menu_button.setHidden(True)
        self.left_menu.setHidden(True)
        self.token_balance_widget.setHidden(True)
        self.settings_button.setHidden(True)
        self.add_torrent_button.setHidden(True)
        self.top_search_bar.setHidden(True)
        self.stackedWidget.setCurrentIndex(PAGE_LOADING)

    def on_tribler_started(self):
        self.tribler_started = True

        self.top_menu_button.setHidden(False)
        self.left_menu.setHidden(False)
        self.token_balance_widget.setHidden(False)
        self.settings_button.setHidden(False)
        self.add_torrent_button.setHidden(False)
        self.top_search_bar.setHidden(False)

        # fetch the settings, needed for the video player port
        self.request_mgr = TriblerRequestManager()
        self.fetch_settings()

        self.downloads_page.start_loading_downloads()
        self.home_page.load_popular_torrents()
        if not self.gui_settings.value(
                "first_discover",
                False) and not self.core_manager.use_existing_core:
            self.window().gui_settings.setValue("first_discover", True)
            self.discovering_page.is_discovering = True
            self.stackedWidget.setCurrentIndex(PAGE_DISCOVERING)
        else:
            self.clicked_menu_button_home()

    def on_events_started(self, json_dict):
        self.setWindowTitle("Tribler %s" % json_dict["version"])

    def show_status_bar(self, message):
        self.tribler_status_bar_label.setText(message)
        self.tribler_status_bar.show()

    def hide_status_bar(self):
        self.tribler_status_bar.hide()

    def process_uri_request(self):
        """
        Process a URI request if we have one in the queue.
        """
        if len(self.pending_uri_requests) == 0:
            return

        uri = self.pending_uri_requests.pop()
        if uri.startswith('file') or uri.startswith('magnet'):
            self.start_download_from_uri(uri)

    def perform_start_download_request(self,
                                       uri,
                                       anon_download,
                                       safe_seeding,
                                       destination,
                                       selected_files,
                                       total_files=0,
                                       callback=None):
        # Check if destination directory is writable
        if not is_dir_writable(destination):
            ConfirmationDialog.show_message(
                self.window(), "Download error <i>%s</i>" % uri,
                "Insufficient write permissions to <i>%s</i> directory. "
                "Please add proper write permissions on the directory and "
                "add the torrent again." % destination, "OK")
            return

        selected_files_uri = ""
        if len(selected_files) != total_files:  # Not all files included
            selected_files_uri = u'&' + u''.join(
                u"selected_files[]=%s&" % quote_plus_unicode(filename)
                for filename in selected_files)[:-1]

        anon_hops = int(self.tribler_settings['download_defaults']
                        ['number_hops']) if anon_download else 0
        safe_seeding = 1 if safe_seeding else 0
        post_data = "uri=%s&anon_hops=%d&safe_seeding=%d&destination=%s%s" % (
            quote_plus_unicode(uri), anon_hops, safe_seeding, destination,
            selected_files_uri)
        post_data = post_data.encode(
            'utf-8')  # We need to send bytes in the request, not unicode

        request_mgr = TriblerRequestManager()
        request_mgr.perform_request(
            "downloads",
            callback if callback else self.on_download_added,
            method='PUT',
            data=post_data)

        # Save the download location to the GUI settings
        current_settings = get_gui_setting(self.gui_settings,
                                           "recent_download_locations", "")
        recent_locations = current_settings.split(
            ",") if len(current_settings) > 0 else []
        if isinstance(destination, unicode):
            destination = destination.encode('utf-8')
        encoded_destination = destination.encode('hex')
        if encoded_destination in recent_locations:
            recent_locations.remove(encoded_destination)
        recent_locations.insert(0, encoded_destination)

        if len(recent_locations) > 5:
            recent_locations = recent_locations[:5]

        self.gui_settings.setValue("recent_download_locations",
                                   ','.join(recent_locations))

    def on_new_version_available(self, version):
        if version == str(self.gui_settings.value('last_reported_version')):
            return

        self.new_version_dialog = ConfirmationDialog(
            self, "New version available",
            "Version %s of Tribler is available.Do you want to visit the "
            "website to download the newest version?" % version,
            [('IGNORE', BUTTON_TYPE_NORMAL), ('LATER', BUTTON_TYPE_NORMAL),
             ('OK', BUTTON_TYPE_NORMAL)])
        self.new_version_dialog.button_clicked.connect(
            lambda action: self.on_new_version_dialog_done(version, action))
        self.new_version_dialog.show()

    def on_new_version_dialog_done(self, version, action):
        if action == 0:  # ignore
            self.gui_settings.setValue("last_reported_version", version)
        elif action == 2:  # ok
            import webbrowser
            webbrowser.open("https://tribler.org")

        self.new_version_dialog.setParent(None)
        self.new_version_dialog = None

    def on_search_text_change(self, text):
        self.search_suggestion_mgr = TriblerRequestManager()
        self.search_suggestion_mgr.perform_request(
            "search/completions?q=%s" % text,
            self.on_received_search_completions)

    def on_received_search_completions(self, completions):
        if completions is None:
            return
        self.received_search_completions.emit(completions)
        self.search_completion_model.setStringList(completions["completions"])

    def fetch_settings(self):
        self.request_mgr = TriblerRequestManager()
        self.request_mgr.perform_request("settings",
                                         self.received_settings,
                                         capture_errors=False)

    def received_settings(self, settings):
        # If we cannot receive the settings, stop Tribler with an option to send the crash report.
        if 'error' in settings:
            raise RuntimeError(
                TriblerRequestManager.get_message_from_error(settings))

        self.tribler_settings = settings['settings']

        # Set the video server port
        self.video_player_page.video_player_port = settings["ports"][
            "video_server~port"]

        # Disable various components based on the settings
        if not self.tribler_settings['search_community']['enabled']:
            self.window().top_search_bar.setHidden(True)
        if not self.tribler_settings['video_server']['enabled']:
            self.left_menu_button_video_player.setHidden(True)
        self.downloads_creditmining_button.setHidden(
            not self.tribler_settings["credit_mining"]["enabled"])
        self.downloads_all_button.click()

        # process pending file requests (i.e. someone clicked a torrent file when Tribler was closed)
        # We do this after receiving the settings so we have the default download location.
        self.process_uri_request()

        # Set token balance refresh timer and load the token balance
        self.token_refresh_timer = QTimer()
        self.token_refresh_timer.timeout.connect(self.load_token_balance)
        self.token_refresh_timer.start(60000)

        self.load_token_balance()

    def on_top_search_button_click(self):
        current_ts = time.time()
        current_search_query = self.top_search_bar.text()

        if self.last_search_query and self.last_search_time \
                and self.last_search_query == self.top_search_bar.text() \
                and current_ts - self.last_search_time < 1:
            logging.info(
                "Same search query already sent within 500ms so dropping this one"
            )
            return

        self.left_menu_button_search.setChecked(True)
        self.has_search_results = True
        self.clicked_menu_button_search()
        self.search_results_page.perform_search(current_search_query)
        self.search_request_mgr = TriblerRequestManager()
        self.search_request_mgr.perform_request(
            "search?q=%s" % current_search_query, None)
        self.last_search_query = current_search_query
        self.last_search_time = current_ts

    def on_settings_button_click(self):
        self.deselect_all_menu_buttons()
        self.stackedWidget.setCurrentIndex(PAGE_SETTINGS)
        self.settings_page.load_settings()
        self.navigation_stack = []
        self.hide_left_menu_playlist()

    def on_token_balance_click(self, _):
        self.raise_window()
        self.deselect_all_menu_buttons()
        self.stackedWidget.setCurrentIndex(PAGE_TRUST)
        self.trust_page.load_trust_statistics()
        self.navigation_stack = []
        self.hide_left_menu_playlist()

    def load_token_balance(self):
        self.request_mgr = TriblerRequestManager()
        self.request_mgr.perform_request("trustchain/statistics",
                                         self.received_token_balance,
                                         capture_errors=False)

    def received_token_balance(self, statistics):
        if not statistics or "statistics" not in statistics:
            return

        statistics = statistics["statistics"]
        if 'latest_block' in statistics:
            balance = (statistics["latest_block"]["transaction"]["total_up"] - \
                      statistics["latest_block"]["transaction"]["total_down"]) / 1024 / 1024
            self.token_balance_label.setText("%d" % balance)
        else:
            self.token_balance_label.setText("0")

    def raise_window(self):
        self.setWindowState(self.windowState() & ~Qt.WindowMinimized
                            | Qt.WindowActive)
        self.raise_()
        self.activateWindow()

    def create_add_torrent_menu(self):
        """
        Create a menu to add new torrents. Shows when users click on the tray icon or the big plus button.
        """
        menu = TriblerActionMenu(self)

        browse_files_action = QAction('Import torrent from file', self)
        browse_directory_action = QAction('Import torrent(s) from directory',
                                          self)
        add_url_action = QAction('Import torrent from magnet/URL', self)

        browse_files_action.triggered.connect(self.on_add_torrent_browse_file)
        browse_directory_action.triggered.connect(
            self.on_add_torrent_browse_dir)
        add_url_action.triggered.connect(self.on_add_torrent_from_url)

        menu.addAction(browse_files_action)
        menu.addAction(browse_directory_action)
        menu.addAction(add_url_action)

        return menu

    def on_add_torrent_button_click(self, pos):
        self.create_add_torrent_menu().exec_(
            self.mapToGlobal(self.add_torrent_button.pos()))

    def on_add_torrent_browse_file(self):
        filenames = QFileDialog.getOpenFileNames(
            self, "Please select the .torrent file", QDir.homePath(),
            "Torrent files (*.torrent)")
        if len(filenames[0]) > 0:
            [
                self.pending_uri_requests.append(u"file:%s" % filename)
                for filename in filenames[0]
            ]
            self.process_uri_request()

    def start_download_from_uri(self, uri):
        self.download_uri = uri

        if get_gui_setting(self.gui_settings,
                           "ask_download_settings",
                           True,
                           is_bool=True):
            # Clear any previous dialog if exists
            if self.dialog:
                self.dialog.button_clicked.disconnect()
                self.dialog.setParent(None)
                self.dialog = None

            self.dialog = StartDownloadDialog(self, self.download_uri)
            self.dialog.button_clicked.connect(self.on_start_download_action)
            self.dialog.show()
            self.start_download_dialog_active = True
        else:
            # In the unlikely scenario that tribler settings are not available yet, try to fetch settings again and
            # add the download uri back to self.pending_uri_requests to process again.
            if not self.tribler_settings:
                self.fetch_settings()
                if self.download_uri not in self.pending_uri_requests:
                    self.pending_uri_requests.append(self.download_uri)
                return

            self.window().perform_start_download_request(
                self.download_uri,
                self.window().tribler_settings['download_defaults']
                ['anonymity_enabled'],
                self.window().tribler_settings['download_defaults']
                ['safeseeding_enabled'],
                self.tribler_settings['download_defaults']['saveas'], [], 0)
            self.process_uri_request()

    def on_start_download_action(self, action):
        if action == 1:
            if self.dialog and self.dialog.dialog_widget:
                self.window().perform_start_download_request(
                    self.download_uri,
                    self.dialog.dialog_widget.anon_download_checkbox.isChecked(
                    ),
                    self.dialog.dialog_widget.safe_seed_checkbox.isChecked(),
                    self.dialog.dialog_widget.destination_input.currentText(),
                    self.dialog.get_selected_files(),
                    self.dialog.dialog_widget.files_list_view.
                    topLevelItemCount())
            else:
                ConfirmationDialog.show_error(
                    self, "Tribler UI Error",
                    "Something went wrong. Please try again.")
                logging.exception(
                    "Error while trying to download. Either dialog or dialog.dialog_widget is None"
                )

        self.dialog.request_mgr.cancel_request(
        )  # To abort the torrent info request
        self.dialog.setParent(None)
        self.dialog = None
        self.start_download_dialog_active = False

        if action == 0:  # We do this after removing the dialog since process_uri_request is blocking
            self.process_uri_request()

    def on_add_torrent_browse_dir(self):
        chosen_dir = QFileDialog.getExistingDirectory(
            self, "Please select the directory containing the .torrent files",
            QDir.homePath(), QFileDialog.ShowDirsOnly)

        if len(chosen_dir) != 0:
            self.selected_torrent_files = [
                torrent_file
                for torrent_file in glob.glob(chosen_dir + "/*.torrent")
            ]
            self.dialog = ConfirmationDialog(
                self, "Add torrents from directory",
                "Are you sure you want to add %d torrents to Tribler?" %
                len(self.selected_torrent_files),
                [('ADD', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)])
            self.dialog.button_clicked.connect(
                self.on_confirm_add_directory_dialog)
            self.dialog.show()

    def on_confirm_add_directory_dialog(self, action):
        if action == 0:
            for torrent_file in self.selected_torrent_files:
                escaped_uri = u"file:%s" % pathname2url(torrent_file)
                self.perform_start_download_request(
                    escaped_uri,
                    self.window().tribler_settings['download_defaults']
                    ['anonymity_enabled'],
                    self.window().tribler_settings['download_defaults']
                    ['safeseeding_enabled'],
                    self.tribler_settings['download_defaults']['saveas'], [],
                    0)

        if self.dialog:
            self.dialog.setParent(None)
            self.dialog = None

    def on_add_torrent_from_url(self):
        # Make sure that the window is visible (this action might be triggered from the tray icon)
        self.raise_window()

        self.dialog = ConfirmationDialog(
            self,
            "Add torrent from URL/magnet link",
            "Please enter the URL/magnet link in the field below:",
            [('ADD', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)],
            show_input=True)
        self.dialog.dialog_widget.dialog_input.setPlaceholderText(
            'URL/magnet link')
        self.dialog.dialog_widget.dialog_input.setFocus()
        self.dialog.button_clicked.connect(
            self.on_torrent_from_url_dialog_done)
        self.dialog.show()

    def on_torrent_from_url_dialog_done(self, action):
        if self.dialog and self.dialog.dialog_widget:
            uri = self.dialog.dialog_widget.dialog_input.text()

            # Remove first dialog
            self.dialog.setParent(None)
            self.dialog = None

            if action == 0:
                self.start_download_from_uri(uri)

    def on_download_added(self, result):
        if len(self.pending_uri_requests
               ) == 0:  # Otherwise, we first process the remaining requests.
            self.window().left_menu_button_downloads.click()
        else:
            self.process_uri_request()

    def on_top_menu_button_click(self):
        if self.left_menu.isHidden():
            self.left_menu.show()
        else:
            self.left_menu.hide()

    def deselect_all_menu_buttons(self, except_select=None):
        for button in self.menu_buttons:
            if button == except_select:
                button.setEnabled(False)
                continue
            button.setEnabled(True)

            if button == self.left_menu_button_search and not self.has_search_results:
                button.setEnabled(False)

            button.setChecked(False)

    def clicked_menu_button_home(self):
        self.deselect_all_menu_buttons(self.left_menu_button_home)
        self.stackedWidget.setCurrentIndex(PAGE_HOME)
        self.navigation_stack = []
        self.hide_left_menu_playlist()

    def clicked_menu_button_search(self):
        self.deselect_all_menu_buttons(self.left_menu_button_search)
        self.stackedWidget.setCurrentIndex(PAGE_SEARCH_RESULTS)
        self.navigation_stack = []
        self.hide_left_menu_playlist()

    def clicked_menu_button_discovered(self):
        self.deselect_all_menu_buttons(self.left_menu_button_discovered)
        self.stackedWidget.setCurrentIndex(PAGE_DISCOVERED)
        self.discovered_page.load_discovered_channels()
        self.navigation_stack = []
        self.hide_left_menu_playlist()

    def clicked_menu_button_my_channel(self):
        self.deselect_all_menu_buttons(self.left_menu_button_my_channel)
        self.stackedWidget.setCurrentIndex(PAGE_EDIT_CHANNEL)
        self.edit_channel_page.load_my_channel_overview()
        self.navigation_stack = []
        self.hide_left_menu_playlist()

    def clicked_menu_button_video_player(self):
        self.deselect_all_menu_buttons(self.left_menu_button_video_player)
        self.stackedWidget.setCurrentIndex(PAGE_VIDEO_PLAYER)
        self.navigation_stack = []
        self.show_left_menu_playlist()

    def clicked_menu_button_downloads(self):
        self.raise_window()
        self.left_menu_button_downloads.setChecked(True)
        self.deselect_all_menu_buttons(self.left_menu_button_downloads)
        self.stackedWidget.setCurrentIndex(PAGE_DOWNLOADS)
        self.navigation_stack = []
        self.hide_left_menu_playlist()

    def clicked_menu_button_debug(self):
        if not self.debug_window:
            self.debug_window = DebugWindow(
                self.tribler_settings,
                self.core_manager.events_manager.tribler_version)
        self.debug_window.show()

    def clicked_menu_button_subscriptions(self):
        self.deselect_all_menu_buttons(self.left_menu_button_subscriptions)
        self.subscribed_channels_page.load_subscribed_channels()
        self.stackedWidget.setCurrentIndex(PAGE_SUBSCRIBED_CHANNELS)
        self.navigation_stack = []
        self.hide_left_menu_playlist()

    def hide_left_menu_playlist(self):
        self.left_menu_seperator.setHidden(True)
        self.left_menu_playlist_label.setHidden(True)
        self.left_menu_playlist.setHidden(True)

    def show_left_menu_playlist(self):
        self.left_menu_seperator.setHidden(False)
        self.left_menu_playlist_label.setHidden(False)
        self.left_menu_playlist.setHidden(False)

    def on_channel_item_click(self, channel_list_item):
        list_widget = channel_list_item.listWidget()
        from TriblerGUI.widgets.channel_list_item import ChannelListItem
        if isinstance(list_widget.itemWidget(channel_list_item),
                      ChannelListItem):
            channel_info = channel_list_item.data(Qt.UserRole)
            self.channel_page.initialize_with_channel(channel_info)
            self.navigation_stack.append(self.stackedWidget.currentIndex())
            self.stackedWidget.setCurrentIndex(PAGE_CHANNEL_DETAILS)

    def on_playlist_item_click(self, playlist_list_item):
        list_widget = playlist_list_item.listWidget()
        from TriblerGUI.widgets.playlist_list_item import PlaylistListItem
        if isinstance(list_widget.itemWidget(playlist_list_item),
                      PlaylistListItem):
            playlist_info = playlist_list_item.data(Qt.UserRole)
            self.playlist_page.initialize_with_playlist(playlist_info)
            self.navigation_stack.append(self.stackedWidget.currentIndex())
            self.stackedWidget.setCurrentIndex(PAGE_PLAYLIST_DETAILS)

    def on_page_back_clicked(self):
        try:
            prev_page = self.navigation_stack.pop()
            self.stackedWidget.setCurrentIndex(prev_page)
            if prev_page == PAGE_SEARCH_RESULTS:
                self.stackedWidget.widget(
                    prev_page).load_search_results_in_list()
            if prev_page == PAGE_SUBSCRIBED_CHANNELS:
                self.stackedWidget.widget(prev_page).load_subscribed_channels()
            if prev_page == PAGE_DISCOVERED:
                self.stackedWidget.widget(prev_page).load_discovered_channels()
        except IndexError:
            logging.exception("Unknown page found in stack")

    def on_edit_channel_clicked(self):
        self.stackedWidget.setCurrentIndex(PAGE_EDIT_CHANNEL)
        self.navigation_stack = []
        self.channel_page.on_edit_channel_clicked()

    def resizeEvent(self, _):
        # Resize home page cells
        cell_width = self.home_page_table_view.width(
        ) / 3 - 3  # We have some padding to the right
        cell_height = cell_width / 2 + 60

        for i in range(0, 3):
            self.home_page_table_view.setColumnWidth(i, cell_width)
            self.home_page_table_view.setRowHeight(i, cell_height)
        self.resize_event.emit()

    def exit_full_screen(self):
        self.top_bar.show()
        self.left_menu.show()
        self.video_player_page.is_full_screen = False
        self.showNormal()

    def close_tribler(self):
        if not self.core_manager.shutting_down:

            def show_force_shutdown():
                self.loading_text_label.setText(
                    "Tribler is taking longer than expected to shut down. You can force "
                    "Tribler to shutdown by pressing the button below. This might lead "
                    "to data loss.")
                self.window().force_shutdown_btn.show()

            if self.tray_icon:
                self.tray_icon.deleteLater()
            self.show_loading_screen()
            self.hide_status_bar()
            self.loading_text_label.setText("Shutting down...")

            self.shutdown_timer = QTimer()
            self.shutdown_timer.timeout.connect(show_force_shutdown)
            self.shutdown_timer.start(SHUTDOWN_WAITING_PERIOD)

            self.gui_settings.setValue("pos", self.pos())
            self.gui_settings.setValue("size", self.size())

            if self.core_manager.use_existing_core:
                # Don't close the core that we are using
                QApplication.quit()

            self.core_manager.stop()
            self.core_manager.shutting_down = True
            self.downloads_page.stop_loading_downloads()
            request_queue.clear()

    def closeEvent(self, close_event):
        self.close_tribler()
        close_event.ignore()

    def keyReleaseEvent(self, event):
        if event.key() == Qt.Key_Escape:
            self.escape_pressed.emit()
            if self.isFullScreen():
                self.exit_full_screen()

    def clicked_force_shutdown(self):
        process_checker = ProcessChecker()
        if process_checker.already_running:
            core_pid = process_checker.get_pid_from_lock_file()
            os.kill(int(core_pid), 9)
        # Stop the Qt application
        QApplication.quit()
Beispiel #50
0
    def startSplashWindowPhase(self) -> None:
        super().startSplashWindowPhase()
        i18n_catalog = i18nCatalog("uranium")
        self.showSplashMessage(i18n_catalog.i18nc("@info:progress", "Initializing package manager..."))
        self._package_manager.initialize()

        signal.signal(signal.SIGINT, signal.SIG_DFL)
        # This is done here as a lot of plugins require a correct gl context. If you want to change the framework,
        # these checks need to be done in your <framework>Application.py class __init__().

        self._configuration_error_message = ConfigurationErrorMessage(self,
              i18n_catalog.i18nc("@info:status", "Your configuration seems to be corrupt."),
              lifetime = 0,
              title = i18n_catalog.i18nc("@info:title", "Configuration errors")
              )
        # Remove, install, and then loading plugins
        self.showSplashMessage(i18n_catalog.i18nc("@info:progress", "Loading plugins..."))
        # Remove and install the plugins that have been scheduled
        self._plugin_registry.initializeBeforePluginsAreLoaded()
        self._plugin_registry.pluginLoadStarted.connect(self._displayLoadingPluginSplashMessage)
        self._loadPlugins()
        self._plugin_registry.pluginLoadStarted.disconnect(self._displayLoadingPluginSplashMessage)
        self._plugin_registry.checkRequiredPlugins(self.getRequiredPlugins())
        self.pluginsLoaded.emit()

        self.showSplashMessage(i18n_catalog.i18nc("@info:progress", "Updating configuration..."))
        with self._container_registry.lockFile():
            VersionUpgradeManager.getInstance().upgrade()

        # Load preferences again because before we have loaded the plugins, we don't have the upgrade routine for
        # the preferences file. Now that we have, load the preferences file again so it can be upgraded and loaded.
        self.showSplashMessage(i18n_catalog.i18nc("@info:progress", "Loading preferences..."))
        try:
            preferences_filename = Resources.getPath(Resources.Preferences, self._app_name + ".cfg")
            with open(preferences_filename, "r", encoding = "utf-8") as f:
                serialized = f.read()
            # This performs the upgrade for Preferences
            self._preferences.deserialize(serialized)
            self._preferences.setValue("general/plugins_to_remove", "")
            self._preferences.writeToFile(preferences_filename)
        except (EnvironmentError, UnicodeDecodeError):
            Logger.log("i", "The preferences file cannot be opened or it is corrupted, so we will use default values")

        self.processEvents()
        # Force the configuration file to be written again since the list of plugins to remove maybe changed
        try:
            self.readPreferencesFromConfiguration()
        except FileNotFoundError:
            Logger.log("i", "The preferences file '%s' cannot be found, will use default values",
                       self._preferences_filename)
            self._preferences_filename = Resources.getStoragePath(Resources.Preferences, self._app_name + ".cfg")
        Logger.info("Completed loading preferences.")

        # FIXME: This is done here because we now use "plugins.json" to manage plugins instead of the Preferences file,
        # but the PluginRegistry will still import data from the Preferences files if present, such as disabled plugins,
        # so we need to reset those values AFTER the Preferences file is loaded.
        self._plugin_registry.initializeAfterPluginsAreLoaded()

        # Check if we have just updated from an older version
        self._preferences.addPreference("general/last_run_version", "")
        last_run_version_str = self._preferences.getValue("general/last_run_version")
        if not last_run_version_str:
            last_run_version_str = self._version
        last_run_version = Version(last_run_version_str)
        current_version = Version(self._version)
        if last_run_version < current_version:
            self._just_updated_from_old_version = True
        self._preferences.setValue("general/last_run_version", str(current_version))
        self._preferences.writeToFile(self._preferences_filename)

        # Preferences: recent files
        self._preferences.addPreference("%s/recent_files" % self._app_name, "")
        file_names = self._preferences.getValue("%s/recent_files" % self._app_name).split(";")
        for file_name in file_names:
            if not os.path.isfile(file_name):
                continue
            self._recent_files.append(QUrl.fromLocalFile(file_name))

        if not self.getIsHeadLess():
            # Initialize System tray icon and make it invisible because it is used only to show pop up messages
            self._tray_icon = None
            if self._tray_icon_name:
                try:
                    self._tray_icon = QIcon(Resources.getPath(Resources.Images, self._tray_icon_name))
                    self._tray_icon_widget = QSystemTrayIcon(self._tray_icon)
                    self._tray_icon_widget.setVisible(False)
                    Logger.info("Created system tray icon.")
                except FileNotFoundError:
                    Logger.log("w", "Could not find the icon %s", self._tray_icon_name)
Beispiel #51
0
class NomnsParse(QApplication):
    """Application Control."""

    def __init__(self, *args):
        self.setAttribute(Qt.AA_EnableHighDpiScaling)

        super().__init__(*args)

        # Updates
        self._toggled = False
        self._log_reader = None

        # Load Parsers
        self._load_parsers()
        self._settings = SettingsWindow()

        # Tray Icon
        self._system_tray = QSystemTrayIcon()
        self._system_tray.setIcon(QIcon(resource_path('data/ui/icon.png')))
        self._system_tray.setToolTip("nParse")
        # self._system_tray.setContextMenu(self._create_menu())
        self._system_tray.activated.connect(self._menu)
        self._system_tray.show()

        # Turn On
        self._toggle()

        if self.new_version_available():
            self._system_tray.showMessage(
                "nParse Update".format(ONLINE_VERSION),
                "New version available!\ncurrent: {}\nonline: {}".format(
                    CURRENT_VERSION,
                    ONLINE_VERSION
                ),
                msecs=3000
            )

    def _load_parsers(self):
        self._parsers = [
            parsers.Maps(),
            parsers.Spells()
        ]
        for parser in self._parsers:
            if parser.name in config.data.keys() and 'geometry' in config.data[parser.name].keys():
                g = config.data[parser.name]['geometry']
                parser.setGeometry(g[0], g[1], g[2], g[3])
            if config.data[parser.name]['toggled']:
                parser.toggle()

    def _toggle(self):
        if not self._toggled:
            try:
                config.verify_paths()
            except ValueError as error:
                self._system_tray.showMessage(
                    error.args[0], error.args[1], msecs=3000)

            else:
                self._log_reader = logreader.LogReader(
                    config.data['general']['eq_log_dir'])
                self._log_reader.new_line.connect(self._parse)
                self._toggled = True
        else:
            if self._log_reader:
                self._log_reader.deleteLater()
                self._log_reader = None
            self._toggled = False

    def _parse(self, new_line):
        if new_line:
            timestamp, text = new_line  # (datetime, text)
            #  don't send parse to non toggled items, except maps.  always parse maps
            for parser in [parser for parser in self._parsers if config.data[parser.name]['toggled'] or parser.name == 'maps']:
                parser.parse(timestamp, text)

    def _menu(self, event):
        """Returns a new QMenu for system tray."""
        menu = QMenu()
        menu.setAttribute(Qt.WA_DeleteOnClose)
        # check online for new version
        new_version_text = ""
        if self.new_version_available():
            new_version_text = "Update Available {}".format(ONLINE_VERSION)
        else:
            new_version_text = "Version {}".format(CURRENT_VERSION)

        check_version_action = menu.addAction(new_version_text)
        menu.addSeparator()
        get_eq_dir_action = menu.addAction('Select EQ Logs Directory')
        menu.addSeparator()

        parser_toggles = set()
        for parser in self._parsers:
            toggle = menu.addAction(parser.name.title())
            toggle.setCheckable(True)
            toggle.setChecked(config.data[parser.name]['toggled'])
            parser_toggles.add(toggle)

        menu.addSeparator()
        settings_action = menu.addAction('Settings')
        menu.addSeparator()
        quit_action = menu.addAction('Quit')

        action = menu.exec_(QCursor.pos())

        if action == check_version_action:
            webbrowser.open('https://github.com/nomns/nparse/releases')

        elif action == get_eq_dir_action:
            dir_path = str(QFileDialog.getExistingDirectory(
                None, 'Select Everquest Logs Directory'))
            if dir_path:
                config.data['general']['eq_log_dir'] = dir_path
                config.save()
                self._toggle()

        elif action == settings_action:
            if self._settings.exec_():
                # Update required settings
                for parser in self._parsers:
                    if parser.windowOpacity() != config.data['general']['parser_opacity']:
                        parser.setWindowOpacity(
                            config.data['general']['parser_opacity'] / 100)
                        parser.settings_updated()
            # some settings are saved within other settings automatically
            # force update
            for parser in self._parsers:
                if parser.name == "spells":
                    parser.load_custom_timers()

        elif action == quit_action:
            if self._toggled:
                self._toggle()

            # save parser geometry
            for parser in self._parsers:
                g = parser.geometry()
                config.data[parser.name]['geometry'] = [
                    g.x(), g.y(), g.width(), g.height()
                ]
                config.save()

            self._system_tray.setVisible(False)
            self.quit()

        elif action in parser_toggles:
            parser = [
                parser for parser in self._parsers if parser.name == action.text().lower()][0]
            parser.toggle()

    def new_version_available(self):
        # this will only work if numbers go up
        try:
            for (o, c) in zip(ONLINE_VERSION.split('.'), CURRENT_VERSION.split('.')):
                if int(o) > int(c):
                    return True
        except:
            return False
Beispiel #52
0
class MainWindow(QMainWindow):
    """
            Сheckbox and system tray icons.
            Will initialize in the constructor.
    """
    check_box = None
    tray_icon = None

    # Override the class constructor
    def __init__(self, canvas):
        # Be sure to call the super class method
        super().__init__(None, Qt.WindowStaysOnTopHint)

        self.canvas = canvas

        self.setVisible(False)

        self.setMinimumSize(QSize(480, 80))
        self.setWindowTitle("Settings - Coming Soon")

        # Init QSystemTrayIcon
        self.tray_icon = QSystemTrayIcon(self)
        icon = os.path.join(os.path.dirname(os.path.abspath(__file__)),
                            'mdpi.png')
        self.tray_icon.setIcon(QIcon(icon))
        '''
            Define and add steps to work with the system tray icon
            show - show window
            hide - hide window
            exit - exit from application
        '''
        activate = QAction("Activate", self)
        show_action = QAction("Show", self)
        quit_action = QAction("Exit", self)
        hide_action = QAction("Hide", self)
        activate.triggered.connect(self.canvas.show)
        show_action.triggered.connect(self.show)
        hide_action.triggered.connect(self.hide)
        quit_action.triggered.connect(qApp.quit)
        tray_menu = QMenu()
        tray_menu.addAction(activate)
        tray_menu.addAction(show_action)
        tray_menu.addAction(hide_action)
        tray_menu.addAction(quit_action)

        self.tray_icon.setContextMenu(tray_menu)
        self.tray_icon.show()

        ui = QMainWindow()
        #ui.setWindowFlags(Qt.FramelessWindowHint)
        ui.setWindowModality(Qt.NonModal)
        ui.move(0, 0)
        ui.setMinimumSize(QSize(480, 80))
        ui.show()

    # Override closeEvent, to intercept the window closing event
    # The window will be closed only if there is no check mark in the check box
    def closeEvent(self, event):
        event.ignore()
        self.hide()
        self.tray_icon.showMessage("Tray Program",
                                   "Application was minimized to Tray",
                                   QSystemTrayIcon.Information, 2000)
Beispiel #53
0
 def setup_icon(self):
     self.icon_normal = QIcon(ALARM_PATH)
     self.icon = QSystemTrayIcon(self.icon_normal)
     self.icon_urgent = QIcon(ALARM_URGENT_PATH)
     self.icon_active = QIcon(ALARM_ACTIVE_PATH)
     self.icon.activated.connect(self.activate_menu)
class MyApp(QMainWindow):
    def __init__(self):
        super().__init__()
        print("ready MainWindow")
        self.initUI()
        self.is_pypresence_client_set = False
        self.p = Help()
        self.loadFile()

    #pyqt 종료 이벤트 override 됨
    def closeEvent(self, event):
        if self.is_pypresence_client_set:
            self.RPC.close()
            print("RPC setup done, close event done")
        else:
            print("RPC setup not done, close event done")
            print("check line empty:" + str(self.checkEmptyLine()))
        self.saveFile()
        self.p.close()

    def run_pypresence(self, *args):
        print("run pypresence : " + str(args))
        #pypresence 첫 실행 시
        #pypresence 주어진 client_id로 연결하고 상태를 업데이트
        if not self.is_pypresence_client_set:
            self.client_id = args[0]  #get Discord Developer Portal
            self.RPC = Presence(self.client_id, pipe=0)
            self.RPC.connect()
            self.is_pypresence_client_set = True
            startTime = datetime.datetime.today().timestamp()
            self.RPC.update(details=args[1],
                            state=args[2],
                            large_image=args[3],
                            start=startTime)
        #pypresence 첫 실행이 아닐 시 연결에 변화 없이 내용 업데이트
        elif self.is_pypresence_client_set:
            startTime = datetime.datetime.today().timestamp()
            self.RPC.update(details=args[1],
                            state=args[2],
                            large_image=args[3],
                            start=startTime)

    def initUI(self):
        #Get Foreground Window process name
        w = win32gui
        w.GetWindowText(w.GetForegroundWindow())
        pid = win32process.GetWindowThreadProcessId(w.GetForegroundWindow())
        print(psutil.Process(pid[-1]).name())
        #foreground 프로세스 변경 시 자동으로 사용자 상태가 현재 활성화 된 창을 표시할 수 있도록
        #SetWinEventHook 를 사용하여 foreground프로세스가 변경될 때 이벤트를 받을 수 있어야야함

        #메뉴바 액션
        exitAction = QAction('종료', self)
        exitAction.setShortcut('Ctrl+Q')
        exitAction.setStatusTip('프로그램 종료')
        exitAction.triggered.connect(qApp.quit)

        aboutAction = QAction('제작자', self)
        aboutAction.setStatusTip('제작자의 정보 : iro_bound')
        #exitAction.triggered.connect()

        #스테이터스 바
        #첫번째 호출 시 statusbar 생성, 이후 호출시 상태바 객체 반환
        #showMessage(str) 로 상태 메세지 변경
        self.statusBar().showMessage('DCGA 준비됨')

        #메뉴 바
        #--self.tray icon--
        self.tray = QSystemTrayIcon(QIcon('icon.png'), parent=self)
        self.tray.setToolTip("check out this app on self.tray icon")
        self.tray.setVisible(True)
        menubar = self.menuBar()
        menubar.setNativeMenuBar(False)  #Mac OS sync
        menu = QMenu('&도움말')
        filemenu = menu
        self.tray.setContextMenu(menu)
        filemenu.addAction(exitAction)
        filemenu.addAction(aboutAction)
        menubar.addMenu(menu)

        #센트럴 위젯
        central = QWidget()
        #central.setStyleSheet("background-color:#333333; border-style:solid; border-width: 1px; border-color: #555555; border-radius: 4px;")

        self.idLine = QLineEdit()
        self.idLine.setPlaceholderText("Client ID를 입력")
        self.contentLine = QLineEdit()
        self.contentLine.setPlaceholderText("원하는 내용을 입력")
        self.statusLine = QLineEdit()
        self.statusLine.setPlaceholderText("원하는 상태를 입력")
        self.imageLine = QLineEdit()
        self.imageLine.setPlaceholderText("이미지 이름")
        self.okButton = QPushButton("적용")
        self.okButton.setSizePolicy(QSizePolicy.Expanding,
                                    QSizePolicy.Expanding)
        self.doingButton = QPushButton("설정하기")

        labelID = QLabel("Client 설정 :")
        labelID.setAlignment(Qt.AlignCenter)
        label0 = QLabel("~하는 중 :")
        label0.setAlignment(Qt.AlignCenter)
        label1 = QLabel("내용 : ")
        label1.setAlignment(Qt.AlignCenter)
        label2 = QLabel("상태 : ")
        label2.setAlignment(Qt.AlignCenter)
        label3 = QLabel("이미지 : ")
        label3.setAlignment(Qt.AlignCenter)

        grid = QGridLayout()
        grid.setContentsMargins(59, 50, 50, 50)

        grid.addWidget(labelID, 0, 0)
        grid.addWidget(self.idLine, 0, 1)
        grid.addWidget(label0, 1, 0)
        grid.addWidget(self.doingButton, 1, 1)
        grid.addWidget(label1, 2, 0)
        grid.addWidget(self.contentLine, 2, 1)
        grid.addWidget(label2, 3, 0)
        grid.addWidget(self.statusLine, 3, 1)
        grid.addWidget(label3, 4, 0)
        grid.addWidget(self.imageLine, 4, 1)
        grid.addWidget(self.okButton, 5, 0, 1, 2)

        temp = QPushButton("temp")
        grid.addWidget(temp, 6, 0, 1, 2)
        temp.clicked.connect(self.temms)

        #grid.setColumnStretch(0, 2)
        #grid.setColumnStretch(1, 2)

        central.setLayout(grid)
        self.setCentralWidget(central)

        #윈도우 기본 셋
        self.setWindowTitle('Discord Custom GameActivity')
        self.setWindowIcon(QIcon('icon.png'))
        self.resize(400, 300)

        #self.setWindowFlags(Qt.FramelessWindowHint) #window frame hide
        #self.setAttribute(Qt.WA_TranslucentBackground) #remove border top side grabage
        self.center()
        self.show()
        #self.setStyleSheet("background-color: #333333;") #self -> style css type

        #이벤트
        self.doingButton.clicked.connect(self.onDoingButton)
        self.okButton.clicked.connect(self.onOkButton)
        #self.tray.activated(self.self.trayActiviy(self.tray))

    #화면 창을 가운데로 정렬
    def center(self):
        qr = self.frameGeometry()  #get 창의 위치, 크기 정보를
        cp = QDesktopWidget().availableGeometry().center(
        )  #get 현재 모니터 화면의 가운데 위치
        qr.moveCenter(cp)  #qr에 담긴 프로그램 창의 중심정보를 화면의 중심으로 이동
        self.move(qr.topLeft()
                  )  #현재 창을 qr의 위치로 실제로 이동시킴, 의미 : topLeft => 모니터의 좌상단을 기준으로

    #액션
    def onDoingButton(self):
        webbrowser.open("https://discord.com/developers/applications")
        self.p.show()

    def onOkButton(self):
        id = self.idLine.text()
        content = self.contentLine.text()
        status = self.statusLine.text()
        image = self.imageLine.text()
        print(id, content, status, image)

        if self.checkEmptyLine():
            print("Enter onOkButton event -> checkEmptyLine False Enter here")
            x = QMessageBox.question(self, '경고', '입력 항목을 다시 확인해주세요',
                                     QMessageBox.Yes | QMessageBox.No,
                                     QMessageBox.No)
        else:
            self.run_pypresence(id, content, status, image)

    def checkEmptyLine(self):
        check = True
        if self.idLine.text() == "" or self.contentLine.text(
        ) == "" or self.statusLine.text() == "" or self.imageLine.text() == "":
            check = True
        else:
            check = False
        return check

    def loadFile(self):
        loadContent = './config.json'
        if os.path.isfile(loadContent):
            with open("config.json", "r") as f:
                readfile = json.load(f)
                self.idLine.setText(readfile[0])
                self.contentLine.setText(readfile[1])
                self.statusLine.setText(readfile[2])
                self.imageLine.setText(readfile[3])
                print("file load success")
                print(readfile)
        else:
            print("file load fail")

    def saveFile(self):
        if self.checkEmptyLine():
            print("file save file. check line edit is empty line.")
        else:
            writeContent = [
                self.idLine.text(),
                self.contentLine.text(),
                self.statusLine.text(),
                self.imageLine.text()
            ]
            with open("config.json", "w") as json_file:
                json.dump(writeContent, json_file)
                print("file save success")
                print(writeContent)

    def temms(self):
        if self.tray.isVisible():
            self.tray.setVisible(False)
        elif not self.tray.isVisible():
            self.tray.setVisible(True)
        else:
            print("error")
    def initUI(self):
        #Get Foreground Window process name
        w = win32gui
        w.GetWindowText(w.GetForegroundWindow())
        pid = win32process.GetWindowThreadProcessId(w.GetForegroundWindow())
        print(psutil.Process(pid[-1]).name())
        #foreground 프로세스 변경 시 자동으로 사용자 상태가 현재 활성화 된 창을 표시할 수 있도록
        #SetWinEventHook 를 사용하여 foreground프로세스가 변경될 때 이벤트를 받을 수 있어야야함

        #메뉴바 액션
        exitAction = QAction('종료', self)
        exitAction.setShortcut('Ctrl+Q')
        exitAction.setStatusTip('프로그램 종료')
        exitAction.triggered.connect(qApp.quit)

        aboutAction = QAction('제작자', self)
        aboutAction.setStatusTip('제작자의 정보 : iro_bound')
        #exitAction.triggered.connect()

        #스테이터스 바
        #첫번째 호출 시 statusbar 생성, 이후 호출시 상태바 객체 반환
        #showMessage(str) 로 상태 메세지 변경
        self.statusBar().showMessage('DCGA 준비됨')

        #메뉴 바
        #--self.tray icon--
        self.tray = QSystemTrayIcon(QIcon('icon.png'), parent=self)
        self.tray.setToolTip("check out this app on self.tray icon")
        self.tray.setVisible(True)
        menubar = self.menuBar()
        menubar.setNativeMenuBar(False)  #Mac OS sync
        menu = QMenu('&도움말')
        filemenu = menu
        self.tray.setContextMenu(menu)
        filemenu.addAction(exitAction)
        filemenu.addAction(aboutAction)
        menubar.addMenu(menu)

        #센트럴 위젯
        central = QWidget()
        #central.setStyleSheet("background-color:#333333; border-style:solid; border-width: 1px; border-color: #555555; border-radius: 4px;")

        self.idLine = QLineEdit()
        self.idLine.setPlaceholderText("Client ID를 입력")
        self.contentLine = QLineEdit()
        self.contentLine.setPlaceholderText("원하는 내용을 입력")
        self.statusLine = QLineEdit()
        self.statusLine.setPlaceholderText("원하는 상태를 입력")
        self.imageLine = QLineEdit()
        self.imageLine.setPlaceholderText("이미지 이름")
        self.okButton = QPushButton("적용")
        self.okButton.setSizePolicy(QSizePolicy.Expanding,
                                    QSizePolicy.Expanding)
        self.doingButton = QPushButton("설정하기")

        labelID = QLabel("Client 설정 :")
        labelID.setAlignment(Qt.AlignCenter)
        label0 = QLabel("~하는 중 :")
        label0.setAlignment(Qt.AlignCenter)
        label1 = QLabel("내용 : ")
        label1.setAlignment(Qt.AlignCenter)
        label2 = QLabel("상태 : ")
        label2.setAlignment(Qt.AlignCenter)
        label3 = QLabel("이미지 : ")
        label3.setAlignment(Qt.AlignCenter)

        grid = QGridLayout()
        grid.setContentsMargins(59, 50, 50, 50)

        grid.addWidget(labelID, 0, 0)
        grid.addWidget(self.idLine, 0, 1)
        grid.addWidget(label0, 1, 0)
        grid.addWidget(self.doingButton, 1, 1)
        grid.addWidget(label1, 2, 0)
        grid.addWidget(self.contentLine, 2, 1)
        grid.addWidget(label2, 3, 0)
        grid.addWidget(self.statusLine, 3, 1)
        grid.addWidget(label3, 4, 0)
        grid.addWidget(self.imageLine, 4, 1)
        grid.addWidget(self.okButton, 5, 0, 1, 2)

        temp = QPushButton("temp")
        grid.addWidget(temp, 6, 0, 1, 2)
        temp.clicked.connect(self.temms)

        #grid.setColumnStretch(0, 2)
        #grid.setColumnStretch(1, 2)

        central.setLayout(grid)
        self.setCentralWidget(central)

        #윈도우 기본 셋
        self.setWindowTitle('Discord Custom GameActivity')
        self.setWindowIcon(QIcon('icon.png'))
        self.resize(400, 300)

        #self.setWindowFlags(Qt.FramelessWindowHint) #window frame hide
        #self.setAttribute(Qt.WA_TranslucentBackground) #remove border top side grabage
        self.center()
        self.show()
        #self.setStyleSheet("background-color: #333333;") #self -> style css type

        #이벤트
        self.doingButton.clicked.connect(self.onDoingButton)
        self.okButton.clicked.connect(self.onOkButton)
Beispiel #56
0
    def __init__(self, parent=None):
        QSystemTrayIcon.__init__(self, self.get_trayicon(), parent)
        self.activated.connect(self.on_activated)

        self.menu_is_visible = False
Beispiel #57
0
    def __init__(self):
        QMainWindow.__init__(self)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        self.ui.actionAdd_camera.triggered.connect(self.handle_add_camera_triggered)
        self.ui.actionOpen_dataset.triggered.connect(self.handle_open_dataset_triggered)
        self.ui.actionSave_dataset.triggered.connect(self.handle_save_dataset_triggered)
        self.ui.actionClose_dataset.triggered.connect(self.handle_close_dataset_triggered)
        self.ui.actionExport_to_JSON.triggered.connect(self.handle_export_to_json_triggered)

        self.startup_page = QWidget()
        self.startup_page_ui = Ui_StartupPage()
        self.startup_page_ui.setupUi(self.startup_page)

        self.startup_page_ui.addCamera_button.setDefaultAction(self.ui.actionAdd_camera)

        self.startup_page_ui.openDataset_button.setDefaultAction(self.ui.actionOpen_dataset)
        self.startup_page_ui.openDataset_button.setMenu(None)
        self.startup_page_ui.openDataset_button.setArrowType(Qt.NoArrow)

        self.startup_page_ui.label_recent_datasets_image_placeholder.setPixmap(
            QPixmap(':/icons/document-open-recent.svg'))
        self.startup_page_ui.label_recent_cameras_image_placeholder.setPixmap(
            QPixmap(':/icons/document-open-recent.svg'))

        self.startup_dataset_buttons: List[QToolButton] = []
        self.startup_camera_buttons: List[QToolButton] = []

        self.state = self.get_config_state()

        self.recent_datasets: List[Path] = []
        self.recent_datasets_menu = QMenu()
        self.recent_datasets_menu.setToolTipsVisible(True)
        self.recent_dataset_qactions = []

        for ds in self.state['recent_datasets']:
            self.add_dataset_entry_to_recent(Path(ds))

        if len(self.recent_datasets) == 0:
            self.startup_page_ui.verticalLayout_recentDatasets.addWidget(self.startup_page_ui.label_noRecentDatasets)
            self.startup_page_ui.label_noRecentDatasets.setAlignment(Qt.AlignHCenter)
            self.startup_page_ui.label_noRecentDatasets.show()

        self.recent_datasets_menu.triggered.connect(lambda action: self.open_dataset(Path(action.toolTip())))

        self.ui.actionOpen_dataset.setMenu(self.recent_datasets_menu)

        self.recent_cameras: List[Path] = []
        self.recent_cameras_menu = QMenu()
        self.recent_cameras_menu.setToolTipsVisible(True)
        self.recent_cameras_qactions: Dict[str, QAction] = {}


        for cam in self.state['recent_cameras']:
            self.add_camera_entry_to_recent(Path(cam))

        if len(self.recent_cameras) == 0:
            self.startup_page_ui.verticalLayout_recentCameras.addWidget(self.startup_page_ui.label_noRecentCameras)
            self.startup_page_ui.label_noRecentCameras.setAlignment(Qt.AlignHCenter)
            self.startup_page_ui.label_noRecentCameras.show()

        self.recent_cameras_menu.triggered.connect(self.handle_add_recent_camera_triggered)
        self.ui.actionAdd_camera.setMenu(self.recent_cameras_menu)

        self.dataset = Dataset()
        self.processing_widget = CameraProcessingWidget()
        self.processing_widget.set_dataset(self.dataset)
        self.processing_widget.camera_loaded.connect(self.handle_camera_added)
        self.processing_widget.processing_started.connect(self.show_progress_bar)
        self.processing_widget.processing_updated.connect(self.update_progress_bar)
        self.processing_widget.processing_stopped.connect(self.hide_progress_bar)
        self.processing_widget.stick_verification_needed.connect(self.notify_user)
        self.processing_widget.no_cameras_open.connect(self.handle_no_cameras_open)

        self.ui.stackedWidget.removeWidget(self.ui.page)
        self.ui.stackedWidget.removeWidget(self.ui.page_2)

        self.ui.stackedWidget.addWidget(self.startup_page)
        self.ui.stackedWidget.addWidget(self.processing_widget)
        self.ui.stackedWidget.setCurrentIndex(0)
        self.setCentralWidget(self.ui.stackedWidget)
        self.ui.toolBar.hide()

        self.progress_bar = QProgressBar()
        self.progress_bar.hide()

        skipped_q_indicator = QPixmap(24, 24)
        skipped_q_indicator.fill(QColor(150, 150, 150))
        skipped_label = QLabel()
        skipped_label.setPixmap(skipped_q_indicator)

        bad_q_indicator = QPixmap(24, 24)
        bad_q_indicator.fill(QColor(200, 0, 0))
        bad_label = QLabel()
        bad_label.setPixmap(bad_q_indicator)
        ok_q_indicator = QPixmap(24, 24)
        ok_q_indicator.fill(QColor(200, 100, 0))
        ok_label = QLabel()
        ok_label.setPixmap(ok_q_indicator)
        good_q_indicator = QPixmap(24, 24)
        good_q_indicator.fill(QColor(100, 200, 0))
        good_label = QLabel()
        good_label.setPixmap(good_q_indicator)

        status_box = QHBoxLayout()
        indicator_box = QHBoxLayout()

        self.statusBar().hide()
        indicator_box.addWidget(QLabel("Stick visibility:\t"))
        indicator_box.addWidget(skipped_label)
        indicator_box.addWidget(QLabel(" Skipped  "))
        indicator_box.addWidget(bad_label)
        indicator_box.addWidget(QLabel(" Bad  "))
        indicator_box.addWidget(ok_label)
        indicator_box.addWidget(QLabel(" OK  "))
        indicator_box.addWidget(good_label)
        indicator_box.addWidget(QLabel(" Good  "))
        status_box.addItem(indicator_box)
        status_box.addWidget(self.progress_bar)
        status_box.setAlignment(indicator_box, Qt.AlignLeft)
        status_box.setSpacing(100)
        status_widget = QWidget()
        status_widget.setLayout(status_box)
        self.statusBar().addPermanentWidget(status_widget, 1)

        self.progress_bar.setFormat("%v / %m")
        self.sys_tray = QSystemTrayIcon(QIcon(':icons/snowflake.svg'))
        self.sys_tray.show()

        self.thread_pool = QThreadPool()
Beispiel #58
0
class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        settings = QSettings()
        self.setup_trayicon()
        self.setup_ui()
        self.update_work_end_time()
        self.update_rest_end_time()
        self.setup_connections()
        self.timeFormat = "hh:mm:ss"
        self.time = QTime(0, 0, 0, 0)
        self.workTime = QTime(0, 0, 0, 0)
        self.restTime = QTime(0, 0, 0, 0)
        self.totalTime = QTime(0, 0, 0, 0)
        self.currentMode = Mode.work
        self.maxRepetitions = -1
        self.currentRepetitions = 0
        self.show()

    def leaveEvent(self, event):
        super(MainWindow, self).leaveEvent(event)
        self.tasksTableWidget.clearSelection()

    def closeEvent(self, event):
        super(MainWindow, self).closeEvent(event)
        settings = QSettings()
        settings.setValue("timer/work/hours", self.workHoursSpinBox.value())
        settings.setValue("timer/work/minutes",
                          self.workMinutesSpinBox.value())
        settings.setValue("timer/work/seconds",
                          self.workSecondsSpinBox.value())
        settings.setValue("timer/rest/hours", self.restHoursSpinBox.value())
        settings.setValue("timer/rest/minutes",
                          self.restMinutesSpinBox.value())
        settings.setValue("timer/rest/seconds",
                          self.restSecondsSpinBox.value())

        tasks = []
        for i in range(self.tasksTableWidget.rowCount()):
            item = self.tasksTableWidget.item(i, 0)
            if not item.font().strikeOut():
                tasks.append(item.text())
        settings.setValue("tasks/tasks", tasks)

    def start_timer(self):
        try:
            if not self.timer.isActive():
                self.create_timer()
        except:
            self.create_timer()

    def create_timer(self):
        self.timer = QTimer()
        self.timer.timeout.connect(self.update_time)
        self.timer.timeout.connect(self.maybe_change_mode)
        self.timer.setInterval(1000)
        self.timer.setSingleShot(False)
        self.timer.start()

    def pause_timer(self):
        try:
            self.timer.stop()
            self.timer.disconnect()
        except:
            pass

    def reset_timer(self):
        try:
            self.pause_timer()
            self.time = QTime(0, 0, 0, 0)
            self.display_time()
        except:
            pass

    def maybe_start_timer(self):
        if self.currentRepetitions != self.maxRepetitions:
            self.start_timer()
            started = True
        else:
            self.currentRepetitions = 0
            started = False
        return started

    def update_work_end_time(self):
        self.workEndTime = QTime(self.workHoursSpinBox.value(),
                                 self.workMinutesSpinBox.value(),
                                 self.workSecondsSpinBox.value())

    def update_rest_end_time(self):
        self.restEndTime = QTime(self.restHoursSpinBox.value(),
                                 self.restMinutesSpinBox.value(),
                                 self.restSecondsSpinBox.value())

    def update_current_mode(self, mode: str):
        self.currentMode = Mode.work if mode == "work" else Mode.rest

    def update_time(self):
        self.time = self.time.addSecs(1)
        self.totalTime = self.totalTime.addSecs(1)
        if self.modeComboBox.currentText() == "work":
            self.workTime = self.workTime.addSecs(1)
        else:
            self.restTime = self.restTime.addSecs(1)
        self.display_time()

    def update_max_repetitions(self, value):
        if value == 0:
            self.currentRepetitions = 0
            self.maxRepetitions = -1
        else:
            self.maxRepetitions = 2 * value

    def maybe_change_mode(self):
        if self.currentMode is Mode.work and self.time >= self.workEndTime:
            self.reset_timer()
            self.modeComboBox.setCurrentIndex(1)
            self.increment_current_repetitions()
            started = self.maybe_start_timer()
            self.show_window_message(
                Status.workFinished if started else Status.repetitionsReached)
        elif self.currentMode is Mode.rest and self.time >= self.restEndTime:
            self.reset_timer()
            self.modeComboBox.setCurrentIndex(0)
            self.increment_current_repetitions()
            started = self.maybe_start_timer()
            self.show_window_message(
                Status.restFinished if started else Status.repetitionsReached)

    def increment_current_repetitions(self):
        if self.maxRepetitions > 0:
            self.currentRepetitions += 1

    def insert_task(self):
        task = self.taskTextEdit.toPlainText()
        self.insert_tasks(task)

    def insert_tasks(self, *tasks):
        for task in tasks:
            if task:
                rowCount = self.tasksTableWidget.rowCount()
                self.tasksTableWidget.setRowCount(rowCount + 1)
                self.tasksTableWidget.setItem(rowCount, 0,
                                              QTableWidgetItem(task))
                self.tasksTableWidget.resizeRowsToContents()
                self.taskTextEdit.clear()

    def delete_task(self):
        selectedIndexes = self.tasksTableWidget.selectedIndexes()
        if selectedIndexes:
            self.tasksTableWidget.removeRow(selectedIndexes[0].row())

    def mark_task_as_finished(self, row, col):
        item = self.tasksTableWidget.item(row, col)
        font = self.tasksTableWidget.item(row, col).font()
        font.setStrikeOut(False if item.font().strikeOut() else True)
        item.setFont(font)

    def display_time(self):
        self.timeDisplay.display(self.time.toString(self.timeFormat))
        self.statisticsRestTimeDisplay.display(
            self.restTime.toString(self.timeFormat))
        self.statisticsWorkTimeDisplay.display(
            self.workTime.toString(self.timeFormat))
        self.statisticsTotalTimeDisplay.display(
            self.totalTime.toString(self.timeFormat))

    def show_window_message(self, status):
        if status is Status.workFinished:
            self.trayIcon.showMessage("Break", choice(work_finished_phrases),
                                      QIcon("icons/tomato.png"))
        elif status is Status.restFinished:
            self.trayIcon.showMessage("Work", choice(rest_finished_phrases),
                                      QIcon("icons/tomato.png"))
        else:
            self.trayIcon.showMessage("Finished",
                                      choice(pomodoro_finished_phrases),
                                      QIcon("icons/tomato.png"))
            self.resetButton.click()

    def setup_connections(self):
        self.playButton.clicked.connect(self.start_timer)
        self.playButton.clicked.connect(
            lambda: self.playButton.setDisabled(True))
        self.playButton.clicked.connect(
            lambda: self.pauseButton.setDisabled(False))
        self.playButton.clicked.connect(
            lambda: self.resetButton.setDisabled(False))

        self.pauseButton.clicked.connect(self.pause_timer)
        self.pauseButton.clicked.connect(
            lambda: self.playButton.setDisabled(False))
        self.pauseButton.clicked.connect(
            lambda: self.pauseButton.setDisabled(True))
        self.pauseButton.clicked.connect(
            lambda: self.resetButton.setDisabled(False))

        self.resetButton.clicked.connect(self.reset_timer)
        self.resetButton.clicked.connect(
            lambda: self.playButton.setDisabled(False))
        self.resetButton.clicked.connect(
            lambda: self.pauseButton.setDisabled(True))
        self.resetButton.clicked.connect(
            lambda: self.resetButton.setDisabled(True))

        self.workHoursSpinBox.valueChanged.connect(self.update_work_end_time)
        self.workMinutesSpinBox.valueChanged.connect(self.update_work_end_time)
        self.workSecondsSpinBox.valueChanged.connect(self.update_work_end_time)

        self.restHoursSpinBox.valueChanged.connect(self.update_rest_end_time)
        self.restMinutesSpinBox.valueChanged.connect(self.update_rest_end_time)
        self.restSecondsSpinBox.valueChanged.connect(self.update_rest_end_time)

        self.modeComboBox.currentTextChanged.connect(self.update_current_mode)
        self.repetitionsSpinBox.valueChanged.connect(
            self.update_max_repetitions)

        self.acceptTaskButton.pressed.connect(self.insert_task)
        self.deleteTaskButton.pressed.connect(self.delete_task)

        self.tasksTableWidget.cellDoubleClicked.connect(
            self.mark_task_as_finished)

    def setup_ui(self):
        settings = QSettings()

        self.size_policy = sizePolicy = QSizePolicy(QSizePolicy.Expanding,
                                                    QSizePolicy.Expanding)
        #TABWIDGET
        self.tabWidget = QTabWidget()

        self.pomodoroWidget = QWidget(self)

        self.pomodoroWidgetLayout = QVBoxLayout(self.pomodoroWidget)
        self.pomodoroWidget.setLayout(self.pomodoroWidgetLayout)
        # work
        self.workGroupBox = QGroupBox("Work")
        self.workGroupBoxLayout = QHBoxLayout(self.workGroupBox)
        self.workGroupBox.setLayout(self.workGroupBoxLayout)
        self.workHoursSpinBox = QSpinBox(minimum=0,
                                         maximum=24,
                                         value=settings.value(
                                             "timer/work/hours", 0),
                                         suffix="h",
                                         sizePolicy=self.size_policy)
        self.workMinutesSpinBox = QSpinBox(minimum=0,
                                           maximum=60,
                                           value=settings.value(
                                               "timer/work/minutes", 25),
                                           suffix="m",
                                           sizePolicy=self.size_policy)
        self.workSecondsSpinBox = QSpinBox(minimum=0,
                                           maximum=60,
                                           value=settings.value(
                                               "timer/work/seconds", 0),
                                           suffix="s",
                                           sizePolicy=self.size_policy)
        self.workGroupBoxLayout.addWidget(self.workHoursSpinBox)
        self.workGroupBoxLayout.addWidget(self.workMinutesSpinBox)
        self.workGroupBoxLayout.addWidget(self.workSecondsSpinBox)
        # rest
        self.restGroupBox = QGroupBox("Rest")
        self.restGroupBoxLayout = QHBoxLayout(self.restGroupBox)
        self.restGroupBox.setLayout(self.restGroupBoxLayout)
        self.restHoursSpinBox = QSpinBox(minimum=0,
                                         maximum=24,
                                         value=settings.value(
                                             "timer/rest/hours", 0),
                                         suffix="h",
                                         sizePolicy=self.size_policy)
        self.restMinutesSpinBox = QSpinBox(minimum=0,
                                           maximum=60,
                                           value=settings.value(
                                               "timer/rest/minutes", 5),
                                           suffix="m",
                                           sizePolicy=self.size_policy)
        self.restSecondsSpinBox = QSpinBox(minimum=0,
                                           maximum=60,
                                           value=settings.value(
                                               "timer/rest/seconds", 0),
                                           suffix="s",
                                           sizePolicy=self.size_policy)
        self.restGroupBoxLayout.addWidget(self.restHoursSpinBox)
        self.restGroupBoxLayout.addWidget(self.restMinutesSpinBox)
        self.restGroupBoxLayout.addWidget(self.restSecondsSpinBox)
        #OTHER
        self.otherGroupBox = QGroupBox("Other")
        self.otherGroupBoxLayout = QHBoxLayout(self.otherGroupBox)
        self.otherGroupBox.setLayout(self.otherGroupBoxLayout)
        self.repetitionsLabel = QLabel("Repetitions",
                                       sizePolicy=self.size_policy)
        self.repetitionsSpinBox = QSpinBox(minimum=0,
                                           maximum=10000,
                                           value=0,
                                           sizePolicy=self.size_policy,
                                           specialValueText="∞")
        self.modeLabel = QLabel("Mode", sizePolicy=self.size_policy)
        self.modeComboBox = QComboBox()
        self.modeComboBox.addItems(["work", "rest"])
        self.otherGroupBoxLayout.addWidget(self.repetitionsLabel)
        self.otherGroupBoxLayout.addWidget(self.repetitionsSpinBox)
        self.otherGroupBoxLayout.addWidget(self.modeLabel)
        self.otherGroupBoxLayout.addWidget(self.modeComboBox)
        #LCDDISPLAY
        self.lcdDisplayGroupBox = QGroupBox("Time")
        self.lcdDisplayGroupBoxLayout = QHBoxLayout(self.lcdDisplayGroupBox)
        self.lcdDisplayGroupBox.setLayout(self.lcdDisplayGroupBoxLayout)
        self.timeDisplay = QLCDNumber(8, sizePolicy=self.size_policy)
        self.timeDisplay.setFixedHeight(100)
        self.timeDisplay.display("00:00:00")
        self.lcdDisplayGroupBoxLayout.addWidget(self.timeDisplay)

        #BUTTONS
        self.buttonWidget = QWidget()
        self.buttonWidgetLayout = QHBoxLayout(self.buttonWidget)
        self.buttonWidget.setLayout(self.buttonWidgetLayout)
        self.playButton = self.make_button("start", disabled=False)
        self.resetButton = self.make_button("reset")
        self.pauseButton = self.make_button("pause")
        self.buttonWidgetLayout.addWidget(self.pauseButton)
        self.buttonWidgetLayout.addWidget(self.playButton)
        self.buttonWidgetLayout.addWidget(self.resetButton)

        #CENTRALWIDGET
        self.pomodoroWidgetLayout.addWidget(self.workGroupBox)
        self.pomodoroWidgetLayout.addWidget(self.restGroupBox)
        self.pomodoroWidgetLayout.addWidget(self.otherGroupBox)
        self.pomodoroWidgetLayout.addWidget(self.lcdDisplayGroupBox)
        self.pomodoroWidgetLayout.addWidget(self.buttonWidget)
        #CREATE TASKS TAB
        self.tasksWidget = QWidget(self.tabWidget)
        self.tasksWidgetLayout = QVBoxLayout(self.tasksWidget)
        self.tasksWidget.setLayout(self.tasksWidgetLayout)
        self.inputWidget = QWidget()
        self.inputWidget.setFixedHeight(50)
        self.inputWidgetLayout = QHBoxLayout(self.inputWidget)
        self.inputWidgetLayout.setContentsMargins(0, 0, 0, 0)
        self.inputWidget.setLayout(self.inputWidgetLayout)
        self.taskTextEdit = QTextEdit(
            placeholderText="Describe your task briefly.",
            undoRedoEnabled=True)
        self.inputButtonContainer = QWidget()
        self.inputButtonContainerLayout = QVBoxLayout(
            self.inputButtonContainer)
        self.inputButtonContainerLayout.setContentsMargins(0, 0, 0, 0)
        self.inputButtonContainer.setLayout(self.inputButtonContainerLayout)
        self.acceptTaskButton = QToolButton(icon=QIcon("icons/check.png"))
        self.deleteTaskButton = QToolButton(icon=QIcon("icons/trash.png"))
        self.inputButtonContainerLayout.addWidget(self.acceptTaskButton)
        self.inputButtonContainerLayout.addWidget(self.deleteTaskButton)

        self.inputWidgetLayout.addWidget(self.taskTextEdit)
        self.inputWidgetLayout.addWidget(self.inputButtonContainer)
        self.tasksTableWidget = QTableWidget(0, 1)
        self.tasksTableWidget.setHorizontalHeaderLabels(["Tasks"])
        self.tasksTableWidget.horizontalHeader().setStretchLastSection(True)
        self.tasksTableWidget.verticalHeader().setVisible(False)
        self.tasksTableWidget.setWordWrap(True)
        self.tasksTableWidget.setTextElideMode(Qt.ElideNone)
        self.tasksTableWidget.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.tasksTableWidget.setSelectionMode(
            QAbstractItemView.SingleSelection)
        self.insert_tasks(*settings.value("tasks/tasks", []))

        self.tasksWidgetLayout.addWidget(self.inputWidget)
        self.tasksWidgetLayout.addWidget(self.tasksTableWidget)
        #CREATE STATISTICS TAB
        self.statisticsWidget = QWidget()
        self.statisticsWidgetLayout = QVBoxLayout(self.statisticsWidget)
        self.statisticsWidget.setLayout(self.statisticsWidgetLayout)

        self.statisticsWorkTimeGroupBox = QGroupBox("Work Time")
        self.statisticsWorkTimeGroupBoxLayout = QHBoxLayout()
        self.statisticsWorkTimeGroupBox.setLayout(
            self.statisticsWorkTimeGroupBoxLayout)
        self.statisticsWorkTimeDisplay = QLCDNumber(8)
        self.statisticsWorkTimeDisplay.display("00:00:00")
        self.statisticsWorkTimeGroupBoxLayout.addWidget(
            self.statisticsWorkTimeDisplay)

        self.statisticsRestTimeGroupBox = QGroupBox("Rest Time")
        self.statisticsRestTimeGroupBoxLayout = QHBoxLayout()
        self.statisticsRestTimeGroupBox.setLayout(
            self.statisticsRestTimeGroupBoxLayout)
        self.statisticsRestTimeDisplay = QLCDNumber(8)
        self.statisticsRestTimeDisplay.display("00:00:00")
        self.statisticsRestTimeGroupBoxLayout.addWidget(
            self.statisticsRestTimeDisplay)

        self.statisticsTotalTimeGroupBox = QGroupBox("Total Time")
        self.statisticsTotalTimeGroupBoxLayout = QHBoxLayout()
        self.statisticsTotalTimeGroupBox.setLayout(
            self.statisticsTotalTimeGroupBoxLayout)
        self.statisticsTotalTimeDisplay = QLCDNumber(8)
        self.statisticsTotalTimeDisplay.display("00:00:00")
        self.statisticsTotalTimeGroupBoxLayout.addWidget(
            self.statisticsTotalTimeDisplay)

        self.statisticsWidgetLayout.addWidget(self.statisticsTotalTimeGroupBox)
        self.statisticsWidgetLayout.addWidget(self.statisticsWorkTimeGroupBox)
        self.statisticsWidgetLayout.addWidget(self.statisticsRestTimeGroupBox)

        #ADD TABS
        self.timerTab = self.tabWidget.addTab(self.pomodoroWidget,
                                              QIcon("icons/timer.png"),
                                              "Timer")
        self.tasksTab = self.tabWidget.addTab(self.tasksWidget,
                                              QIcon("icons/tasks.png"),
                                              "Tasks")
        self.statisticsTab = self.tabWidget.addTab(
            self.statisticsWidget, QIcon("icons/statistics.png"), "Statistics")

        self.setCentralWidget(self.tabWidget)

    def make_button(self, text, iconPath=None, disabled=True):
        button = QPushButton(text, sizePolicy=self.size_policy)
        if iconPath:
            button.setIcon(QIcon(iconPath))
        button.setDisabled(disabled)
        return button

    def setup_trayicon(self):
        self.trayIcon = QSystemTrayIcon(QIcon("icons/tomato.png"))
        self.trayIcon.setContextMenu(QMenu())
        self.quitAction = self.trayIcon.contextMenu().addAction(
            QIcon("icons/exit.png"), "Quit", self.exit)
        self.quitAction.triggered.connect(self.exit)
        self.trayIcon.activated.connect(self.onActivate)
        self.trayIcon.show()

    def exit(self):
        self.close()
        app = QApplication.instance()
        if app:
            app.quit()

    def onActivate(self, reason):
        if reason == QSystemTrayIcon.Trigger:
            self.show()
Beispiel #59
0
#!/usr/bin/python
# coding: UTF-8
import sys
from PyQt5.QtGui import QIcon

from PyQt5.QtWidgets import QApplication, QSystemTrayIcon

from cracker.cracker import Cracker

if __name__ == "__main__":
    app = QApplication(sys.argv)
    app.setApplicationName('Cracker')

    cracker = Cracker(app)
    cracker.run()

    icon = QIcon('icon.png')
    tray = QSystemTrayIcon(icon)
    tray.setIcon(icon)
    tray.setVisible(True)

    sys.exit(app.exec_())
Beispiel #60
0
class MyWindow(QtWidgets.QWidget, Ui_MainWindow):
    def __init__(self):
        super(MyWindow, self).__init__()
        self.setupUi(self)
        self.setWindowTitle('天气小工具')  # 界面标题
        self.setWindowOpacity(0.9)  # 设置窗口透明度
        self.setAttribute(QtCore.Qt.WA_TranslucentBackground)  # 设置窗口背景透明
        self.setWindowFlag(QtCore.Qt.FramelessWindowHint)  # 隐藏边框
        ''' 给无边框移动用的三个变量 '''
        self._startPos = None
        self._endPos = None
        self._isTracking = False
        self.init_info()  # 读取csv文件,初始化城市数据
        self.cityComboBox.addItems(["{0}".format(x)
                                    for x in config.city_list])  # 初始化界面下拉框
        # self.pushButton_refresh.clicked.connect(lambda: self.start_create("customer", 1))  # 可以传多个参数入线程
        self.pushButton_refresh.clicked.connect(
            lambda: self.start_create())  # 界面的按钮事件需要提前注册
        self.closeButton.clicked.connect(self.close)  # 界面退出程序
        self.miniButton.clicked.connect(self.init_tuopan)  # 界面最小化
        # self.miniButton.clicked.connect(self.showMinimized)  # 界面最小化
        self.trayIcon = QSystemTrayIcon(self)  # 托盘
        self.trayIcon.setIcon(QIcon(config.path_icon))  # 托盘图标
        self.time_thread = Runthread_t(self)
        self.time_thread.nonglisignal.connect(self.displaynongli)  # 刷新界面农历
        self.time_thread.timesignal.connect(self.displaytime)  # 刷新界面公历
        self.time_thread.start()

    def init_tuopan(self):
        self.hide()
        restoreAction = QAction("&显示主窗口", self, triggered=self.showNormal)
        quitAction = QAction("&退出", self, triggered=self.close)
        self.trayIconMenu = QMenu(self)
        self.trayIconMenu.addAction(restoreAction)
        # self.trayIconMenu.addSeparator()   # 分割线
        self.trayIconMenu.addAction(quitAction)

        self.setWindowIcon(QIcon(config.path_icon))
        self.trayIcon.setContextMenu(self.trayIconMenu)
        self.trayIcon.show()

    def init_info(self):
        File = open(config.path_csv, 'r')
        cityReader = csv.reader(File)
        cityData = list(cityReader)
        for i in cityData:
            if i[0] != '城市':
                config.city_Info[i[0]] = i[1]
                config.city_list.append(i[0])
        print(config.city_list, config.city_Info)
        File.close()

    def mouseMoveEvent(self, e: QMouseEvent):  # 重写移动事件
        self._endPos = e.pos() - self._startPos
        self.move(self.pos() + self._endPos)

    def mousePressEvent(self, e: QMouseEvent):  # 重写鼠标事件
        if e.button() == Qt.LeftButton:
            self._isTracking = True
            self._startPos = QPoint(e.x(), e.y())

    def mouseReleaseEvent(self, e: QMouseEvent):  # 重写鼠标事件
        if e.button() == Qt.LeftButton:
            self._isTracking = False
            self._startPos = None
            self._endPos = None

    def displaytime(self, s):  # 界面刷新当前时间
        self.timeLabel.setText(s)

    def displaynongli(self, s):  # 界面刷新农历
        self.yinliLabel.setText(s)

    # 可以建多个线程
    # def start_create(self, type, Nums):               # 可以传多个参数入线程
    def start_create(self):
        self.pushButton_refresh.setDisabled(True)
        # self.thread = Runthread(self, type, Nums)     # 可以传多个参数入线程
        self.thread = Runthread(self)
        self.thread.cmysignal.connect(self.add_info)  # 通过槽输出信号到前台界面
        self.thread.weasignal.connect(self.refreshweather)  # 刷新界面表格
        self.thread.start()

    def refreshweather(self, weather):
        for i in range(2):
            for j in range(5):
                self.weaTable.setItem(j + 1, i + 1,
                                      QTableWidgetItem(weather[i][j]))

    def add_info(self, message):  # 界面打印信息
        if message == "结束!":
            self.pushButton_refresh.setDisabled(False)