Beispiel #1
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 #2
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
Beispiel #3
0
class SystemTray(QWidget):

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

    def add_action(self, name, triggered_action):
        action = QAction(QCoreApplication.translate(trad_context, name), self, triggered = triggered_action)
        self.tray_icon_menu.addAction(action)

    def add_separator(self):
        self.tray_icon_menu.addSeparator()

    def show(self):
        super(SystemTray, self).show()
        self.tray_icon.show()

    @pyqtSlot()
    def event_started(self):
        self.icon_management.start()

    @pyqtSlot()
    def event_finished(self):
        self.icon_management.stop()

    @pyqtSlot(Exception)
    def conductor_problem(self, e):
        self.notify("Demerio", "There was a problem : %s" % (e,))
        self.icon_management.conductor_problem()

    @pyqtSlot(bool)
    def internet_connection(self, internet_is_ok):
        if not internet_is_ok:
            self.notify("Demerio", "Internet connection is lost")
        self.icon_management.internet_is_ok(internet_is_ok)

    def notify(self, title, message):
        self.tray_icon.showMessage(title, message, BAR_NOTIFICATION_TIME)
Beispiel #4
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))
Beispiel #5
0
class SVApplication(QApplication):

    # Signals need to be on a QObject
    create_new_window_signal = pyqtSignal(str, object)
    cosigner_received_signal = pyqtSignal(object, object)
    labels_changed_signal = pyqtSignal(object, object, object)
    window_opened_signal = pyqtSignal(object)
    window_closed_signal = pyqtSignal(object)
    # Async tasks
    async_tasks_done = pyqtSignal()
    # Logging
    new_category = pyqtSignal(str)
    new_log = pyqtSignal(object)
    # Preferences updates
    fiat_ccy_changed = pyqtSignal()
    custom_fee_changed = pyqtSignal()
    op_return_enabled_changed = pyqtSignal()
    num_zeros_changed = pyqtSignal()
    base_unit_changed = pyqtSignal()
    fiat_history_changed = pyqtSignal()
    fiat_balance_changed = pyqtSignal()
    update_check_signal = pyqtSignal(bool, object)
    # Contact events
    contact_added_signal = pyqtSignal(object, object)
    contact_removed_signal = pyqtSignal(object)
    identity_added_signal = pyqtSignal(object, object)
    identity_removed_signal = pyqtSignal(object, object)

    def __init__(self, argv):
        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-sv.desktop')
        super().__init__(argv)
        self.windows = []
        self.log_handler = SVLogHandler()
        self.log_window = None
        self.net_dialog = None
        self.timer = QTimer()
        self.exception_hook = None
        # A floating point number, e.g. 129.1
        self.dpi = self.primaryScreen().physicalDotsPerInch()

        # init tray
        self.dark_icon = app_state.config.get("dark_icon", False)
        self.tray = QSystemTrayIcon(self._tray_icon(), None)
        self.tray.setToolTip('ElectrumSV')
        self.tray.activated.connect(self._tray_activated)
        self._build_tray_menu()
        self.tray.show()

        # FIXME Fix what.. what needs to be fixed here?
        set_language(app_state.config.get('language', get_default_language()))

        logs.add_handler(self.log_handler)
        self._start()

    def _start(self):
        self.setWindowIcon(read_QIcon("electrum-sv.png"))
        self.installEventFilter(OpenFileEventFilter(self.windows))
        self.create_new_window_signal.connect(self.start_new_window)
        self.async_tasks_done.connect(app_state.async_.run_pending_callbacks)
        self.num_zeros_changed.connect(
            partial(self._signal_all, 'on_num_zeros_changed'))
        self.fiat_ccy_changed.connect(
            partial(self._signal_all, 'on_fiat_ccy_changed'))
        self.base_unit_changed.connect(
            partial(self._signal_all, 'on_base_unit_changed'))
        self.fiat_history_changed.connect(
            partial(self._signal_all, 'on_fiat_history_changed'))
        # Toggling of showing addresses in the fiat preferences.
        self.fiat_balance_changed.connect(
            partial(self._signal_all, 'on_fiat_balance_changed'))
        self.update_check_signal.connect(
            partial(self._signal_all, 'on_update_check'))
        ColorScheme.update_from_widget(QWidget())

    def _signal_all(self, method, *args):
        for window in self.windows:
            getattr(window, method)(*args)

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

    def close_window(self, window):
        app_state.daemon.stop_wallet_at_path(window._wallet.get_storage_path())
        self.windows.remove(window)
        self.window_closed_signal.emit(window)
        self._build_tray_menu()
        if not self.windows:
            self._last_window_closed()

    def setup_app(self):
        # app_state.daemon is initialised after app. Setup things dependent on daemon here.
        pass

    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.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 ElectrumSV"), self._close)
        self.tray.setContextMenu(m)

    def _tray_icon(self):
        if self.dark_icon:
            return read_QIcon('electrumsv_dark_icon.png')
        else:
            return read_QIcon('electrumsv_light_icon.png')

    def _toggle_tray_icon(self):
        self.dark_icon = not self.dark_icon
        app_state.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 new_window(self,
                   path: Optional[str],
                   uri: Optional[str] = None) -> None:
        # Use a signal as can be called from daemon thread
        self.create_new_window_signal.emit(path, uri)

    def show_network_dialog(self, parent) -> None:
        if not app_state.daemon.network:
            parent.show_warning(_(
                'You are using ElectrumSV in offline mode; restart '
                'ElectrumSV if you want to get connected'),
                                title=_('Offline'))
            return
        if self.net_dialog:
            self.net_dialog.on_update()
            self.net_dialog.show()
            self.net_dialog.raise_()
            return
        from . import network_dialog
        from importlib import reload
        reload(network_dialog)
        self.net_dialog = network_dialog.NetworkDialog(
            app_state.daemon.network, app_state.config)
        self.net_dialog.show()

    def show_log_viewer(self) -> None:
        if self.log_window is None:
            self.log_window = SVLogWindow(None, self.log_handler)
        self.log_window.show()

    def _last_window_closed(self):
        for dialog in (self.net_dialog, self.log_window):
            if dialog:
                dialog.accept()

    def on_transaction_label_change(self, account: AbstractAccount,
                                    tx_hash: bytes, text: str) -> None:
        self.label_sync.set_transaction_label(account, tx_hash, text)

    def on_keyinstance_label_change(self, account: AbstractAccount,
                                    key_id: int, text: str) -> None:
        self.label_sync.set_keyinstance_label(account, key_id, text)

    def _create_window_for_wallet(self, wallet: Wallet):
        w = ElectrumWindow(wallet)
        self.windows.append(w)
        self._build_tray_menu()
        self._register_wallet_events(wallet)
        self.window_opened_signal.emit(w)
        return w

    def _register_wallet_events(self, wallet: Wallet) -> None:
        wallet.contacts._on_contact_added = self._on_contact_added
        wallet.contacts._on_contact_removed = self._on_contact_removed
        wallet.contacts._on_identity_added = self._on_identity_added
        wallet.contacts._on_identity_removed = self._on_identity_removed

    def _on_identity_added(self, contact: ContactEntry,
                           identity: ContactIdentity) -> None:
        self.identity_added_signal.emit(contact, identity)

    def _on_identity_removed(self, contact: ContactEntry,
                             identity: ContactIdentity) -> None:
        self.identity_removed_signal.emit(contact, identity)

    def _on_contact_added(self, contact: ContactEntry,
                          identity: ContactIdentity) -> None:
        self.contact_added_signal.emit(contact, identity)

    def _on_contact_removed(self, contact: ContactEntry) -> None:
        self.contact_removed_signal.emit(contact)

    def get_wallet_window(self, path: str) -> Optional[ElectrumWindow]:
        for w in self.windows:
            if w._wallet.get_storage_path() == path:
                return w

    def get_wallet_window_by_id(self,
                                wallet_id: int) -> Optional[ElectrumWindow]:
        for w in self.windows:
            for child_wallet in w._wallet.get_accounts():
                if child_wallet.get_id() == wallet_id:
                    return w

    def start_new_window(self,
                         wallet_path: Optional[str],
                         uri: Optional[str] = None,
                         is_startup: bool = False) -> Optional[ElectrumWindow]:
        '''Raises the window for the wallet if it is open.  Otherwise
        opens the wallet and creates a new window for it.'''
        for w in self.windows:
            if w._wallet.get_storage_path() == wallet_path:
                w.bring_to_top()
                break
        else:
            wizard_window: Optional[WalletWizard] = None
            if wallet_path is not None:
                is_valid, was_aborted, wizard_window = WalletWizard.attempt_open(
                    wallet_path)
                if was_aborted:
                    return None
                if not is_valid:
                    wallet_filename = os.path.basename(wallet_path)
                    MessageBox.show_error(
                        _("Unable to load file '{}'.").format(wallet_filename))
                    return None
            else:
                wizard_window = WalletWizard(is_startup=is_startup)
            if wizard_window is not None:
                result = wizard_window.run()
                if result != QDialog.Accepted:
                    return None
                wallet_path = wizard_window.get_wallet_path()
                # We cannot rely on accept alone indicating success.
                if wallet_path is None:
                    return None
            wallet = app_state.daemon.load_wallet(wallet_path)
            assert wallet is not None
            w = self._create_window_for_wallet(wallet)
        if uri:
            w.pay_to_URI(uri)
        w.bring_to_top()
        w.setWindowState(w.windowState() & ~QtCore.Qt.WindowMinimized
                         | QtCore.Qt.WindowActive)

        # this will activate the window
        w.activateWindow()
        return w

    def update_check(self) -> None:
        if (not app_state.config.get('check_updates', True)
                or app_state.config.get("offline", False)):
            return

        def f():
            import requests
            try:
                response = requests.request(
                    'GET',
                    "https://electrumsv.io/release.json",
                    headers={'User-Agent': 'ElectrumSV'},
                    timeout=10)
                result = response.json()
                self._on_update_check(True, result)
            except Exception:
                self._on_update_check(False, sys.exc_info())

        t = threading.Thread(target=f)
        t.setDaemon(True)
        t.start()

    def _on_update_check(self, success: bool, result: dict) -> None:
        if success:
            when_checked = datetime.datetime.now().astimezone().isoformat()
            app_state.config.set_key('last_update_check', result)
            app_state.config.set_key('last_update_check_time', when_checked,
                                     True)
        self.update_check_signal.emit(success, result)

    def initial_dialogs(self) -> None:
        '''Suppressible dialogs that are shown when first opening the app.'''
        dialogs.show_named('welcome-ESV-1.3.0b5')

    def event_loop_started(self) -> None:
        self.cosigner_pool = CosignerPool()
        self.label_sync = LabelSync()
        if app_state.config.get("show_crash_reporter", default=True):
            self.exception_hook = Exception_Hook(self)
        self.timer.start()
        signal.signal(signal.SIGINT, lambda *args: self.quit())
        self.initial_dialogs()
        path = app_state.config.get_cmdline_wallet_filepath()
        if not self.start_new_window(
                path, app_state.config.get('url'), is_startup=True):
            self.quit()

    def run_app(self) -> None:
        when_started = datetime.datetime.now().astimezone().isoformat()
        app_state.config.set_key('previous_start_time',
                                 app_state.config.get("start_time"))
        app_state.config.set_key('start_time', when_started, True)
        self.update_check()

        threading.current_thread().setName('GUI')
        self.timer.setSingleShot(False)
        self.timer.setInterval(500)  # msec
        self.timer.timeout.connect(app_state.device_manager.timeout_clients)

        QTimer.singleShot(0, self.event_loop_started)
        self.exec_()

        logs.remove_handler(self.log_handler)
        # 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.sendEvent(self.clipboard(), event)
        self.tray.hide()

    def run_coro(self, coro, *args, on_done=None):
        '''Run a coroutine.  on_done, if given, is passed the future containing the reuslt or
        exception, and is guaranteed to be called in the context of the GUI thread.
        '''
        def task_done(future):
            self.async_tasks_done.emit()

        future = app_state.async_.spawn(coro, *args, on_done=on_done)
        future.add_done_callback(task_done)
        return future

    def run_in_thread(self, func, *args, on_done=None):
        '''Run func(*args) in a thread.  on_done, if given, is passed the future containing the
        reuslt or exception, and is guaranteed to be called in the context of the GUI
        thread.
        '''
        return self.run_coro(run_in_thread, func, *args, on_done=on_done)
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_()
Beispiel #7
0
	def initUI(self):
		# 获取电脑屏幕宽高 让主界面初始化后处于屏幕中间
		wh = QApplication.desktop().screenGeometry()
		self.screen_w , self.screen_h = wh.width() ,wh.height()
		self.setGeometry(int((self.screen_w-300)/2),int((self.screen_h-600)/2),300,600)
		# self.setWindowOpacity(0.97); 

		#当前播放歌曲的封面 
		songer_img = DragLabel(self)
		# songer_img.setwinflag.connect(self.setwinflag)
		songer_img.setParent(self)
		songer_img.resize(300,200)

		self.picture = QLabel(songer_img)
		self.picture.resize(300,200)
		self.picture.setStyleSheet("QLabel{ border-image:url("+conf['pifu']+")}")

		# syl = QLabel(songer_img)
		# syl.setGeometry(15,5,34,15)
		# syl.setStyleSheet("QLabel{ border-image:url(image/newimg/logo.png);}")

		# ================================
		songinfo = QLabel(songer_img)
		songinfo.setGeometry(0,30,300,80)
		songinfo.setStyleSheet("QLabel{ background:transparent;}")

		songpic = QLabel(songinfo)
		songpic.setGeometry(10,0,80,80)
		songpic.setStyleSheet("QLabel{ border-image:url(image/newimg/user.jpg);border-radius:2px;}")

		self.songname = QLabel("老鼠爱大米 - 香香",songinfo)
		self.songname.setGeometry(105,0,210,25)
		self.songname.setStyleSheet("QLabel{ color:#EEE;font-size:15px;}")
		uploaduser = QLabel("By 张三的歌",songinfo)
		uploaduser.move(105,25)
		# uploaduser.setCursor(QCursor(Qt.PointingHandCursor))
		uploaduser.setStyleSheet("QLabel{ color:yellow;font-size:15px;} QLabel:hover{color:red}")

		fenshu = QLabel("评分 - 7.6",songinfo)
		fenshu.setGeometry(105,50,210,25)
		# self.picture.setGraphicsEffect(QGraphicsBlurEffect())
		fenshu.setStyleSheet("QLabel{ color:#EEE;font-size:15px;}")


		songtool = QLabel(songer_img)
		songtool.setGeometry(0,110,300,35)
		songtool.setStyleSheet("QLabel{ background:transparent;}")

		# 喜欢歌曲
		lovesong = QLabel(songtool)
		lovesong.setGeometry(20,10,25,25)
		lovesong.setStyleSheet("QLabel{ border-image:url(image/newimg/kg_ic_player_liked.png);}")
		# 评论
		pinglun = QLabel(songtool)
		pinglun.setGeometry(50,5,33,33)
		pinglun.setStyleSheet("QLabel{ border-image:url(image/newimg/pinglun.png);}")
		# 歌曲更多信息
		songmore = QLabel("查看这首歌的更多资料",songtool)
		songmore.move(100,10)
		# songmore.setCursor(QCursor(Qt.PointingHandCursor))
		songmore.setStyleSheet("QLabel{ color:#BBB} QLabel:hover{color:pink}")


		# ======================================

		# 顶部工具栏
		# 隐藏
		btn = QPushButton("",self)
		btn.setGeometry(270,0,15,32)
		# btn.setCursor(QCursor(Qt.PointingHandCursor))
		btn.setStyleSheet("QPushButton{ border:none;color:white;background:transparent;border-image:url(image/newimg/mini.png) } QPushButton:hover{ border-image:url(image/newimg/mini_2.png) } ")
		btn.clicked.connect(self.close)

		# 换皮肤
		btn = QPushButton("",self)
		btn.setGeometry(230,10,20,20)
		# btn.setCursor(QCursor(Qt.PointingHandCursor))
		btn.setStyleSheet("QPushButton{ border:none;color:white;background:transparent;border-image:url(image/newimg/fx_slide_menu_change_bg_2.png) } QPushButton:hover{ border-image:url(image/newimg/fx_slide_menu_change_bg.png) } ")
		btn.clicked.connect(self.huanfu)
		# 设置封面
		# btn = QPushButton("",self)
		# btn.setGeometry(230,-10,41,48)
		# btn.setCursor(QCursor(Qt.PointingHandCursor))
		# btn.setStyleSheet("QPushButton{ border:none;color:white;background:transparent;border-image:url(image/newimg/fengmian.png) } ")
		# btn.clicked.connect(self.setHeaderImg)
		# 开启/关闭歌词
		# btn = QPushButton("",self)
		# btn.setGeometry(200,0,30,30)
		# btn.setCursor(QCursor(Qt.PointingHandCursor))
		# btn.setStyleSheet("QPushButton{ border:none;color:white;background:transparent;border-image:url(image/newimg/geci.png) } ")
		# btn.clicked.connect(self.lrc)
		# 播放组件  ( 播放  前进 后退 播放时间 进度条 歌曲名 音量 )
		# 播放/暂停
		self.playBtn = QPushButton("",songer_img)
		self.playBtn.setGeometry(130,155,32,25)
		self.playBtn.setStyleSheet("QPushButton{ border-image:url(image/newimg/statusbar_btn_play.png);border:none } QPushButton:hover{ border-image:url(image/newimg/statusbar_btn_play_2.png)} ")
		# 下一首
		self.nextBtn = QPushButton("",songer_img)
		self.nextBtn.setGeometry(186,159,20,20)
		self.nextBtn.setStyleSheet("QPushButton{ border-image:url(image/newimg/statusbar_btn_next.png);border:none } QPushButton:hover{ border-image:url(image/newimg/statusbar_btn_next_2.png)}")
		# 音量调节
		self.songvolume = QPushButton("",songer_img)
		self.songvolume.setGeometry(236,159,20,20)
		self.songvolume.setStyleSheet("QPushButton{ border-image:url(image/newimg/ic_player_menu_volume.png);border:none } QPushButton:hover{ border-image:url(image/newimg/ic_player_menu_volume_2.png)}")
		self.songvolume.clicked.connect(self.setvolume)
		# 音量
		self.volslider = QSlider(Qt.Horizontal,self)
		self.volslider.setCursor(QCursor(Qt.UpArrowCursor))
		self.volslider.setGeometry(250,165,45,6)
		self.volslider.setValue(70)
		self.volslider.setRange(0,100)
		self.volslider.setStyleSheet(qss_vol)
		self.volslider.setVisible(False)

		# 上一首
		self.prevBtn = QPushButton("",songer_img)
		self.prevBtn.setGeometry(85,159,20,20)
		self.prevBtn.setStyleSheet("QPushButton{ border-image:url(image/newimg/statusbar_btn_prev.png);border:none } QPushButton:hover{ border-image:url(image/newimg/statusbar_btn_prev_2.png)}")
		# 播放模式
		self.playmodel = QPushButton("",songer_img)
		self.playmodel.setGeometry(35,156,25,25)
		self.playmodel.setStyleSheet("QPushButton{ border-image:url(image/newimg/allmodel.png);border:none } QPushButton:hover{ border-image:url(image/newimg/allmodel_2.png)}")
		self.playmodel.clicked.connect(self.moshi)

		# 当前播放时间
		self.songTime = QLabel("",self)
		self.songTime.setGeometry(240,180,80,20)
		self.songTime.setStyleSheet("QLabel{ color:#AAA;font-size:12px;}")
		self.songTime.setAlignment(Qt.AlignHCenter)
		
		# 当前歌曲名   
		self.currentMusicName = QLabel("",songer_img)
		self.currentMusicName.setGeometry(0,180,200,20)
		self.currentMusicName.setStyleSheet("QLabel{ color:white ;font-weight:100;font-size:12px;margin-left:5px;}")
		# 歌曲进度条
		self.processSlider = QSlider(Qt.Horizontal,self)
		self.processSlider.setGeometry(0,193,300,7)
		# self.processSlider.setRange(1,100)
		self.processSlider.setValue(0)
		self.processSlider.setStyleSheet(qss_process_slider)
		
		self.processSlider.setCursor(QCursor(Qt.UpArrowCursor))

		# 歌曲列表 ---------------------------
		listWgt = QWidget(self)
		listWgt.setGeometry(0, 200, 300,380)
		listWgt.setStyleSheet(qss_scrollbar)

		#列表
		self.songList = QListWidget(listWgt)
		self.songList.setGeometry(5,0,235,380)   
		self.songList.setStyleSheet(qss_songlist)	
		# 列表添加右键菜单
		# self.songList.setContextMenuPolicy(Qt.CustomContextMenu)
		# self.songList.customContextMenuRequested.connect(self.rightMenuShow)

		#歌曲列表右边的功能列表
		funcList = QListWidget(listWgt)
		funcList.setGeometry(240,0,55,380)   
		funcList.setStyleSheet(qss_menu)
		btn = QPushButton("",funcList)
		btn.clicked.connect(self.newwindow)
		btn.setGeometry(15,10,30,30)
		btn.setStyleSheet("QPushButton{ border-image:url(image/home.png)} \
			QPushButton:hover{ border-image:url(image/homehover.png) }")
		# btn.setCursor(QCursor(Qt.PointingHandCursor))
		btn = QPushButton("",funcList)
		btn.setGeometry(15,60,30,30)
		btn.setStyleSheet("QPushButton{ border-image:url(image/tuijian.png) } \
			QPushButton:hover{ border-image:url(image/tuijianhover.png) }")
		# btn.setCursor(QCursor(Qt.PointingHandCursor))
		btn = QPushButton("",funcList)
		btn.setGeometry(15,100,30,30)
		btn.setStyleSheet("QPushButton{ border-image:url(image/shoucang.png) }\QPushButton:hover{ border-image:url(image/shoucanghover.png) }")
		# btn.setCursor(QCursor(Qt.PointingHandCursor))

		btn = QPushButton("",funcList)
		btn.setGeometry(15,140,30,30)
		btn.setStyleSheet("QPushButton{ border-image:url(image/rizhi.png) }\
			QPushButton:hover{ border-image:url(image/rizhihover.png) }")
		# btn.setCursor(QCursor(Qt.PointingHandCursor))

		btn = QPushButton("",funcList)
		btn.setGeometry(17,180,30,30)
		btn.setStyleSheet("QPushButton{ border-image:url(image/mv.png) }\
			QPushButton:hover{ border-image:url(image/mvhover.png) }")
		# btn.setCursor(QCursor(Qt.PointingHandCursor))

		setbtn = QPushButton("",funcList)
		setbtn.setGeometry(15,225,33,33)
		setbtn.setStyleSheet("QPushButton{ border-image:url(image/settinghover.png) }\
			QPushButton:hover{ border-image:url(image/setting.png) }")
		setbtn.clicked.connect(self.openseting)

		#底部状态栏
		wg = QWidget(self)
		wg.setGeometry(0, 580, 300,20)
		wg.setStyleSheet("QWidget{ background:#2D2D2D; } ")
		# ql = QLabel(" <a style='color:#444;text-decoration:none;font-size:12px;'  href ='https://github.com/codeAB/music-player' >S Y L </a>",wg)
		# ql.resize(300,20)
		# ql.setAlignment(Qt.AlignRight)
		# ql.linkActivated.connect(self.openurl)

		#设置托盘图标
		tray = QSystemTrayIcon(self)
		tray.setIcon(QIcon('image/tray.png'))
		self.trayIconMenu = QMenu(self)
		self.trayIconMenu.setStyleSheet(qss_tray)
		showAction = QAction(QIcon('image/tray.png'),u"显示主面板", self,triggered=self.show)
		self.trayIconMenu.addAction(showAction)
		# self.trayIconMenu.addAction(preAction)
		# self.trayIconMenu.addAction(pauseAction)
		# self.trayIconMenu.addAction(nextAction)
		# self.trayIconMenu.addAction(quitAction)
		tray.setContextMenu(self.trayIconMenu)
		tray.show()
		tray.activated.connect(self.dbclick_tray)
Beispiel #8
0
class MyApp(QtWidgets.QMainWindow):
	mouseLeaveTimer=0

	def __init__(self):
		# Ui_MainWindow.__init__(self)
		#自己有__init__函数时,不会默认调用基类的__init__函数
		# 因为这里重写了__init__将基类的覆盖掉了,故需要主动调用之
		
		# QtWidgets.QMainWindow.__init__(self) 
		# super(MyApp,self).__init__()
		#上面两句的作用是相同的,下面这句是python3的新写法
		super().__init__()
		# 	Get the Screen size
		self.screenWidth=QDesktopWidget().availableGeometry().width()
		self.screenHeight=QDesktopWidget().availableGeometry().height()
		#初始化字体
		font=QFont('黑体')
		font.setPointSize(12)
		app.setFont(font)
		#  ColorSetting
		self.bgColor=QColor(66,66,77,88)

		#
		# self.setupUi(self)
		self.initUI()
		#用来控制半透明的bg面板自动消失
		self.timer=QTimer()
		self.timer.start(30)
		self.setGeometry(0,30,self.screenWidth,self.screenHeight//3)

		#Flagsq
		self.IsMouseHover=False
		self.MouseOver=False
		self.Locked=False
		self.Hidden=False
		self.isDrag=False
		self.isResize=False
		#变量初始化
		GLOBAL.WINDOWWIDTH=self.width()
		GLOBAL.WINDOWHEIGHT=self.height()
		self.bullets=[]
		self.dragPos=QPoint(22,22)
		self.savedName=''
		# self.screenBuffer=QBitmap(GLOBAL.WINDOWWIDTH,GLOBAL.WINDOWHEIGHT)
		# self.bufferPainter=QPainter(self.screenBuffer)
		# self.picture=QPicture()
		# 建立connection和slot的回调连接
		self.createConnections()
		# 连接到nodejs建立的服务器
		self.connect2Server()

	def initUI(self):
		#构建托盘
		self.trayIcon=QSystemTrayIcon(self)
		self.trayIcon.setIcon(QtGui.QIcon("tmpIcon.ico"))
		self.trayIcon.show()
		self.trayIcon.setToolTip('BulletGo')

		# 构建托盘菜单
		action_quit=QAction('退出',self)
		action_quit.triggered.connect(self.exitApp)
		action_switchLock=QAction('锁定/解锁(F6)',self)
		action_switchLock.triggered.connect(self.switchLock)
		action_showHide=QAction('显示/隐藏(F7)',self)
		action_showHide.triggered.connect(lambda:self.switchVisible(self))
		action_Settings=QAction('设置',self)
		action_Settings.triggered.connect(lambda:self.switchVisible(self.settingWindow))
		trayIconMenu=QtWidgets.QMenu(self)
		trayIconMenu.addAction(action_switchLock)
		trayIconMenu.addAction(action_showHide)
		trayIconMenu.addSeparator()
		trayIconMenu.addAction(action_Settings)
		trayIconMenu.addAction(action_quit)

		#设定快捷键
		QtWidgets.QShortcut(QtGui.QKeySequence(\
			QtCore.Qt.Key_F7),self,\
		(lambda:self.switchVisible(self.settingWindow)))
		QtWidgets.QShortcut(QtGui.QKeySequence(\
			QtCore.Qt.Key_F6),self,\
		(self.switchLock))

		self.trayIcon.setContextMenu(trayIconMenu)
		# 保障不按下鼠标也追踪mouseMove事件
		self.setMouseTracking(True)
		self.setMinimumSize(600,260)
		self.setWindowTitle("BulletGo")
		sizeGrip=QtWidgets.QSizeGrip(self)
		self.setWindowFlags(Qt.FramelessWindowHint\
			|Qt.WindowStaysOnTopHint|Qt.Window|\
			Qt.X11BypassWindowManagerHint)
		#Plan A
		self.setAttribute(Qt.WA_TranslucentBackground,True)
		#这一句是给Mac系统用的,防止它绘制(很黯淡的)背景
		self.setAutoFillBackground(False)
		QSizeGrip(self).setVisible(True)
		sizeGrip.setVisible(True)
		#Plan B  失败
		# palette=QPalette()
		# color=QColor(190, 230, 250)
		# color.setAlphaF(0.6)
		# palette.setBrush(self.backgroundRole(), color)
		# self.setPalette(palette)
		# self.setAutoFillBackground(True)
		# self.setBackgroundRole(QPalette.Window)

		#创建房间的Button和 输入框

		self.roomName=QPlainTextEdit()
		self.roomName.setPlaceholderText('请输入房间名')
		# self.roomName.resize(50,20)
		# self.roomName.move(0,0)
		# self.roomName.setBackgroundVisible(False)
		self.createBtn=QPushButton("创建/进入")
		self.hideBtn=QPushButton('隐藏本设置窗口')
		self.hideBtn.clicked.connect(self.joinRoom)
			# lambda:self.switchVisible(self.settingWindow))
		# self.createBtn.resize(50,20)
		# self.move(0,100)
		# self.d
		settingLayout=QVBoxLayout()
		hLayout=QHBoxLayout()
		settingLayout.addWidget(self.roomName)
		hLayout.addWidget(self.hideBtn)
		hLayout.addWidget(self.createBtn)
		self.settingWindow=QWidget()
		# self.hideBtn=setShortcut(QtGui.QKeySequence('Ctrl+B'))
		settingLayout.addLayout(hLayout)
		self.settingWindow.setLayout(settingLayout)
		# Qt.Tool的作用是  不在任务栏显示
		self.settingWindow.setWindowFlags(Qt.FramelessWindowHint|Qt.Tool\
			|Qt.X11BypassWindowManagerHint|Qt.Popup)
		self.roomName.show()
		self.createBtn.show()
		self.settingWindow.resize(160,26)
		self.settingWindow.show()


		# self.btnFire=QPushButton("Fire",self)
		# self.btnFire.resize(60,60)
		# self.btnFire.move(100,30)
		# self.btnFire.show()

		# self.btnLock=QPushButton("Lock",self)
		# self.btnLock.resize(40,40)
		# self.btnLock.move(self.screenWidth/2,30)
		# self.btnLock.setFlat(True)
		# self.btnLock.setIcon(QtGui.QIcon("tmpIcon.png"))
		# self.btnLock.show()



		# self.danmakuEditText=QPlainTextEdit(self)
		# self.danmakuEditText.resize(200,100)
		# self.danmakuEditText.move(100,100)
		# self.danmakuEditText.setBackgroundVisible(False)
		# self.danmakuEditText.show()

	def joinRoom(self):
		name=self.roomName.toPlainText()
		self.socketio.emit('join',name)

	def connect2Server(self):
		self.socketio=SocketIO('115.159.102.76/bulletgo_client',80,LoggingNamespace)
		self.registerEvents()
		# 开新线程监听服务器发来的消息,否则主线程被阻塞
		_thread.start_new_thread(self.socketio.wait,())

	def registerEvents(self):
		self.socketio.on('create_rsp',lambda rsp:self.handleIncomeBullet(rsp))
		self.socketio.on('bullet',lambda msg:self.handleIncomeBullet(msg))
		self.socketio.on('control_msg',lambda msg:print\
			('---control message---  : '+msg))
		self.socketio.on('sys_notification',lambda msg:print\
			('---system notification---  : '+msg))

	def handleIncomeBullet(self,bulletMsg):
		textsAndInfo=self.preProcessText(bulletMsg)
		if(len(textsAndInfo)>1):
			self.fireABullet(textsAndInfo[0],self.genQColorFromStr(textsAndInfo[1]))

	def createRoom_Nodejs(self,name):
		self.socketio.emit('create_room',name)

	def fireBtn(self):
		txt=self.danmakuEditText.toPlainText()
		tmpbullet=Bullet(txt,GLOBAL.ORANGE,random.randrange(9,16,2))
		self.bullets.append(tmpbullet)
		tmpbullet.prepare()
		# print(len(self.bullets))
		# testStr="line1\nline2\nline3"
		# textsAndInfo=self.preProcessText(testStr)
		# print(len(textsAndInfo))
		# print

	


	def fireABullet(self,txt,color=GLOBAL.ORANGE):
		tmpbullet=Bullet(txt,color,random.randrange(12,22,2))
		self.bullets.append(tmpbullet)
		tmpbullet.prepare()

	def createConnections(self):
		self.timer.timeout.connect(self.update)
		# self.btnFire.clicked.connect(self.fireBtn)
		self.createBtn.clicked.connect(\
			lambda:self.createRoom_Nodejs\
				(self.roomName.toPlainText()))
		# self.btnLock.clicked.connect(self.switchLock)
		# self.btnLock.clicked.connect(self.pullMsg)
		self.trayIcon.activated.connect(self.trayClick)

	def switchVisible(self,handle):
		if(handle.isHidden()):
			handle.activateWindow()
		handle.setHidden(not handle.isHidden())

	def trayClick(self,reason):
		#单击事件还没设计好
		# if(reason==QSystemTrayIcon.Trigger):
		# 	self.switchVisible(self)
		if(reason==QSystemTrayIcon.DoubleClick):
			self.switchVisible(self.settingWindow)


	def switchLock(self):
		self.Locked=not self.Locked

	'''这个神奇的用法, 在js中也可用'''
	'''博客搞好后, 这个要单独写个文章'''
	def genQColorFromStr(self,color):
		# print(color)
		return{
			'white':GLOBAL.WHITE,
			'green':GLOBAL.GREEN,
			'red':GLOBAL.RED,
			'pink':GLOBAL.PINK,
			'purple':GLOBAL.PURPLE,
			'darkblue':GLOBAL.DARKBLUE,
			'blue':GLOBAL.BLUE,
			'yellow':GLOBAL.YELLOW,
			'cyan':GLOBAL.CYAN,
			'orange':GLOBAL.ORANGE,
			'':GLOBAL.ORANGE
		}[color]

	def preProcessText(self,string):
		return string.split('`<')

	'''---[deprecated]---'''
	def realPullMsg(self):
		url='http://danmaku.applinzi.com/message.php'
		r =  requests.post(url,data=self.savedName)
		r.encoding='utf-8'
		#预处理收到的字符串  
		# print(r.text)
		# r.te
		textsAndInfo=self.preProcessText(r.text)
		i=0
		# print(textsAndInfo)
		# print(r.text)
		if(len(textsAndInfo)>1):
			while(i<len(textsAndInfo)-1):
				# print(len(textsAndInfo))
				# print('ddddd')
				# print(i)
				self.fireABullet(textsAndInfo[i],self.genQColorFromStr(textsAndInfo[i+1]))
				i+=2

	'''---[deprecated]---'''
	def pullMsg(self):
		_thread.start_new_thread(self.realPullMsg,())

	'''---[deprecated]---'''
	def createRoom(self):
		#编码问题实在天坑!!!
		self.savedName=self.roomName.toPlainText().encode('utf-8')#保存自己的房间号
		postData=self.roomName.toPlainText().encode('utf-8')
		r = requests.post('http://danmaku.applinzi.com/createroom.php',data=postData)
		r.encoding='utf-8'
		self.fireABullet(r.text)
		# print(r.encoding)
		if(len(r.text)==7):
			# 开始自动获取服务器上的消息内容
			self.pullTimer=QTimer()
			self.pullTimer.start(2000)
			self.pullTimer.timeout.connect(self.pullMsg)
		# print(r.content)
		# print(r.text)


	def closeEvent(self,e):
		e.accept()

	def mouseReleaseEvent(self,e):
		if(e.button()==Qt.LeftButton):
			self.isDrag=False
			self.isResize=False

	def mousePressEvent(self,e):
		if e.button()==Qt.LeftButton:
			self.LDown=True
			# self.dragPos=e.globalPos()-self.frameGeometry().topLeft()
			self.dragPos=e.pos()#效果同上,鼠标相对窗口左上角的位置
			
			if(GLOBAL.WINDOWWIDTH-e.pos().x()<16\
			and GLOBAL.WINDOWHEIGHT-e.pos().y()<16):
				self.topLeft=self.frameGeometry().topLeft()
				self.isResize=True
			else:
				if(not self.Locked):
					self.isDrag=True
		# else:
		# 	if e.button()==Qt.RightButton:
		# 		self.exitApp()
		e.accept()

	def mouseMoveEvent(self,e):
		if(GLOBAL.WINDOWWIDTH-e.pos().x()<16\
			and GLOBAL.WINDOWHEIGHT-e.pos().y()<16):
			#更改鼠标样式
			self.setCursor(Qt.SizeFDiagCursor)
		else:
			self.setCursor(Qt.ArrowCursor)
		#如果是Resize,改变窗口大小
		if(self.isResize):
			tmp=e.globalPos()-self.topLeft
			self.move(self.topLeft)
			self.resize(tmp.x(),tmp.y())
		if (self.isDrag):
			self.move(e.globalPos()-self.dragPos)
		e.accept();



	def enterEvent(self,e):
		self.MouseOver=True
		self.IsMouseHover=True
		return super(MyApp,self).enterEvent(e)

	def setMouseHoverFalse(self):
		# if(not self.MouseOver):
		self.IsMouseHover=self.MouseOver


	def leaveEvent(self,e):
		QTimer.singleShot(800,self.setMouseHoverFalse)
		self.MouseOver=False
		return super(MyApp,self).leaveEvent(e)

	def resizeEvent(self,e):
		GLOBAL.WINDOWWIDTH=self.width()
		GLOBAL.WINDOWHEIGHT=self.height()
		# self.screenBuffer=QBitmap(GLOBAL.WINDOWWIDTH,GLOBAL.WINDOWHEIGHT)
		# self.bufferPainter=QPainter(self.screenBuffer)
		# print('resized')
		# self.repaint()
		e.accept()

	def paintEvent(self,e):
		# Get the Painter
		painter=QPainter(self)
		font=QFont('黑体',GLOBAL.BULLETFONTSIZE,QFont.Bold)
		painter.setFont(font)
		#Draw a semi-Transparent rect whose size is the same with this window
		if(self.IsMouseHover and (not self.Locked)):
			painter.fillRect(0,0,GLOBAL.WINDOWWIDTH,GLOBAL.WINDOWHEIGHT\
				,self.bgColor)
		# painter.setBackground(QBrush(QColor(123,222,123,122)))
		#画所有bullet
		for b in self.bullets:
			b.draw(painter)
		for b in self.bullets:
			if(b.IsExpired):
				self.bullets.remove(b)
		# painter.drawPicture(0,0,self.picture)
		# painter.drawText(30,100,"Hello this is a PyQt5 App我也会说中文")
		return super(MyApp,self).paintEvent(e)

	def exitApp(self):
		self.trayIcon.hide()
		sys.exit()
Beispiel #9
0
class Magneto(MagnetoCore):

    """
    Magneto Updates Notification Applet class.
    """

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

    def _first_check(self):
        def _do_check():
            self.send_check_updates_signal(startup_check = True)
            return False

        if self._dbus_service_available:
            const_debug_write("_first_check", "spawning check.")
            QTimer.singleShot(10000, _do_check)

    def startup(self):
        self._dbus_service_available = self.setup_dbus()
        if config.settings["APPLET_ENABLED"] and \
            self._dbus_service_available:
            self.enable_applet(do_check = False)
            const_debug_write("startup", "applet enabled, dbus service available.")
        else:
            const_debug_write("startup", "applet disabled.")
            self.disable_applet()
        if not self._dbus_service_available:
            const_debug_write("startup", "dbus service not available.")
            QTimer.singleShot(30000, self.show_service_not_available)
        else:
            const_debug_write("startup", "spawning first check.")
            self._first_check()

        # Notice Window instance
        self._notice_window = AppletNoticeWindow(self)

        self._window.show()

        # Enter main loop
        self._app.exec_()

    def close_service(self):
        super(Magneto, self).close_service()
        self._app.quit()

    def change_icon(self, icon_name):
        name = self.icons.get(icon_name)
        self._window.setIcon(QIcon.fromTheme(name))

    def disable_applet(self, *args):
        super(Magneto, self).disable_applet()
        self._menu_items["disable_applet"].setEnabled(False)
        self._menu_items["enable_applet"].setEnabled(True)

    def enable_applet(self, w = None, do_check = True):
        done = super(Magneto, self).enable_applet(do_check = do_check)
        if done:
            self._menu_items["disable_applet"].setEnabled(True)
            self._menu_items["enable_applet"].setEnabled(False)

    def show_alert(self, title, text, urgency = None, force = False,
                   buttons = None):

        # NOTE: there is no support for buttons via QSystemTrayIcon.

        if ((title, text) == self.last_alert) and not force:
            return

        def _action_activate_cb(action_num):
            if not buttons:
                return

            try:
                action_info = buttons[action_num - 1]
            except IndexError:
                return

            _action_id, _button_name, button_callback = action_info
            button_callback()

        def do_show():
            if not self._window.supportsMessages():
                const_debug_write("show_alert", "messages not supported.")
                return

            icon_id = QSystemTrayIcon.Information
            if urgency == "critical":
                icon_id = QSystemTrayIcon.Critical

            self._window.showMessage(title, text, icon_id)
            self.last_alert = (title, text)

        QTimer.singleShot(0, do_show)

    def update_tooltip(self, tip):
        def do_update():
            self._window.setToolTip(tip)
        QTimer.singleShot(0, do_update)

    def applet_context_menu(self):
        """No action for now."""

    def _applet_activated(self, reason):
        const_debug_write("applet_activated", "Applet activated: %s" % reason)
        if reason == QSystemTrayIcon.DoubleClick:
            const_debug_write("applet_activated", "Double click event.")
            self.applet_doubleclick()

    def hide_notice_window(self):
        self.notice_window_shown = False
        self._notice_window.hide()

    def show_notice_window(self):

        if self.notice_window_shown:
            const_debug_write("show_notice_window", "Notice window already shown.")
            return

        if not self.package_updates:
            const_debug_write("show_notice_window", "No computed updates.")
            return

        entropy_ver = None
        packages = []
        for atom in self.package_updates:

            key = entropy.dep.dep_getkey(atom)
            avail_rev = entropy.dep.dep_get_entropy_revision(atom)
            avail_tag = entropy.dep.dep_gettag(atom)
            my_pkg = entropy.dep.remove_entropy_revision(atom)
            my_pkg = entropy.dep.remove_tag(my_pkg)
            pkgcat, pkgname, pkgver, pkgrev = entropy.dep.catpkgsplit(my_pkg)
            ver = pkgver
            if pkgrev != "r0":
                ver += "-%s" % (pkgrev,)
            if avail_tag:
                ver += "#%s" % (avail_tag,)
            if avail_rev:
                ver += "~%s" % (avail_tag,)

            if key == "sys-apps/entropy":
                entropy_ver = ver

            packages.append("%s (%s)" % (key, ver,))

        critical_msg = ""
        if entropy_ver is not None:
            critical_msg = "%s <b>sys-apps/entropy</b> %s, %s <b>%s</b>. %s." % (
                _("Your system currently has an outdated version of"),
                _("installed"),
                _("the latest available version is"),
                entropy_ver,
                _("It is recommended that you upgrade to "
                  "the latest before updating any other packages")
            )

        self._notice_window.populate(packages, critical_msg)

        self._notice_window.show()
        self.notice_window_shown = True
Beispiel #10
0
class PymodoroGUI(QWidget):
    """
        GUI for Pymodoro
    """
    def __init__(self):
        """
            Initializer for the Pomodoro GUI class
        """
        super(PymodoroGUI, self).__init__()

        self.res_dir = os.path.join("../ext/")
        self.green_tomato_icon = os.path.join(self.res_dir, "greentomato.png")
        self.red_tomato_icon = os.path.join(self.res_dir, "redtomato.png")
        self.tray = QSystemTrayIcon(QIcon(self.green_tomato_icon))
        self.pom = Pomodoro()
        self.pom.ringer.connect(self.signal)
        self.init_ui()

    def signal(self, pomodori):
        """
            Callback given to the Pomodoro class.
            Called when a pomodoro is up
        """
        if pomodori % 4 == 0 and pomodori != 0:
            self.tray.showMessage("4 Pomodori has passed!",
                                   "Take a long break: 15-30min",
                                   QSystemTrayIcon.Information)
        else:
            self.tray.showMessage("Pomodoro's up!",
                                   "Take a short break: 3-5min",
                                   QSystemTrayIcon.Information)
        self.tray.setIcon(QIcon(self.green_tomato_icon))

    def init_tray(self):
        """
            Initializes the systray menu
        """
        traymenu = QMenu("Menu")
        self.tray.setContextMenu(traymenu)
        self.tray.show()
        self.tray.activated.connect(self.tray_click)
        self.tray.setToolTip("Pomodori: "+str(self.pom.pomodori))

        set_timer_tray = QLineEdit()
        set_timer_tray.setPlaceholderText("Set timer")
        set_timer_tray.textChanged.connect(lambda:
                                           self.update_timer_text(set_timer_tray.text()))
        traywidget = QWidgetAction(set_timer_tray)
        traywidget.setDefaultWidget(set_timer_tray)
        traymenu.addAction(traywidget)

        start_timer_action = QAction("&Start Timer", self)
        start_timer_action.triggered.connect(self.start_timer_click)
        traymenu.addAction(start_timer_action)

        exit_action = QAction("&Exit", self)
        exit_action.triggered.connect(QCoreApplication.instance().quit)
        traymenu.addAction(exit_action)

    def tray_click(self, activation):
        """
            Method called when clicking the tray icon
        """
        if activation == QSystemTrayIcon.Trigger:
            if self.isVisible():
                self.hide()
            else:
                self.show()
        elif activation == QSystemTrayIcon.Context:
            self._tray.show()

    def close_event(self, event):
        self._tray.showMessage("Running in system tray",
                                """The program will keep running in the system tray.\n
                                To terminate the program choose exit from the context menu""",
                                QSystemTrayIcon.Information)
        self.hide()
        event.ignore()

    def wheel_event(self, event):
        if event.delta() > 0:
            timervalue = int(self.settimertext.text())
            timervalue = timervalue + 1
            self.settimertext.setText(str(timervalue))
        else:
            timervalue = int(self.settimertext.text())
            timervalue = timervalue - 1
            self.settimertext.setText(str(timervalue))

    def init_ui(self):
        """
            Initializes the GUI
        """
        self.init_tray()
        resolution = QApplication.desktop().availableGeometry()
        width = 150
        height = 100

        # place exactly in center of screen
        self.setGeometry((resolution.width() / 2) - (width / 2),
                         (resolution.height() / 2) - (height / 2),
                         width, height)
        self.setWindowTitle("Pomodoro")
        self.setWindowIcon(QIcon(os.path.join(self.res_dir, "redtomato.png")))

        grid = QGridLayout()
        grid.setSpacing(5)
        self.settimertext = QLineEdit()
        grid.addWidget(self.settimertext, 1, 0)

        self.errortext = QLabel()
        grid.addWidget(self.errortext, 2, 0)
        self.errortext.hide()

        self.settimertext.setText(str(25))
        self.settimertext.textChanged.connect(lambda:
                                              self.update_timer_text(
                                                 self.settimertext.text()))

        self.start_timerbutton = QPushButton("start timer")
        grid.addWidget(self.start_timerbutton, 3, 0)
        self.start_timerbutton.clicked.connect(self.start_timer_click)

        self.setLayout(grid)
        self.show()

    def start_timer_click(self):
        """
            Method run when starting the pomodoro timer
        """
        self.pom.start_timer()
        self.tray.setIcon(QIcon(self.red_tomato_icon))
        self.hide()

    def update_timer_text(self, number):
        """
            Method run when setting the number of minutes in the timer
        """
        try:
            self.pom.set_timer_minutes(int(number))
            self.errortext.hide()
        except ValueError:
            self.errortext.setText("Please input a number")
            self.errortext.show()
Beispiel #11
0
class ElectrumGui(PrintError):

    @profiler
    def __init__(self, config, daemon, plugins):
        set_language(config.get('language', get_default_language()))
        # 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.print_error('Error setting dark theme: {}'.format(repr(e)))
        # 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_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:
            traceback.print_exc(file=sys.stdout)
            QMessageBox.warning(None, _('Error'),
                                _('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:
            wallet = self._start_wizard_to_select_or_create_wallet(path)
        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:
            traceback.print_exc(file=sys.stdout)
            QMessageBox.warning(None, _('Error'),
                                _('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
        except (WalletFileException, BitcoinException) as e:
            traceback.print_exc(file=sys.stderr)
            QMessageBox.warning(None, _('Error'),
                                _('Cannot load wallet') + ' (2):\n' + str(e))
            return
        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):
        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:
            traceback.print_exc(file=sys.stdout)
            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.print_error('closing GUI')
        self.app.quit()
Beispiel #12
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
class UI_Shakespeare(QMainWindow, Ui_MainWindow):
    def __init__(self, parent=None):
        super(UI_Shakespeare, self).__init__(parent)
        self.setupUi(self)
        self.result_TextEdit.setFontPointSize(10)
        self.actionQuit.setShortcut(QKeySequence('Ctrl+Q'))
        self.actionQuit.triggered.connect(self.on_actionQuit_triggered)
        self.engine = Shakespeare()
        self.search_lineEdit.setFocus()
        self.setupSysTray()
        self.rightCorner()

    def connectActions(self):
        self.language_pushButton.triggered.connect(
            self.on_language_pushButton_clicked)
        self.search_lineEdit.textChanged.connect(
            self.on_search_lineEdit_textChanged)
        self.exact_checkBox.stateChanged.connect(
            self.on_exact_checkBox_stateChanged)
        self.searchtype_comboBox.currentIndexChanged.connect(
            self.on_searchtype_comboBox_currentIndexChanged)

    # hide with Escape
    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Escape:
            self.hide()

    def rightCorner(self):
        widget = QApplication.desktop()
        screenwidth = widget.width()
        screenheight = widget.height()
        windowsize = self.size()
        width = windowsize.width()
        height = windowsize.height()
        x = screenwidth - width
        y = screenheight - height
        y -= 50
        self.move(x, y)

    def setupSysTray(self):
        self.trayIcon = QSystemTrayIcon(QIcon(':/icons/imgs/shakespeare.png'))
        self.trayIcon.activated.connect(self.restore)
        quitAction = QAction(QIcon(":/icons/imgs/quit.png"),
                             "Quit",
                             self,
                             triggered=qApp.quit)
        trayIconMenu = QMenu(self)
        trayIconMenu.addAction(quitAction)
        self.trayIcon.setContextMenu(trayIconMenu)
        self.trayIcon.show()

    def restore(self, reason):
        """Toggle main window visibility"""
        if reason == 3:
            if self.isVisible():
                self.hide()
            else:
                self.update()
                self.show()

    def on_clear_pushButton_clicked(self):
        self.search()

    def on_language_pushButton_clicked(self):
        self.search()

    def statusMessage(self, amount):
        message = ('%i words found' % amount) if amount else 'word not found'
        self.statusbar.showMessage(message)

    def renderText(self, result, reverse=False):
        if reverse:
            color1, color2 = 'Red', 'Blue'
        else:
            color1, color2 = 'Blue', 'Red'
        for e, s in result:
            self.result_TextEdit.setTextColor(QColor(color1))
            self.result_TextEdit.insertPlainText(e)
            self.result_TextEdit.setTextColor(QColor('Black'))
            self.result_TextEdit.insertPlainText(' : ')
            self.result_TextEdit.setTextColor(QColor(color2))
            self.result_TextEdit.insertPlainText('  ' + s + '\n')

    def search(self):
        self.result_TextEdit.clear()
        if not len(self.search_lineEdit.text()):
            self.statusbar.clearMessage()
            return
        result = self.engine.search(self.search_lineEdit.text(),
                                    self.searchtype_comboBox.currentIndex(),
                                    self.exact_checkBox.isChecked(),
                                    self.language_pushButton.isChecked())
        self.statusMessage(len(result))
        if not result:
            self.statusMessage(len(result))
            return
        self.renderText(result, self.language_pushButton.isChecked())

    def on_search_lineEdit_textChanged(self):
        self.search()

    def on_exact_checkBox_stateChanged(self):
        self.search()
        self.searchtype_comboBox.setVisible(
            not self.exact_checkBox.isChecked())

    def on_searchtype_comboBox_currentIndexChanged(self):
        self.search()

    def on_actionQuit_triggered(self):
        qApp.quit()

    @pyqtSlot()
    def on_actionAbout_triggered(self):
        QMessageBox.about(
            self, u"About Shakespeare", u"<h3>Shakespeare %s </h3>\
        English/Spanish dictionary <br/>\
        <hr>(C) Manuel E. Gutierrez 2007-2013 <br/>\
         <br/>This program is licensed under the GPL v3<br />\
         <br />\
         http://github.org/xr09/shakespeare</p>" % VERSION)

    @pyqtSlot()
    def on_actionAbout_Qt_triggered(self):
        QMessageBox.aboutQt(self, "About Qt")

    def closeEvent(self, event):
        self.hide()
        event.ignore()
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}))
class MainWidget(QTabWidget):

    """Custom main widget."""

    def __init__(self, parent=None, *args, **kwargs):
        """Init class custom tab widget."""
        super(MainWidget, self).__init__(parent=None, *args, **kwargs)
        self.parent = parent
        self.setTabBar(TabBar(self))
        self.setMovable(False)
        self.setTabsClosable(False)
        self.setTabShape(QTabWidget.Triangular)
        self.init_preview()
        self.init_corner_menus()
        self.init_tray()
        self.addTab(TabSearch(self), "Search")
        self.addTab(TabTool(self), "Tools")
        self.addTab(TabHtml(self), "HTML")
        self.addTab(TabSymbols(self), "Symbols")
        self._recent_tab = TabRecent(self)
        self.recentify = self._recent_tab.recentify  # shortcut
        self.addTab(self._recent_tab, "Recent")
        self.widgets_to_tabs(self.json_to_widgets(UNICODEMOTICONS))
        self.make_trayicon()
        self.setMinimumSize(QDesktopWidget().screenGeometry().width() // 1.5,
                            QDesktopWidget().screenGeometry().height() // 1.5)
        # self.showMaximized()

    def init_preview(self):
        self.previews, self.timer = [], QTimer(self)
        self.fader, self.previous_pic = FaderWidget(self), None
        self.timer.setSingleShot(True)
        self.timer.timeout.connect(lambda: [_.close() for _ in self.previews])
        self.taimer, self.preview = QTimer(self), QLabel("Preview")
        self.taimer.setSingleShot(True)
        self.taimer.timeout.connect(lambda: self.preview.hide())
        font = self.preview.font()
        font.setPixelSize(100)
        self.preview.setFont(font)
        self.preview.setDisabled(True)
        self.preview.setWindowFlags(Qt.FramelessWindowHint | Qt.Tool)
        self.preview.setAttribute(Qt.WA_TranslucentBackground, True)

    def init_corner_menus(self):
        self.menu_1, self.menu_0 = QToolButton(self), QToolButton(self)
        self.menu_1.setText(" ⚙ ")
        self.menu_1.setToolTip("<b>Options, Extras")
        self.menu_0.setText(" ? ")
        self.menu_0.setToolTip("<b>Help, Info")
        font = self.menu_1.font()
        font.setBold(True)
        self.menu_1.setFont(font)
        self.menu_0.setFont(font)
        self.menu_tool, self.menu_help = QMenu("Tools Extras"), QMenu("Help")
        self.menu_tool.addAction(" Tools & Extras ").setDisabled(True)
        self.menu_help.addAction(" Help & Info ").setDisabled(True)
        self.menu_0.setMenu(self.menu_help)
        self.menu_1.setMenu(self.menu_tool)
        self.menu_tool.addAction("Explain Unicode", self.make_explain_unicode)
        self.menu_tool.addAction("Alternate Case Clipboard",
                                 self.alternate_clipboard)
        self.menu_tool.addSeparator()
        self.menu_tool.addAction("AutoCenter Window", self.center)
        self.menu_tool.addAction("Set Icon", self.set_icon)
        self.menu_tool.addAction(  # force recreate desktop file
            "Add Launcher to Desktop", lambda: set_desktop_launcher(
                "unicodemoticon", AUTOSTART_DESKTOP_FILE, True))
        self.menu_tool.addAction("Move to Mouse position",
                                 self.move_to_mouse_position)
        self.menu_tool.addSeparator()
        self.menu_tool.addAction("Minimize", self.showMinimized)
        self.menu_tool.addAction("Hide", self.hide)
        self.menu_tool.addAction("Quit", exit)
        self.menu_help.addAction("About Qt 5",
                                 lambda: QMessageBox.aboutQt(None))
        self.menu_help.addAction("About Unicodemoticon",
                                 lambda: open_new_tab(__url__))
        self.setCornerWidget(self.menu_1, 1)
        self.setCornerWidget(self.menu_0, 0)
        self.currentChanged.connect(self.make_tabs_previews)
        self.currentChanged.connect(self.make_tabs_fade)

    def init_tray(self):
        self.tray, self.menu = QSystemTrayIcon(self), QMenu(__doc__)
        self.menu.addAction("    Emoticons").setDisabled(True)
        self.menu.setIcon(self.windowIcon())
        self.menu.addSeparator()
        self.menu.setProperty("emoji_menu", True)
        list_of_labels = sorted(UNICODEMOTICONS.keys())  # menus
        menus = [self.menu.addMenu(_.title()) for _ in list_of_labels]
        self.menu.addSeparator()
        log.debug("Building Emoticons SubMenus.")
        for item, label in zip(menus, list_of_labels):
            item.setStyleSheet("padding:0;margin:0;border:0;menu-scrollable:1")
            font = item.font()
            font.setPixelSize(20)
            item.setFont(font)
            self.build_submenu(UNICODEMOTICONS[label.lower()], item)
        self.menu.addSeparator()
        self.menu.addAction("Alternate Case Clipboard",
                            self.alternate_clipboard)
        self.menu.addSeparator()
        self.menu.addAction("Quit", exit)
        self.menu.addAction("Show", self.showMaximized)
        self.menu.addAction("Minimize", self.showMinimized)
        self.tray.setContextMenu(self.menu)

    def build_submenu(self, char_list: (str, tuple), submenu: QMenu) -> QMenu:
        """Take a list of characters and a submenu and build actions on it."""
        submenu.setProperty("emoji_menu", True)
        submenu.setWindowOpacity(0.9)
        submenu.setToolTipsVisible(True)
        for _char in sorted(char_list):
            action = submenu.addAction(_char.strip())
            action.setToolTip(self.get_description(_char))
            action.hovered.connect(lambda _, ch=_char: self.make_preview(ch))
            action.triggered.connect(
                lambda _, char=_char: QApplication.clipboard().setText(char))
        return submenu

    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.showMaximized())
            return self.tray.show()

    def make_explain_unicode(self) -> tuple:
        """Make an explanation from unicode entered,if at least 1 chars."""
        explanation, uni = "", None
        uni = str(QInputDialog.getText(
            None, __doc__, "<b>Type Unicode character to explain?")[0]).strip()
        if uni and len(uni):
            explanation = ", ".join([self.get_description(_) for _ in uni])
            QMessageBox.information(None, __doc__, str((uni, explanation)))
        log.debug((uni, explanation))
        return (uni, explanation)

    def alternate_clipboard(self) -> str:
        """Make alternating camelcase clipboard."""
        return QApplication.clipboard().setText(
            self.make_alternate_case(str(QApplication.clipboard().text())))

    def make_alternate_case(self, stringy: str) -> str:
        """Make alternating camelcase string."""
        return "".join([_.lower() if i % 2 else _.upper()
                        for i, _ in enumerate(stringy)])

    def get_description(self, emote: str):
        description = ""
        try:
            description = unicodedata.name(str(emote).strip()).title()
        except ValueError:
            log.debug("Description not found for Unicode: " + emote)
        finally:
            return description

    def make_preview(self, emoticon_text: str):
        """Make Emoticon Previews for the current Hovered one."""
        log.debug(emoticon_text)
        if self.taimer.isActive():  # Be Race Condition Safe
            self.taimer.stop()
        self.preview.setText(" " + emoticon_text + " ")
        self.preview.move(QCursor.pos())
        self.preview.show()
        self.taimer.start(1000)  # how many time display the previews

    def json_to_widgets(self, jotason: dict):
        """Take a json string object return QWidgets."""
        dict_of_widgets, row = {}, 0
        for titlemotes in tuple(sorted(jotason.items())):
            tit = str(titlemotes[0]).strip()[:9].title()
            area = ScrollGroup(tit)
            layout = area.layout()

            grid_cols = 2 if tit.lower() == "multichar" else 8
            for index, emote in enumerate(tuple(set(list(titlemotes[1])))):
                button = QPushButton(emote, self)
                button.clicked.connect(lambda _, c=emote:
                                       QApplication.clipboard().setText(c))
                button.released.connect(self.hide)
                button.released.connect(lambda c=emote: self.recentify(c))
                button.pressed.connect(lambda c=emote: self.make_preview(c))
                button.setToolTip("<center><h1>{0}<br>{1}".format(
                    emote, self.get_description(emote)))
                button.setFlat(True)
                font = button.font()
                font.setPixelSize(50)
                button.setFont(font)
                row = row + 1 if not index % grid_cols else row
                layout.addWidget(button, row, index % grid_cols)

            dict_of_widgets[tit] = area
        return dict_of_widgets

    def widgets_to_tabs(self, dict_of_widgets: dict):
        """Take a dict of widgets and build tabs from them."""
        for title, widget in tuple(sorted(dict_of_widgets.items())):
            self.addTab(widget, title)

    def center(self):
        """Center Window on the Current Screen,with Multi-Monitor support."""
        self.showNormal()
        self.resize(QDesktopWidget().screenGeometry().width() // 1.5,
                    QDesktopWidget().screenGeometry().height() // 1.5)
        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)
        return bool(not self.move(window_geometry.topLeft()))

    def move_to_mouse_position(self):
        """Center the Window on the Current Mouse position."""
        self.showNormal()
        self.resize(QDesktopWidget().screenGeometry().width() // 1.5,
                    QDesktopWidget().screenGeometry().height() // 1.5)
        window_geometry = self.frameGeometry()
        window_geometry.moveCenter(QApplication.desktop().cursor().pos())
        return bool(not self.move(window_geometry.topLeft()))

    def set_icon(self, icon: (None, str)=None) -> str:
        """Return a string with opendesktop standard icon name for Qt."""
        if not icon:
            try:
                cur_idx = STD_ICON_NAMES.index(self.windowIcon().name())
            except ValueError:
                cur_idx = 0
            icon = QInputDialog.getItem(None, __doc__, "<b>Choose Icon name?:",
                                        STD_ICON_NAMES, cur_idx, False)[0]
        if icon:
            log.debug("Setting Tray and Window Icon name to:{}.".format(icon))
            self.tray.setIcon(QIcon.fromTheme("{}".format(icon)))
            self.setWindowIcon(QIcon.fromTheme("{}".format(icon)))
        return icon

    def make_tabs_fade(self, index):
        """Make tabs fading transitions."""
        self.fader.fade(
            self.previous_pic, self.widget(index).geometry(),
            1 if self.tabPosition() else self.tabBar().tabRect(0).height())
        self.previous_pic = self.currentWidget().grab()

    def make_undock(self):
        """Undock a Tab from TabWidget and promote to a Dialog."""
        dialog, index = QDialog(self), self.currentIndex()
        widget_from_tab = self.widget(index)
        dialog_layout = QVBoxLayout(dialog)
        dialog.setWindowTitle(self.tabText(index))
        dialog.setToolTip(self.tabToolTip(index))
        dialog.setWhatsThis(self.tabWhatsThis(index))
        dialog.setWindowIcon(self.tabIcon(index))
        dialog.setFont(widget_from_tab.font())
        dialog.setStyleSheet(widget_from_tab.styleSheet())
        dialog.setMinimumSize(widget_from_tab.minimumSize())
        dialog.setMaximumSize(widget_from_tab.maximumSize())
        dialog.setGeometry(widget_from_tab.geometry())

        def closeEvent_override(event):
            """Re-dock back from Dialog to a new Tab."""
            msg = "<b>Close this Floating Tab Window and Re-Dock as a new Tab?"
            conditional = QMessageBox.question(
                self, "Undocked Tab", msg, QMessageBox.Yes | QMessageBox.No,
                QMessageBox.No) == QMessageBox.Yes
            if conditional:
                index_plus_1 = self.count() + 1
                self.insertTab(index_plus_1, widget_from_tab,
                               dialog.windowIcon(), dialog.windowTitle())
                self.setTabToolTip(index_plus_1, dialog.toolTip())
                self.setTabWhatsThis(index_plus_1, dialog.whatsThis())
                return event.accept()
            else:
                return event.ignore()

        dialog.closeEvent = closeEvent_override
        self.removeTab(index)
        widget_from_tab.setParent(self.parent if self.parent else dialog)
        dialog_layout.addWidget(widget_from_tab)
        dialog.setLayout(dialog_layout)
        widget_from_tab.show()
        dialog.show()  # exec_() for modal dialog, show() for non-modal dialog
        dialog.move(QCursor.pos())

    def make_tabs_previews(self, index):
        """Make Tabs Previews for all tabs except current, if > 3 Tabs."""
        if self.count() < 4 or not self.tabBar().tab_previews:
            return False  # At least 4Tabs to use preview,and should be Enabled
        if self.timer.isActive():  # Be Race Condition Safe
            self.timer.stop()
        for old_widget in self.previews:
            old_widget.close()  # Visually Hide the Previews closing it
            old_widget.setParent(None)  # Orphan the old previews
            old_widget.destroy()  # Destroy to Free Resources
        self.previews = [QLabel(self) for i in range(self.count())]  # New Ones
        y_pos = self.size().height() - self.tabBar().tabRect(0).size().height()
        for i, widget in enumerate(self.previews):  # Iterate,set QPixmaps,Show
            if i != index:  # Dont make a pointless preview for the current Tab
                widget.setScaledContents(True)  # Auto-Scale QPixmap contents
                tabwidth = self.tabBar().tabRect(i).size().width()
                tabwidth = 200 if tabwidth > 200 else tabwidth  # Limit sizes
                widget.setPixmap(self.widget(i).grab().scaledToWidth(tabwidth))
                widget.resize(tabwidth - 1, tabwidth)
                if self.tabPosition():  # Move based on Top / Bottom positions
                    widget.move(self.tabBar().tabRect(i).left() * 1.1,
                                y_pos - tabwidth - 3)
                else:
                    widget.move(self.tabBar().tabRect(i).bottomLeft() * 1.1)
                widget.show()
        self.timer.start(1000)  # how many time display the previews
        return True
Beispiel #16
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)
Beispiel #17
0
from PyQt5.QtGui import QIcon, QClipboard, QKeySequence
import cpf

def gerar_cpf():
    clip = QApplication.clipboard()
    clip.setText(cpf.gerar_cpf(False))

def gerar_cpf_formatado():
    clip = QApplication.clipboard()
    clip.setText(cpf.gerar_cpf(True))

def buscar_ip():
    clip = QApplication.clipboard()
    clip.setText(ipgetter.myip())

if __name__ == '__main__':
    app = QApplication([])
    icon = QSystemTrayIcon(QIcon("img/icon.png"))
    menu = QMenu()

    menu.addAction("Gerar cpf sem formatação", gerar_cpf)
    menu.addAction("Gerar cpf com formatação", gerar_cpf_formatado)
    menu.addSeparator()
    menu.addAction("Buscar id público", buscar_ip)
    menu.addSeparator()
    menu.addAction("Fechar", app.quit)
    icon.setContextMenu(menu)

    icon.show()
    app.exec_()
Beispiel #18
0
class MainWindow(QMainWindow, Ui_MainWindow):
    tmpPath = os.path.dirname(os.path.realpath(sys.argv[0])) + '/tmp'

    mailer = None
    runThread = None
    time = 0
    taskInfo = None

    tray = None
    dlgSetting = None
    settings = {
        "chkMinToTray": True,
    }

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

        self.mailer = MyMailer()
        self.mailer.signal.connect(self.log)
        self.mailer.init()

    def changeEvent(self, e):
        if e.type() == QEvent.WindowStateChange:
            if self.isMinimized() and Util.isWindows():
                if self.settings["chkMinToTray"]:
                    self.hide()
            elif self.isMaximized():
                pass
            elif self.isFullScreen():
                pass
            elif self.isActiveWindow():
                pass
        elif e.type() == QEvent.ActivationChange:
            #self.repaint()
            pass

    def closeEvent(self, event):
        reply = QMessageBox.question(self, '提示', "您确认要退出吗?",
                                     QMessageBox.Yes | QMessageBox.No,
                                     QMessageBox.No)
        if reply == QMessageBox.Yes:
            self.exitAll()
        else:
            event.ignore()

    def addSystemTray(self):
        self.tray = QSystemTrayIcon()

        self.icon = Icon.getLogoIcon()
        self.tray.setIcon(self.icon)

        self.tray.activated.connect(self.clickTray)
        self.tray.messageClicked.connect(self.clickTray)
        self.tray_menu = QMenu(QApplication.desktop())
        self.RestoreAction = QAction('主界面', self, triggered=self.restoreAction)
        self.SettingsAction = QAction('设置',
                                      self,
                                      triggered=self.settingsAction)
        self.QuitAction = QAction('退出', self, triggered=self.exitAction)
        self.tray_menu.addAction(self.RestoreAction)
        self.tray_menu.addAction(self.SettingsAction)
        self.tray_menu.addAction(self.QuitAction)
        self.tray.setContextMenu(self.tray_menu)
        self.tray.show()

    def settingsAction(self):
        self.dlgSetting = DlgSetting(self)
        self.dlgSetting.show()

    def exitAction(self):
        self.close()

    def clickTray(self, reason):
        if reason != QSystemTrayIcon.DoubleClick:
            return

        self.restoreAction()

    def restoreAction(self):
        if self.isMaximized():
            self.showMaximized()
        elif self.isFullScreen():
            self.showFullScreen()
        else:
            self.showNormal()

        self.activateWindow()

        #scrollbar
        self.txt_log.verticalScrollBar().setValue(
            self.txt_log.verticalScrollBar().maximum())

    def addContextMenu(self):
        #export log
        self.logContextMenu = QMenu(self)
        self.txt_log.setContextMenuPolicy(Qt.CustomContextMenu)
        self.txt_log.customContextMenuRequested.connect(
            lambda: self.logContextMenu.exec_(QCursor.pos()))
        self.clearLogAction = self.logContextMenu.addAction('清空')
        self.clearLogAction.triggered.connect(lambda: self.txt_log.clear())

        self.emailContextMenu = QMenu(self)
        self.txt_email_list.setContextMenuPolicy(Qt.CustomContextMenu)
        self.txt_email_list.customContextMenuRequested.connect(
            lambda: self.emailContextMenu.exec_(QCursor.pos()))
        self.clearEmailAction = self.emailContextMenu.addAction('清空')
        self.clearEmailAction.triggered.connect(
            lambda: self.txt_email_list.clear())
        self.reloadAction = self.emailContextMenu.addAction('重新加载')
        self.reloadAction.triggered.connect(lambda: self.loadEmail())
        self.loadErrorAction = self.emailContextMenu.addAction('加载错误数据')
        self.loadErrorAction.triggered.connect(lambda: self.loadErrorEmail())

    def setTimer(self):
        if self.time > 0:
            self.lbl_timer.setText(self.convertTime(time.time() - self.time))

    def convertTime(self, raw_time):
        hour = int(raw_time // 3600)
        minute = int((raw_time % 3600) // 60)
        second = int(raw_time % 60)

        return '{:0>2d}:{:0>2d}:{:0>2d}'.format(hour, minute, second)

    def exitAll(self):
        if self.tray:
            self.tray.hide()

        sys.exit()

    #############################################################
    @pyqtSlot()
    def on_btn_bold_clicked(self):
        tmpFormat = self.txt_email_body.currentCharFormat()
        if tmpFormat.fontWeight() == QFont.Bold:
            self.txt_email_body.setFontWeight(QFont.Normal)
        else:
            self.txt_email_body.setFontWeight(QFont.Bold)

        #self.txt_email_body.mergeCurrentCharFormat(tmpFormat)

    @pyqtSlot()
    def on_btn_italic_clicked(self):
        self.txt_email_body.setFontItalic(not self.txt_email_body.fontItalic())

    @pyqtSlot()
    def on_btn_underline_clicked(self):
        self.txt_email_body.setFontUnderline(
            not self.txt_email_body.fontUnderline())

    @pyqtSlot()
    def on_btn_undo_clicked(self):
        self.txt_email_body.undo()

    @pyqtSlot()
    def on_btn_redo_clicked(self):
        self.txt_email_body.redo()

    @pyqtSlot()
    def on_btn_left_clicked(self):
        self.txt_email_body.setAlignment(Qt.AlignLeft)

    @pyqtSlot()
    def on_btn_center_clicked(self):
        self.txt_email_body.setAlignment(Qt.AlignCenter)

    @pyqtSlot()
    def on_btn_right_clicked(self):
        self.txt_email_body.setAlignment(Qt.AlignRight)

    @pyqtSlot()
    def on_btn_size_clicked(self):
        pass

    @pyqtSlot()
    def on_btn_color_clicked(self):
        col = QColorDialog.getColor()
        if col.isValid():
            self.txt_email_body.setTextColor(col)

    @pyqtSlot()
    def on_btn_bgcolor_clicked(self):
        col = QColorDialog.getColor()
        if col.isValid():
            self.txt_email_body.setTextBackgroundColor(col)

    @pyqtSlot(int)
    def on_combo_size_currentIndexChanged(self, x):
        self.txt_email_body.setFontPointSize(int(
            self.combo_size.currentText()))

    @pyqtSlot(int)
    def on_combo_font_currentIndexChanged(self, x):
        self.txt_email_body.setFontFamily(self.combo_font.currentText())

    @pyqtSlot()
    def on_btn_erase_clicked(self):
        self.txt_email_body.setCurrentCharFormat(self.getDefaultTextFormat())

    #############################################################
    def getDefaultTextFormat(self):
        defaultFormat = QTextCharFormat()
        defaultFormat.setFontPointSize(10)

        return defaultFormat

    @pyqtSlot()
    def on_btn_source_clicked(self):
        html = self.txt_email_body.toHtml()
        text = self.txt_email_body.toPlainText()

        defaultFormat = self.getDefaultTextFormat()

        if self.btn_source.isChecked():
            self.txt_email_body.setAcceptRichText(False)

            self.txt_email_body.clear()
            self.txt_email_body.setCurrentCharFormat(defaultFormat)

            self.txt_email_body.setPlainText(html)

            self.editable = False
            self.txt_email_body.setStyleSheet(
                'QTextEdit{background:#434343;color:#fff;}')
        else:
            self.txt_email_body.setAcceptRichText(True)

            self.txt_email_body.clear()
            self.txt_email_body.setCurrentCharFormat(defaultFormat)

            self.txt_email_body.setHtml(text)

            self.editable = True
            self.txt_email_body.setStyleSheet(
                'QTextEdit{background:#fff;color:#000;}')

        #enabled
        list = self.group_editor_toolbar.findChildren(QToolButton)
        for i in range(0, len(list)):
            if list[i].objectName() not in [
                    'btn_source', 'btn_undo', 'btn_redo'
            ]:
                list[i].setEnabled(self.editable)
        list = self.group_editor_toolbar.findChildren(QComboBox)
        for i in range(0, len(list)):
            list[i].setEnabled(self.editable)

    def on_txt_email_body_selectionChanged(self):
        tmpFormat = self.txt_email_body.currentCharFormat()
        if tmpFormat.fontWeight() == QFont.Bold:
            self.btn_bold.setChecked(True)
        else:
            self.btn_bold.setChecked(False)

        if tmpFormat.fontItalic():
            self.btn_italic.setChecked(True)
        else:
            self.btn_italic.setChecked(False)

        if tmpFormat.fontUnderline():
            self.btn_underline.setChecked(True)
        else:
            self.btn_underline.setChecked(False)

        #align
        if self.txt_email_body.alignment() == Qt.AlignLeft:
            self.btn_left.setChecked(True)
        else:
            self.btn_left.setChecked(False)

        if self.txt_email_body.alignment() == Qt.AlignCenter:
            self.btn_center.setChecked(True)
        else:
            self.btn_center.setChecked(False)
        if self.txt_email_body.alignment() == Qt.AlignRight:
            self.btn_right.setChecked(True)
        else:
            self.btn_right.setChecked(False)

        #family

    def on_txt_email_body_undoAvailable(self, bool):
        if bool:
            self.btn_undo.setEnabled(True)
        else:
            self.btn_undo.setEnabled(False)

    def on_txt_email_body_redoAvailable(self, bool):
        if bool:
            self.btn_redo.setEnabled(True)
        else:
            self.btn_redo.setEnabled(False)

    #######################################################################################
    @pyqtSlot()
    def on_btn_test_clicked(self):
        self.on_btn_save_clicked()

        #check upgrade
        t1 = threading.Thread(target=self.mailer.test, args=())
        t1.setDaemon(True)
        t1.start()

        self.btn_test.setDisabled(True)

    @pyqtSlot()
    def on_btn_save_clicked(self):
        if not self.txt_host.text() or not self.txt_user.text():
            self.log({'str': 'SMTP服务器信息不能为空!', 'extra': ['error']})
            return

        if not self.mailer.saveSetting({
                'EmailBody':
                self.txt_email_body.toPlainText() if
                self.btn_source.isChecked() else self.txt_email_body.toHtml(),
                'Mailer': {
                    'host': self.txt_host.text(),
                    'port': self.txt_port.text(),
                    'user': self.txt_user.text(),
                    'password': self.txt_password.text(),
                    'ssl': self.chk_ssl.isChecked(),
                    'fromName': self.txt_fromName.text(),
                    'fromEmail': self.txt_fromEmail.text(),
                    'subject': self.txt_subject.text(),
                    'replyTo': self.txt_reply.text(),
                }
        }):
            return False

    @pyqtSlot()
    def on_btn_start_clicked(self):
        if self.taskInfo['start'] > 0 and self.taskInfo[
                'start'] < self.taskInfo['total']:
            reply = QMessageBox.question(self, '提示',
                                         "任务尚未结束,您确定要重新开始吗?建议您恢复运行!",
                                         QMessageBox.Yes | QMessageBox.No,
                                         QMessageBox.No)
            if reply != QMessageBox.Yes:
                return

        if self.runThread and self.runThread.isAlive():
            self.log({'str': '程序已经在运行中,请等待结束!', 'extra': ['error']})
        else:
            self.runThread = threading.Thread(
                target=self.mailer.startTask,
                args=(self.txt_email_list.toPlainText(), ))
            self.runThread.setDaemon(True)
            self.runThread.start()

            self.btn_start.setDisabled(True)
            self.btn_resume.setDisabled(True)
            self.btn_stop.setDisabled(False)

    @pyqtSlot()
    def on_btn_resume_clicked(self):
        if self.runThread and self.runThread.isAlive():
            self.log({'str': '程序已经在运行中,请等待结束!', 'extra': ['error']})
        else:
            self.runThread = threading.Thread(target=self.mailer.resumeTask,
                                              args=())
            self.runThread.setDaemon(True)
            self.runThread.start()

            self.btn_start.setDisabled(True)
            self.btn_resume.setDisabled(True)
            self.btn_stop.setDisabled(False)

    @pyqtSlot()
    def on_btn_stop_clicked(self):
        #check upgrade
        t1 = threading.Thread(target=self.mailer.stopTask, args=())
        t1.setDaemon(True)
        t1.start()

        self.btn_stop.setDisabled(True)

    @pyqtSlot()
    def on_chk_ssl_clicked(self):
        if self.chk_ssl.isChecked():
            self.txt_port.setText('465')
        else:
            self.txt_port.setText('25')

    @pyqtSlot()
    def on_txt_email_list_textChanged(self):
        c = self.txt_email_list.toPlainText()
        self.lbl_total.setText('总共 %s 条Email' %
                               (format(c.count('\n') + 1, '0,') if c else '0'))

    def log(self, o):
        if o['str']:
            msg = '[' + time.strftime("%H:%M:%S",
                                      time.localtime()) + ']' + o['str']
            self.txt_log.appendPlainText(msg)

        #auto clear
        if self.txt_log.toPlainText().count('\n') > 10000:
            self.txt_log.clear()

        if o['extra']:
            if o['extra'][0] == 'test-end':
                self.btn_test.setDisabled(False)

            if o['extra'][0] in ['task-end', 'task-stop']:
                self.btn_start.setDisabled(False)
                self.btn_resume.setDisabled(False)
                self.btn_stop.setDisabled(True)

            if o['extra'][0] == 'loaded':
                self.txt_email_list.setPlainText(o['extra'][1])

            if o['extra'][0] == 'update':
                self.lbl_result.setText(
                    '成功 %s 条,失败 %s 条' %
                    (format(o['extra'][1], '0,'), format(o['extra'][2], '0,')))

            if o['extra'][0] == 'exit':
                QMessageBox.critical(self, 'Message', o['str'], QMessageBox.Ok,
                                     QMessageBox.Ok)
                sys.exit()
            elif o['extra'][0] == 'check-end' and o['extra'][1]['allowUpgrade']:
                reply = QMessageBox.question(self, '提示', "发现新版本,您确认要更新吗?",
                                             QMessageBox.Yes | QMessageBox.No,
                                             QMessageBox.No)
                if reply == QMessageBox.Yes:
                    mainScript = os.path.basename(
                        sys.argv[0])[0:os.path.basename(sys.argv[0]).rfind('.'
                                                                           )]
                    Util.runScriptFile(self.mailer.updaterFilePath,
                                       ' -s' + mainScript)
                    self.exitAll()

            #progress
            if o['extra'][0] in ['progress']:
                #o['extra'][1]>self.pb_export.value() and self.pb_export.setValue(o['extra'][1])
                self.pb_send.setValue(o['extra'][1])

    def loadData(self):
        self.taskInfo = self.mailer.getTaskInfo()
        self.pb_send.setValue(self.taskInfo['progress'])
        self.lbl_result.setText('成功 %s 条,失败 %s 条' %
                                (format(self.taskInfo['success'], '0,'),
                                 format(self.taskInfo['error'], '0,')))

        self.btn_stop.setDisabled(True)
        self.txt_log.setReadOnly(True)
        '''
        if self.taskInfo['start'] > 0 and self.taskInfo['start']<self.taskInfo['total']:
            self.btn_resume.setDisabled(False)
        else:
            self.btn_resume.setDisabled(True)
        '''

        self.config = self.mailer.config
        if self.config:
            if 'Mailer' in self.config.keys():
                self.txt_host.setText(self.config['Mailer']['host'])
                self.txt_port.setText(str(self.config['Mailer']['port']))
                self.txt_user.setText(self.config['Mailer']['user'])
                self.txt_password.setText(self.config['Mailer']['password'])
                self.chk_ssl.setChecked(self.config['Mailer']['ssl'])
                self.txt_fromName.setText(self.config['Mailer']['fromName'])
                self.txt_fromEmail.setText(self.config['Mailer']['fromEmail'])
                self.txt_subject.setText(self.config['Mailer']['subject'])
                self.txt_reply.setText(
                    self.config['Mailer']['replyTo'] if 'replyTo' in
                    self.config['Mailer'].keys() else '')
        else:
            QMessageBox.critical(self, '错误', '配置文件不存在,请检查!', QMessageBox.Ok,
                                 QMessageBox.Ok)
            os._exit(0)
            return

        #splitter
        self.frame_splitter.setStretchFactor(1, 2)
        self.bottom_splitter.setStretchFactor(1, 2)

        #email
        defaultHtml = self.mailer.getEmailBody()
        self.txt_email_body.setHtml(defaultHtml)
        self.txt_email_body.selectionChanged.connect(
            self.on_txt_email_body_selectionChanged)
        self.txt_email_body.undoAvailable.connect(
            self.on_txt_email_body_undoAvailable)
        self.txt_email_body.redoAvailable.connect(
            self.on_txt_email_body_redoAvailable)
        #self.txt_email_body.setAutoFormatting(QTextEdit.AutoNone)

        #check upgrade
        t1 = threading.Thread(target=self.mailer.checkUpgrade, args=())
        t1.setDaemon(True)
        t1.start()

        #load
        self.loadEmail()

    def loadEmail(self):
        t1 = threading.Thread(target=self.mailer.loadData, args=())
        t1.setDaemon(True)
        t1.start()

    def loadErrorEmail(self):
        t1 = threading.Thread(target=self.mailer.loadErrorData, args=())
        t1.setDaemon(True)
        t1.start()
class MainWindow(QMainWindow):

    restartOdooMenuItem = QtCore.pyqtSignal()
    stopOdooMenuItem = QtCore.pyqtSignal()
    restartPostgreMenuItem = QtCore.pyqtSignal()
    stopPostgreMenuItem = QtCore.pyqtSignal()

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

        if OdooInstallationFound == False:

            if not os.path.isdir(appfolder + '\\Temp'):
                os.makedirs(appfolder + '\\Temp')

            unzipToPath = appfolder + '\\Temp\\Unzip'
            destinationPath = appfolder + "\\Runtime\\Odoo"

            self.downloadFileWorker(mainSettings['github']['downloadPath'])

            if os.path.isfile(appfolder + '\\Temp\\GitHub-Odoo.zip'):
                if not os.path.isdir(appfolder + '\\Temp\\unzip'):
                    os.makedirs(appfolder + '\\Temp\\unzip')

                self.zipFileWorker(appfolder + '\\Temp\\GitHub-Odoo.zip', unzipToPath)
                self.zipWindow.close()

                #Check if the file is etxracted to subfolder - Files on github includes branch name -> Correct this
                countFolders = 0
                extractFolder = None
                for name  in os.listdir(unzipToPath):
                    extractFolder = name
                    countFolders += 1
                if countFolders == 1:
                    shutil.move(unzipToPath + "\\" + extractFolder + "\\", destinationPath)
                self.startCybeSystemsApplication()
        else:
            self.startCybeSystemsApplication()

        if os.path.isdir(appfolder + '\\Temp'):
            shutil.rmtree(appfolder + '\\Temp',ignore_errors=True)

    def zipFileWorker(self,file, destination_folder):
        self.zipWindow = ZipWindow(file, destination_folder)
        self.zipWindow.show()

    def downloadFileWorker(self,url):
        self.httpWin = HttpWindow(url)
        self.httpWin.exec()

    def startCybeSystemsApplication(self):

        #Set Loading TrayIcon
        self.setWindowIcon(QtGui.QIcon(appfolder + '/ressource/icons/icon.png'))

        img = QtGui.QImage()
        img.load(appfolder + '/ressource/icons/icon_loading.png')
        self.pixmap = QtGui.QPixmap.fromImage(img)
        self.icon = QtGui.QIcon()
        self.icon.addPixmap(self.pixmap)
        self.tray = QSystemTrayIcon(self.icon, self)
        self.tray.show()
        traymenu = QMenu()

        #Set Real Icon
        self.tray.hide()
        img = QtGui.QImage()
        img.load(appfolder + '/ressource/icons/icon.png')
        self.pixmap = QtGui.QPixmap.fromImage(img)
        self.icon = QtGui.QIcon()
        self.icon.addPixmap(self.pixmap)
        self.tray = QSystemTrayIcon(self.icon, self)
        self.tray.activated.connect(self.onTrayIconActivated)
        self.tray.setContextMenu(traymenu)
        self.tray.show()

        #Load Stylesheet
        if mainSettings['other']['theme'].lower() != 'default':
            if mainSettings['other']['theme'].lower() == 'steamlike':
                stylesheetFile = open(appfolder + '/ressource/ui/steamlike.stylesheet', "r")
            elif mainSettings['other']['theme'].lower() == 'darkorange':
                stylesheetFile = open(appfolder + '/ressource/ui/darkorange.stylesheet', "r")
            elif mainSettings['other']['theme'].lower() == 'maya':
                stylesheetFile = open(appfolder + '/ressource/ui/maya.stylesheet', "r")
            stylesheet = stylesheetFile.read()
            traymenu.setStyleSheet(stylesheet)
            stylesheetFile.close()

        trayoption_openBrowser_entry = QAction(QtGui.QIcon(self.icon), "Open Odoo", self)
        trayoption_openBrowser_entry.triggered.connect(lambda: webbrowser.open(mainSettings['odoo']['startpage']))
        traymenu.addAction(trayoption_openBrowser_entry)
        trayoption_openBrowser_entry.setIcon(QtGui.QIcon(appfolder + '/ressource/icons/world.png'))

        traymenu.addSeparator()

        tools = traymenu.addMenu('&Odoo')
        tools.setIcon(QtGui.QIcon(appfolder + '/ressource/icons/icon.png'))

        tools_odoo_restart = QAction(QtGui.QIcon(self.icon), "Restart Server", self)
        tools_odoo_restart.triggered.connect(lambda: (self.restartOdooMenuItem.emit()))
        tools.addAction(tools_odoo_restart)
        tools_odoo_restart.setIcon(QtGui.QIcon(appfolder + '/ressource/icons/start_server.png'))

        tools_odoo_stop = QAction(QtGui.QIcon(self.icon), "Stop Server", self)
        tools_odoo_stop.triggered.connect(lambda: (self.stopOdooMenuItem.emit()))
        tools.addAction(tools_odoo_stop)
        tools_odoo_stop.setIcon(QtGui.QIcon(appfolder + '/ressource/icons/stop_server.png'))

        #traymenu.addSeparator()

        tools = traymenu.addMenu('&PostgreSQL')
        tools.setIcon(QtGui.QIcon(appfolder + '/ressource/icons/postgresql.png'))

        tools_postgre_restart = QAction(QtGui.QIcon(self.icon), "Restart Server", self)
        tools_postgre_restart.triggered.connect(lambda: (self.restartPostgreMenuItem.emit()))
        tools.addAction(tools_postgre_restart)
        tools_postgre_restart.setIcon(QtGui.QIcon(appfolder + '/ressource/icons/start_server.png'))

        tools_postgre_stop = QAction(QtGui.QIcon(self.icon), "Stop Server", self)
        tools_postgre_stop.triggered.connect(lambda: (self.stopPostgreMenuItem.emit()))
        tools.addAction(tools_postgre_stop)
        tools_postgre_stop.setIcon(QtGui.QIcon(appfolder + '/ressource/icons/stop_server.png'))

        tools.addSeparator()

        tools_pgadmin = QAction(QtGui.QIcon(self.icon), "pgAdmin III", self)
        tools_pgadmin.triggered.connect(lambda: self.startpgadmin())
        tools.addAction(tools_pgadmin)
        tools_pgadmin.setIcon(QtGui.QIcon(appfolder + '/ressource/icons/cog.png'))

        traymenu.addSeparator()

        trayoption_quickconfig = QAction(QtGui.QIcon(self.icon), "Show Output/Config", self)
        trayoption_quickconfig.triggered.connect(lambda: self.showCommandLineWindowTryOption())
        traymenu.addAction(trayoption_quickconfig)
        trayoption_quickconfig.setIcon(QtGui.QIcon(appfolder + '/ressource/icons/application_osx_terminal.png'))

        traymenu.addSeparator()

        trayoption_exit_entry = QAction(QtGui.QIcon(self.icon), "Exit", self)
        trayoption_exit_entry.triggered.connect(lambda: self.trayOptionExit())
        traymenu.addAction(trayoption_exit_entry)
        trayoption_exit_entry.setIcon(QtGui.QIcon(appfolder + '/ressource/icons/cancel.png'))

        self.tray.showMessage('Odoo is Loading - Please wait','\nLeft click to open CommandWindow\nRight click to open Traymenu')

        self.showCommandLineWindow()

    def startpgadmin(self):
        os.startfile(appfolder + '/Runtime/PostgreSQL/bin/pgAdmin3.exe')

    def showCommandLineWindow(self):
        self.ShowCommandLineWindow=CommandLineWindow(self)
        self.ShowCommandLineWindow.setWindowIcon(QtGui.QIcon(appfolder + '/ressource/icons/icon.png'))
        self.ShowCommandLineWindow.show()
        if mainSettings['other']['minimizeToTray']:
            self.ShowCommandLineWindow.setVisible(False)
        else:
            self.ShowCommandLineWindow.setVisible(True)

    def toggleCommandLineWindow(self):
        if self.ShowCommandLineWindow.isMinimized():
            self.ShowCommandLineWindow.setVisible(True)
            self.ShowCommandLineWindow.showNormal()
            self.ShowCommandLineWindow.activateWindow()
        elif self.ShowCommandLineWindow.isVisible ():
            self.ShowCommandLineWindow.showNormal()
            self.ShowCommandLineWindow.setVisible(False)
        else:
            self.ShowCommandLineWindow.setVisible(True)
            self.ShowCommandLineWindow.showNormal()
            self.ShowCommandLineWindow.activateWindow()

    def showCommandLineWindowTryOption(self):
        self.ShowCommandLineWindow.setVisible(True)
        self.ShowCommandLineWindow.showNormal()
        self.ShowCommandLineWindow.activateWindow()

    def onTrayIconActivated(self,reason):
        if reason == QSystemTrayIcon.DoubleClick:
            pass

        if reason == QSystemTrayIcon.Trigger:
            self.toggleCommandLineWindow()

        if reason == QSystemTrayIcon.Context:
            pass

    def center(self):
        qr = self.frameGeometry()
        cp = QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.topLeft())

    def trayOptionExit(self,msgbox=True):
        if msgbox:
            quit_msg = "Stop all running Server ?"
            reply = QMessageBox.question(self.center(), 'Exit',
                quit_msg, QMessageBox.Yes, QMessageBox.No)
            if reply == QMessageBox.Yes:
                confirmed = True
            else:
                confirmed = False
        else:
            confirmed = True

        if confirmed:
            runtimeSettings["closeMainWindow"] = True
            self.ShowCommandLineWindow.setVisible(True)
            app = QApplication.instance()
            app.closeAllWindows()
            self.tray.hide()
            os._exit(1)
Beispiel #20
0
class SystemTrayIcon(QMainWindow):
    def __init__(self, parent=None):
        super(SystemTrayIcon, self).__init__(parent)
        self.setAttribute(Qt.WA_DeleteOnClose)
        self.settings = QSettings()
        self.language = self.settings.value('Language') or ''
        self.temp_decimal_bool = self.settings.value('Decimal') or False
        # initialize the tray icon type in case of first run: issue#42
        self.tray_type = self.settings.value('TrayType') or 'icon&temp'
        cond = conditions.WeatherConditions()
        self.temporary_city_status = False
        self.conditions = cond.trans
        self.clouds = cond.clouds
        self.wind = cond.wind
        self.wind_dir = cond.wind_direction
        self.wind_codes = cond.wind_codes
        self.inerror = False
        self.tentatives = 0
        self.baseurl = 'http://api.openweathermap.org/data/2.5/weather?id='
        self.accurate_url = 'http://api.openweathermap.org/data/2.5/find?q='
        self.day_forecast_url = (
            'http://api.openweathermap.org/data/2.5/forecast?id=')
        self.wIconUrl = 'http://openweathermap.org/img/w/'
        apikey = self.settings.value('APPID') or ''
        self.appid = '&APPID=' + apikey
        self.forecast_icon_url = self.wIconUrl
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.refresh)
        self.menu = QMenu()
        self.citiesMenu = QMenu(self.tr('Cities'))
        desktops_no_left_click = ['ubuntu', 'budgie-desktop']
        if os.environ.get('DESKTOP_SESSION') in desktops_no_left_click:
            # Missing left click on Unity environment issue 63
            self.panelAction = QAction(
                QCoreApplication.translate(
                    "Tray context menu", "Toggle Window",
                    "Open/closes the application window"), self)
            self.panelAction.setIcon(QIcon(':/panel'))
            self.menu.addAction(self.panelAction)
            self.panelAction.triggered.connect(self.showpanel)
        self.tempCityAction = QAction(self.tr('&Temporary city'), self)
        self.refreshAction = QAction(self.tr('&Update'), self)
        self.settingsAction = QAction(self.tr('&Settings'), self)
        self.aboutAction = QAction(self.tr('&About'), self)
        self.exitAction = QAction(self.tr('Exit'), self)
        self.exitAction.setIcon(QIcon(':/exit'))
        self.aboutAction.setIcon(QIcon(':/info'))
        self.refreshAction.setIcon(QIcon(':/refresh'))
        self.settingsAction.setIcon(QIcon(':/configure'))
        self.tempCityAction.setIcon(QIcon(':/tempcity'))
        self.citiesMenu.setIcon(QIcon(':/bookmarks'))
        self.menu.addAction(self.settingsAction)
        self.menu.addAction(self.refreshAction)
        self.menu.addMenu(self.citiesMenu)
        self.menu.addAction(self.tempCityAction)
        self.menu.addAction(self.aboutAction)
        self.menu.addAction(self.exitAction)
        self.settingsAction.triggered.connect(self.config)
        self.exitAction.triggered.connect(qApp.quit)
        self.refreshAction.triggered.connect(self.manual_refresh)
        self.aboutAction.triggered.connect(self.about)
        self.tempCityAction.triggered.connect(self.tempcity)
        self.systray = QSystemTrayIcon()
        self.systray.setContextMenu(self.menu)
        self.systray.activated.connect(self.activate)
        self.systray.setIcon(QIcon(':/noicon'))
        self.systray.setToolTip(self.tr('Searching weather data...'))
        self.notification = ''
        self.hPaTrend = 0
        self.trendCities_dic = {}
        self.notifier_id = ''
        self.temp_trend = ''
        self.systray.show()
        # The dictionnary has to be intialized here. If there is an error
        # the program couldn't become functionnal if the dictionnary is
        # reinitialized in the weatherdata method
        self.weatherDataDico = {}
        # The traycolor has to be initialized here for the case when we cannot
        # reach the tray method (case: set the color at first time usage)
        self.traycolor = ''
        self.refresh()

    def icon_loading(self):
        self.gif_loading = QMovie(":/loading")
        self.gif_loading.frameChanged.connect(self.update_gif)
        self.gif_loading.start()

    def update_gif(self):
        gif_frame = self.gif_loading.currentPixmap()
        self.systray.setIcon(QIcon(gif_frame))

    def manual_refresh(self):
        self.tentatives = 0
        self.refresh()

    def cities_menu(self):
        # Don't add the temporary city in the list
        if self.temporary_city_status:
            return
        self.citiesMenu.clear()
        cities = self.settings.value('CityList') or []
        cities_trans = self.settings.value('CitiesTranslation') or '{}'
        cities_trans_dict = eval(cities_trans)
        if type(cities) is str:
            cities = eval(cities)
        try:
            current_city = (self.settings.value('City') + '_' +
                            self.settings.value('Country') + '_' +
                            self.settings.value('ID'))
        except:
            # firsttime run,if clic cancel in settings without any city configured
            pass
        # Prevent duplicate entries
        try:
            city_toadd = cities.pop(cities.index(current_city))
        except:
            city_toadd = current_city
        finally:
            cities.insert(0, city_toadd)
        # If we delete all cities it results to a '__'
        if (cities is not None and cities != '' and cities != '[]'
                and cities != ['__']):
            if type(cities) is not list:
                # FIXME sometimes the list of cities is read as a string (?)
                # eval to a list
                cities = eval(cities)
            # Create the cities list menu
            for city in cities:
                if city in cities_trans_dict:
                    city = cities_trans_dict[city]
                action = QAction(city, self)
                action.triggered.connect(partial(self.changecity, city))
                self.citiesMenu.addAction(action)
        else:
            self.empty_cities_list()

    @pyqtSlot(str)
    def changecity(self, city):
        cities_list = self.settings.value('CityList')
        cities_trans = self.settings.value('CitiesTranslation') or '{}'
        self.cities_trans_dict = eval(cities_trans)
        logging.debug('Cities' + str(cities_list))
        if cities_list is None:
            self.empty_cities_list()
        if type(cities_list) is not list:
            # FIXME some times is read as string (?)
            cities_list = eval(cities_list)
        prev_city = (self.settings.value('City') + '_' +
                     self.settings.value('Country') + '_' +
                     self.settings.value('ID'))
        citytoset = ''
        # Set the chosen city as the default
        for town in cities_list:
            if town == self.find_city_key(city):
                ind = cities_list.index(town)
                citytoset = cities_list[ind]
                citytosetlist = citytoset.split('_')
                self.settings.setValue('City', citytosetlist[0])
                self.settings.setValue('Country', citytosetlist[1])
                self.settings.setValue('ID', citytosetlist[2])
                if prev_city not in cities_list:
                    cities_list.append(prev_city)
                self.settings.setValue('CityList', str(cities_list))
                logging.debug(cities_list)
        self.refresh()

    def find_city_key(self, city):
        for key, value in self.cities_trans_dict.items():
            if value == city:
                return key
        return city

    def empty_cities_list(self):
        self.citiesMenu.addAction(self.tr('Empty list'))

    def refresh(self):
        self.inerror = False
        self.window_visible = False
        self.systray.setIcon(QIcon(':/noicon'))
        if hasattr(self, 'overviewcity'):
            # if visible, it has to ...remain visible
            # (try reason) Prevent C++ wrapper error
            try:
                if not self.overviewcity.isVisible():
                    # kills the reference to overviewcity
                    # in order to be refreshed
                    self.overviewcity.close()
                    del self.overviewcity
                else:
                    self.overviewcity.close()
                    self.window_visible = True
            except:
                pass
        self.systray.setToolTip(self.tr('Fetching weather data ...'))
        self.city = self.settings.value('City') or ''
        self.id_ = self.settings.value('ID') or None
        if self.id_ is None:
            # Clear the menu, no cities configured
            self.citiesMenu.clear()
            self.empty_cities_list()
            # Sometimes self.overviewcity is in namespace but deleted
            try:
                self.overviewcity.close()
            except:
                e = sys.exc_info()[0]
                logging.error('Error closing overviewcity: ' + str(e))
                pass
            self.timer.singleShot(2000, self.firsttime)
            self.id_ = ''
            self.systray.setToolTip(self.tr('No city configured'))
            return
        # A city has been found, create the cities menu now
        self.cities_menu()
        self.country = self.settings.value('Country') or ''
        self.unit = self.settings.value('Unit') or 'metric'
        self.beaufort = self.settings.value('Beaufort') or 'False'
        self.suffix = ('&mode=xml&units=' + self.unit + self.appid)
        self.interval = int(self.settings.value('Interval') or 30) * 60 * 1000
        self.timer.start(self.interval)
        self.update()

    def firsttime(self):
        self.temp = ''
        self.wIcon = QPixmap(':/noicon')
        self.systray.showMessage(
            'meteo-qt:\n',
            self.tr('No city has been configured yet.') + '\n' +
            self.tr('Right click on the icon and click on Settings.'))

    def update(self):
        if hasattr(self, 'downloadThread'):
            if self.downloadThread.isRunning():
                logging.debug('remaining thread...')
                return
        logging.debug('Update...')
        self.icon_loading()
        self.wIcon = QPixmap(':/noicon')
        self.downloadThread = Download(self.wIconUrl, self.baseurl,
                                       self.day_forecast_url, self.id_,
                                       self.suffix)
        self.downloadThread.wimage['PyQt_PyObject'].connect(self.makeicon)
        self.downloadThread.finished.connect(self.tray)
        self.downloadThread.xmlpage['PyQt_PyObject'].connect(self.weatherdata)
        self.downloadThread.day_forecast_rawpage.connect(self.dayforecast)
        self.downloadThread.uv_signal.connect(self.uv)
        self.downloadThread.error.connect(self.error)
        self.downloadThread.done.connect(self.done, Qt.QueuedConnection)
        self.downloadThread.start()

    def uv(self, value):
        self.uv_coord = value

    def dayforecast(self, data):
        if type(data) == dict:
            self.json_data_bool = True
        else:
            self.json_data_bool = False
        self.dayforecast_data = data

    def instance_overviewcity(self):
        try:
            self.inerror = False
            if hasattr(self, 'overviewcity'):
                logging.debug('Deleting overviewcity instance...')
                del self.overviewcity
            self.overviewcity = overview.OverviewCity(
                self.weatherDataDico, self.wIcon, self.dayforecast_data,
                self.json_data_bool, self.unit, self.forecast_icon_url,
                self.uv_coord, self.hPaTrend, self.temp_trend, self)
            self.overviewcity.closed_status_dialogue.connect(
                self.remove_object)
        except:
            self.inerror = True
            e = sys.exc_info()[0]
            logging.error('Error: ' + str(e))
            logging.debug('Try to create the city overview...\nAttempts: ' +
                          str(self.tentatives))
            return 'error'

    def remove_object(self):
        del self.overviewcity

    def done(self, done):
        if done == 0:
            self.inerror = False
        elif done == 1:
            self.inerror = True
            logging.debug('Trying to retrieve data ...')
            self.timer.singleShot(10000, self.try_again)
            return
        if hasattr(self, 'updateicon'):
            # Keep a reference of the image to update the icon in overview
            self.wIcon = self.updateicon
        if hasattr(self, 'dayforecast_data'):
            if hasattr(self, 'overviewcity'):
                try:
                    # Update also the overview dialog if open
                    if self.overviewcity.isVisible():
                        # delete dialog to prevent memory leak
                        self.overviewcity.close()
                        self.instance_overviewcity()
                        self.overview()
                except:
                    # if the dialogue has been closed by the 'X' button
                    # remove the delelted window object from memory
                    # RuntimeError: wrapped C/C++ object of type OverviewCity has been deleted
                    self.remove_object()
                    self.instance_overviewcity()
            elif self.window_visible is True:
                self.instance_overviewcity()
                self.overview()
            else:
                self.inerror = True
                self.try_create_overview()
        else:
            self.try_again()

    def try_create_overview(self):
        logging.debug('Tries to create overview :' + str(self.tentatives))
        instance = self.instance_overviewcity()
        if instance == 'error':
            self.inerror = True
            self.refresh()
        else:
            self.tentatives = 0
            self.inerror = False
            self.tooltip_weather()

    def try_again(self):
        self.nodata_message()
        logging.debug('Attempts: ' + str(self.tentatives))
        self.tentatives += 1
        self.timer.singleShot(5000, self.refresh)

    def nodata_message(self):
        nodata = QCoreApplication.translate(
            "Tray icon", "Searching for weather data...",
            "Tooltip (when mouse over the icon")
        self.systray.setToolTip(nodata)
        self.notification = nodata

    def error(self, error):
        logging.error('Error:\n' + str(error))
        self.nodata_message()
        self.timer.start(self.interval)
        self.inerror = True

    def makeicon(self, data):
        image = QImage()
        image.loadFromData(data)
        self.wIcon = QPixmap(image)
        # Keep a reference of the image to update the icon in overview
        self.updateicon = self.wIcon

    def weatherdata(self, tree):
        if self.inerror:
            return
        self.tempFloat = tree[1].get('value')
        self.temp = ' ' + str(round(float(self.tempFloat))) + '°'
        self.temp_decimal = '{0:.1f}'.format(float(self.tempFloat)) + '°'
        self.meteo = tree[8].get('value')
        meteo_condition = tree[8].get('number')
        try:
            self.meteo = self.conditions[meteo_condition]
        except:
            logging.debug('Cannot find localisation string for'
                          'meteo_condition:' + str(meteo_condition))
            pass
        clouds = tree[5].get('name')
        clouds_percent = tree[5].get('value') + '%'
        try:
            clouds = self.clouds[clouds]
            clouds = self.conditions[clouds]
        except:
            logging.debug('Cannot find localisation string for clouds:' +
                          str(clouds))
            pass
        wind = tree[4][0].get('name').lower()
        try:
            wind = self.wind[wind]
            wind = self.conditions[wind]
        except:
            logging.debug('Cannot find localisation string for wind:' +
                          str(wind))
            pass
        try:
            wind_codes = tree[4][2].get('code')
            wind_dir_value = tree[4][2].get('value')
            wind_dir = tree[4][2].get('name')
        except:
            wind_codes = tree[4][1].get('code')
            wind_dir_value = tree[4][1].get('value')
            wind_dir = tree[4][1].get('name')
        try:
            wind_codes = self.wind_codes[wind_codes]
        except:
            logging.debug('Cannot find localisation string for wind_codes:' +
                          str(wind_codes))
            pass
        try:
            wind_dir = self.wind_dir[tree[4][2].get('code')]
        except:
            logging.debug('Cannot find localisation string for wind_dir:' +
                          str(wind_dir))
            pass
        self.city_weather_info = (self.city + ' ' + self.country + ' ' +
                                  self.temp_decimal + ' ' + self.meteo)
        self.tooltip_weather()
        self.notification = self.city_weather_info
        self.weatherDataDico['Id'] = self.id_
        self.weatherDataDico['City'] = self.city
        self.weatherDataDico['Country'] = self.country
        self.weatherDataDico['Temp'] = self.tempFloat + '°'
        self.weatherDataDico['Meteo'] = self.meteo
        self.weatherDataDico['Humidity'] = (tree[2].get('value'),
                                            tree[2].get('unit'))
        self.weatherDataDico['Wind'] = (tree[4][0].get('value'), wind,
                                        str(int(float(wind_dir_value))),
                                        wind_codes, wind_dir)
        self.weatherDataDico['Clouds'] = (clouds_percent + ' ' + clouds)
        self.weatherDataDico['Pressure'] = (tree[3].get('value'),
                                            tree[3].get('unit'))
        self.weatherDataDico['Humidity'] = (tree[2].get('value'),
                                            tree[2].get('unit'))
        self.weatherDataDico['Sunrise'] = tree[0][2].get('rise')
        self.weatherDataDico['Sunset'] = tree[0][2].get('set')
        rain_value = tree[7].get('value')
        if rain_value == None:
            rain_value = ''
        self.weatherDataDico['Precipitation'] = (tree[7].get('mode'),
                                                 rain_value)
        if self.id_ not in self.trendCities_dic:
            # dict {'id': 'hPa', 'T°'}
            self.trendCities_dic[self.id_] = [''] * 2
        # hPa self.temp_trend
        pressure = int(self.weatherDataDico['Pressure'][0])
        if self.id_ in self.trendCities_dic and self.trendCities_dic[
                self.id_][0] is not '':
            self.hPaTrend = pressure - int(self.trendCities_dic[self.id_][0])
        else:
            self.hPaTrend = 0
        self.trendCities_dic[self.id_][0] = pressure
        # Temperature trend
        self.notifier()

    def tooltip_weather(self):
        # Creation of the tray tootltip
        trans_cities = self.settings.value('CitiesTranslation') or '{}'
        trans_cities_dict = eval(trans_cities)
        city = self.city + '_' + self.country + '_' + self.id_
        if city in trans_cities_dict:
            self.city_weather_info = (trans_cities_dict[city] + ' ' +
                                      self.temp_decimal + ' ' + self.meteo)
        else:
            self.city_weather_info = (self.city + ' ' + self.country + ' ' +
                                      self.temp_decimal + ' ' + self.meteo)

    def tray(self):
        temp_decimal = eval(self.settings.value('Decimal') or 'False')
        try:
            if temp_decimal:
                temp_tray = self.temp_decimal
            else:
                temp_tray = self.temp
        except:
            # First time launch
            return
        if self.inerror or not hasattr(self, 'temp'):
            logging.critical('Cannot paint icon!')
            if hasattr(self, 'overviewcity'):
                try:
                    # delete dialog to prevent memory leak
                    self.overviewcity.close()
                except:
                    pass
            return
        try:
            self.gif_loading.stop()
        except:
            # In first time run the gif is not animated
            pass
        logging.debug('Paint tray icon...')
        # Place empty.png here to initialize the icon
        # don't paint the T° over the old value
        icon = QPixmap(':/empty')
        self.traycolor = self.settings.value('TrayColor') or ''
        self.fontsize = self.settings.value('FontSize') or '18'
        self.tray_type = self.settings.value('TrayType') or 'icon&temp'
        pt = QPainter()
        pt.begin(icon)
        if self.tray_type != 'temp':
            pt.drawPixmap(0, -12, 64, 64, self.wIcon)
        self.bold_set = self.settings.value('Bold') or 'False'
        if self.bold_set == 'True':
            br = QFont.Bold
        else:
            br = QFont.Normal
        pt.setFont(QFont('sans-sertif', int(self.fontsize), weight=br))
        pt.setPen(QColor(self.traycolor))
        if self.tray_type == 'icon&temp':
            pt.drawText(icon.rect(), Qt.AlignBottom | Qt.AlignCenter,
                        str(temp_tray))
        if self.tray_type == 'temp':
            pt.drawText(icon.rect(), Qt.AlignCenter, str(temp_tray))
        pt.end()
        if self.tray_type == 'icon':
            self.systray.setIcon(QIcon(self.wIcon))
        else:
            self.systray.setIcon(QIcon(icon))
        if self.notifier_settings():
            if (self.temp_trend != ''
                    or self.trendCities_dic[self.id_][1] == ''
                    or self.id_ != self.notifier_id):
                try:
                    if not self.overviewcity.isVisible():
                        self.systray.showMessage(
                            'meteo-qt', self.notification + self.temp_trend)
                except AttributeError:
                    logging.debug('!!! OverviewCity in garbages, try again...')
                    self.systray.showMessage(
                        'meteo-qt', self.notification + self.temp_trend)
                    self.try_again()
                    return
        self.notifier_id = self.id_
        self.restore_city()
        self.tentatives = 0
        self.tooltip_weather()
        logging.info('Actual weather status for: ' + self.notification)

    def notifier_settings(self):
        notifier = self.settings.value('Notifications') or 'True'
        notifier = eval(notifier)
        if notifier:
            return True
        else:
            return False

    def notifier(self):
        ''' The notification is being shown:
        On a city change or first launch or if the temperature changes
        The notification is not shown if is turned off from the settings.
        The tray tooltip is set here '''
        self.temp_trend = ''
        temp = float(self.tempFloat)
        # if self.notifier_settings():
        if (self.id_ in self.trendCities_dic
                and self.trendCities_dic[self.id_][1] is not ''):
            if temp > float(self.trendCities_dic[self.id_][1]):
                self.temp_trend = " "
            elif temp < float(self.trendCities_dic[self.id_][1]):
                self.temp_trend = " "
        self.trendCities_dic[self.id_][1] = temp
        self.systray.setToolTip(self.city_weather_info + self.temp_trend)

    def restore_city(self):
        if self.temporary_city_status:
            logging.debug('Restore the default settings (city)' +
                          'Forget the temporary city...')
            for e in ('ID', self.id_2), ('City', self.city2), ('Country',
                                                               self.country2):
                self.citydata(e)
            self.temporary_city_status = False

    def showpanel(self):
        self.activate(3)

    def activate(self, reason):
        if reason == 3:
            if self.inerror or self.id_ is None or self.id_ == '':
                return
            try:
                if hasattr(self,
                           'overviewcity') and self.overviewcity.isVisible():
                    self.overviewcity.hide()
                else:
                    self.overviewcity.hide()
                    # If dialog closed by the "X"
                    self.done(0)
                    self.overview()
            except:
                self.done(0)
                self.overview()
        elif reason == 1:
            self.menu.popup(QCursor.pos())

    def overview(self):
        if self.inerror or len(self.weatherDataDico) == 0:
            return
        self.overviewcity.show()

    def config_save(self):
        logging.debug('Config saving...')
        city = self.settings.value('City'),
        id_ = self.settings.value('ID')
        country = self.settings.value('Country')
        unit = self.settings.value('Unit')
        beaufort = self.settings.value('Beaufort')
        traycolor = self.settings.value('TrayColor')
        tray_type = self.settings.value('TrayType')
        fontsize = self.settings.value('FontSize')
        bold_set = self.settings.value('Bold')
        language = self.settings.value('Language')
        decimal = self.settings.value('Decimal')
        self.appid = '&APPID=' + self.settings.value('APPID') or ''
        if language != self.language and language is not None:
            self.systray.showMessage(
                'meteo-qt:',
                QCoreApplication.translate(
                    "System tray notification",
                    "The application has to be restarted to apply the language setting",
                    ''))
            self.language = language
        # Check if update is needed
        if traycolor is None:
            traycolor = ''
        if (self.traycolor != traycolor or self.tray_type != tray_type
                or self.fontsize != fontsize or self.bold_set != bold_set
                or decimal != self.temp_decimal):
            self.tray()
        if (city[0] == self.city and id_ == self.id_
                and country == self.country and unit == self.unit
                and beaufort == self.beaufort):
            return
        else:
            logging.debug('Apply changes from settings...')
            self.refresh()

    def config(self):
        dialog = settings.MeteoSettings(self.accurate_url, self.appid, self)
        dialog.applied_signal.connect(self.config_save)
        if dialog.exec_() == 1:
            self.config_save()
            logging.debug('Update Cities menu...')
            self.cities_menu()

    def tempcity(self):
        # Prevent to register a temporary city
        # This happen when a temporary city is still loading
        self.restore_city()
        dialog = searchcity.SearchCity(self.accurate_url, self.appid, self)
        self.id_2, self.city2, self.country2 = (self.settings.value('ID'),
                                                self.settings.value('City'),
                                                self.settings.value('Country'))
        dialog.id_signal[tuple].connect(self.citydata)
        dialog.city_signal[tuple].connect(self.citydata)
        dialog.country_signal[tuple].connect(self.citydata)
        if dialog.exec_():
            self.temporary_city_status = True
            self.systray.setToolTip(self.tr('Fetching weather data...'))
            self.refresh()

    def citydata(self, what):
        self.settings.setValue(what[0], what[1])
        logging.debug('write ' + str(what[0]) + ' ' + str(what[1]))

    def about(self):
        title = self.tr("""<b>meteo-qt</b> v{0}
            <br/>License: GPLv3
            <br/>Python {1} - Qt {2} - PyQt {3} on {4}""").format(
            __version__, platform.python_version(), QT_VERSION_STR,
            PYQT_VERSION_STR, platform.system())
        image = ':/logo'
        text = self.tr(
            """<p>Author: Dimitrios Glentadakis <a href="mailto:[email protected]">[email protected]</a>
                        <p>A simple application showing the weather status
                        information on the system tray.
                        <p>Website: <a href="https://github.com/dglent/meteo-qt">
                        https://github.com/dglent/meteo-qt</a>
                        <br/>Data source: <a href="http://openweathermap.org/">
                        OpenWeatherMap</a>.
                        <br/>This software uses icons from the
                        <a href="http://www.kde.org/">Oxygen Project</a>.
                        <p>To translate meteo-qt in your language or contribute to
                        current translations, you can use the
                        <a href="https://www.transifex.com/projects/p/meteo-qt/">
                        Transifex</a> platform.
                        <p>If you want to report a dysfunction or a suggestion,
                        feel free to open an issue in <a href="https://github.com/dglent/meteo-qt/issues">
                        github</a>.""")

        contributors = QCoreApplication.translate(
            "About dialog", """
            Pavel Fric<br/>
            [cs] Czech translation
            <p>Jürgen <a href="mailto:[email protected]">[email protected]</a><br/>
            [de] German translation
            <p>Peter Mattern <a href="mailto:[email protected]">[email protected]</a><br/>
            [de] German translation, Project
            <p>Dimitrios Glentadakis <a href="mailto:[email protected]">[email protected]</a><br/>
            [el] Greek translation
            <p> juancarlospaco <a href="mailto:[email protected]">[email protected]</a><br/>
            [es] Spanish translation, Project
            <p>Ozkar L. Garcell <a href="mailto:[email protected]">[email protected]</a><br/>
            [es] Spanish translation
            <p>Laurene Albrand <a href="mailto:[email protected]">[email protected]</a><br/>
            [fr] French translation
            <p>Rémi Verschelde <a href="mailto:[email protected]">[email protected]</a><br/>
            [fr] French translation, Project
            <p>Daniel Napora <a href="mailto:[email protected]">[email protected]</a><br/>
            Tomasz Przybył <a href="mailto:[email protected]">[email protected]</a><br/>
            [pl] Polish translation
            <p>Artem Vorotnikov <a href="mailto:[email protected]">[email protected]</a><br/>
            [ru] Russian translation
            <p>Atilla Öntaş <a href="mailto:[email protected]">[email protected]</a><br/>
            [tr] Turkish translation
            <p>Yuri Chornoivan <a href="mailto:[email protected]">[email protected]</a><br/>
            [uk] Ukrainian translation
            <p>You-Cheng Hsieh <a href="mailto:[email protected]">[email protected]</a><br/>
            [zh_TW] Chinese (Taiwan) translation
            <p>pmav99<br/>
            Project""", "List of contributors")

        dialog = about_dlg.AboutDialog(title, text, image, contributors, self)
        dialog.exec_()
Beispiel #21
0
class ZhaoChaFrame(QWidget):
    game_hwnd = 0  # 游戏的窗体句柄
    bgpixmap = None
    pixmap = None
    my_visible = False

    # GAME_CLASS = "#32770"
    # GAME_TITLE = "大家来找茬"
    GAME_CLASS = "MozillaWindowClass"
    GAME_TITLE = "游戏全屏 - Mozilla Firefox"

    WIDTH = 500  # 大图宽
    HEIGHT = 450  # 大图高
    ANCHOR_LEFT_X = 8  # 左图X起点
    ANCHOR_RIGHT_X = 517  # 右图X起点
    ANCHOR_Y = 190  # Y起点
    CLIP_WIDTH = 10
    CLIP_HEIGHT = 10
    DIFF_LIMIT = 2000  # 差异阀值,两片图形对比差异差异超过此值视为不一样

    # 查找区域
    # 大图版 1024 x 738
    BIG_WIDTH = 498  # 大图宽
    BIG_HEIGHT = 448  # 大图高
    BIG_ANCHOR_LEFT_X = 8  # 左图X起点
    BIG_ANCHOR_RIGHT_X = 517  # 右图X起点
    BIG_ANCHOR_Y = 190  # Y起点
    BIG_CLIP_WIDTH = 10
    BIG_CLIP_HEIGHT = 10
    BIG_DIFF_LIMIT = 2000  # 差异阀值,两片图形对比差异差异超过此值视为不一样

    # 小图版 800 x 600
    SMALL_WIDTH = 381  # 大图宽
    SMALL_HEIGHT = 286  # 大图高
    SMALL_ANCHOR_LEFT_X = 10  # 左图X起点
    SMALL_ANCHOR_RIGHT_X = 403  # 右图X起点
    SMALL_ANCHOR_Y = 184  # Y起点
    SMALL_CLIP_WIDTH = 10
    SMALL_CLIP_HEIGHT = 10
    SMALL_DIFF_LIMIT = 2000  # 差异阀值,两片图形对比差异差异超过此值视为不一样


    # 存储对比结果 二位数组,映射每一个基块
    result = []

    clock = 0

    def __init__(self, parent=None):
        QWidget.__init__(self)
        # QWidget.__init__(self, parent, flags=Qt.FramelessWindowHint | Qt.Window | Qt.WindowStaysOnTopHint)
        # 设置背景透明,这样按钮不会太难看
        # self.setAttribute(Qt.WA_TranslucentBackground, True)

        # 这些属性让程序不在任务栏出现标题
        # self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.Popup | Qt.Tool)

        # 托盘
        self.icon = QIcon(":\icon.png")

        self.trayIcon = QSystemTrayIcon(self)
        self.trayIcon.setIcon(self.icon)
        self.trayIcon.setToolTip(u"QQ找茬助手")
        self.trayIcon.show()
        self.trayIcon.showMessage(u"QQ找茬助手", u"QQ找茬助手已经待命,进入游戏即可激活")

        self.action = QAction(u"退出QQ找茬助手", self, triggered=sys.exit)
        self.menu = QMenu(self)
        self.menu.addAction(self.action)
        self.trayIcon.setContextMenu(self.menu)

        # 定时探测游戏
        self.stick_timer = QTimer()
        self.stick_timer.start(20)
        # self.connect(self.stick_timer, SIGNAL('timeout()'), self.StickTarget)

        # 这个QLabel其实就是中间绘图区的背景
        self.label = QLabel(self)

        self.pixmap = QPixmap(self.size())

        # 刷新按钮
        self.btn_compare = QPushButton(self)
        self.btn_compare.setText(u"对比")
        # self.connect(self.btn_compare, SIGNAL('clicked()'), self.Compare)

        # 开关
        self.btn_toggle = QPushButton(self)
        self.btn_toggle.setText(u"擦除")
        # self.connect(self.btn_toggle, SIGNAL('clicked()'), self.Clear)

        self.HideMe()


    def StickTarget(self):
        '''让本窗体粘附在目标窗体上'''
        # 找到目标窗口句柄
        game_hwnd = win32gui.FindWindow(self.GAME_CLASS, self.GAME_TITLE)
        if game_hwnd == 0:
            if self.my_visible:
                # 如果游戏窗体不可见,比如最小化、关闭了,隐藏自己
                self.HideMe()
            return
        else:
            self.game_hwnd = game_hwnd

        try:
            window_rect = win32gui.GetWindowRect(self.game_hwnd)
            if self.game_hwnd == win32gui.GetForegroundWindow() and window_rect[0] > 0:
                point = QPoint(window_rect[0], window_rect[1])
                size = QSize(window_rect[2] - window_rect[0], window_rect[3] - window_rect[1])

                if self.size() != size:
                    self.SyncSize(size)

                if self.pos() != point:
                    self.move(point)

                if not self.my_visible:
                    self.ShowMe()
                    # self.FindAndShow()
            elif win32gui.GetForegroundWindow() != int(self.winId()) and self.my_visible:
                # 游戏窗口隐藏时,同时隐藏找碴助手
                self.HideMe()
        except:
            if self.my_visible:
                self.HideMe()


    def paintEvent(self, event):
        if not self.my_visible:
            self.move(-2000, -2000)

        self.pixmap.fill()
        p = QPainter(self.pixmap)
        p.setPen(QPen(QBrush(QColor(0, 0, 0)), 2))

        for row in range(len(self.result)):
            for col in range(len(self.result[0])):
                if self.result[row][col] != 0:
                    # 定一个基点,避免算数太难看
                    base_l_x = self.ANCHOR_LEFT_X + self.CLIP_WIDTH * col
                    base_r_x = self.ANCHOR_RIGHT_X + self.CLIP_WIDTH * col
                    base_y = self.ANCHOR_Y + self.CLIP_HEIGHT * row

                    if row == 0 or self.result[row - 1][col] == 0:
                        # 如果是第一行,或者上面的格子为空,画一条上边
                        p.drawLine(base_l_x, base_y, base_l_x + self.CLIP_WIDTH, base_y)
                        p.drawLine(base_r_x, base_y, base_r_x + self.CLIP_WIDTH, base_y)
                    if row == len(self.result) - 1 or self.result[row + 1][col] == 0:
                        # 如果是最后一行,或者下面的格子为空,画一条下边
                        p.drawLine(base_l_x, base_y + self.CLIP_HEIGHT, base_l_x + self.CLIP_WIDTH,
                                   base_y + self.CLIP_HEIGHT)
                        p.drawLine(base_r_x, base_y + self.CLIP_HEIGHT, base_r_x + self.CLIP_WIDTH,
                                   base_y + self.CLIP_HEIGHT)
                    if col == 0 or self.result[row][col - 1] == 0:
                        # 如果是第一列,或者左边的格子为空,画一条左边
                        p.drawLine(base_l_x, base_y, base_l_x, base_y + self.CLIP_HEIGHT)
                        p.drawLine(base_r_x, base_y, base_r_x, base_y + self.CLIP_HEIGHT)
                    if col == len(self.result[0]) - 1 or self.result[row][col + 1] == 0:
                        # 如果是第一列,或者右边的格子为空,画一条右边
                        p.drawLine(base_l_x + self.CLIP_WIDTH, base_y, base_l_x + self.CLIP_WIDTH,
                                   base_y + self.CLIP_HEIGHT)
                        p.drawLine(base_r_x + self.CLIP_WIDTH, base_y, base_r_x + self.CLIP_WIDTH,
                                   base_y + self.CLIP_HEIGHT)
        p.fillRect(self.btn_compare.geometry(), QBrush(QColor(0, 0, 0)))
        p.fillRect(self.btn_toggle.geometry(), QBrush(QColor(0, 0, 0)))
        self.setMask(QBitmap(self.pixmap))


    def Clear(self):
        self.ResetResult()
        self.repaint()


    def ShowMe(self):
        self.my_visible = True
        self.repaint()


    def HideMe(self):
        self.my_visible = False
        self.repaint()


    def Compare(self):
        # 对比
        if self.stick_timer.isActive():
            self.FindAndShow()
        else:
            self.stick_timer.start()


    def ResetResult(self):
        # 清楚之前计算的结果
        self.result = [[0 for a in range(0, self.WIDTH / self.CLIP_WIDTH)] for b in
                       range(0, self.HEIGHT / self.CLIP_HEIGHT)]


    def SyncSize(self, size):
        self.resize(size)

        if self.width() == 1024 and self.height() == 738:
            self.WIDTH = self.BIG_WIDTH
            self.HEIGHT = self.BIG_HEIGHT
            self.ANCHOR_LEFT_X = self.BIG_ANCHOR_LEFT_X
            self.ANCHOR_RIGHT_X = self.BIG_ANCHOR_RIGHT_X
            self.ANCHOR_Y = self.BIG_ANCHOR_Y
            self.CLIP_WIDTH = self.BIG_CLIP_WIDTH
            self.CLIP_HEIGHT = self.BIG_CLIP_HEIGHT
            self.DIFF_LIMIT = self.BIG_DIFF_LIMIT
            self.btn_compare.setGeometry(611, 650, 100, 40)
            self.btn_toggle.setGeometry(715, 650, 100, 40)
        elif self.width() == 800 and self.height() == 600:
            self.WIDTH = self.SMALL_WIDTH
            self.HEIGHT = self.SMALL_HEIGHT
            self.ANCHOR_LEFT_X = self.SMALL_ANCHOR_LEFT_X
            self.ANCHOR_RIGHT_X = self.SMALL_ANCHOR_RIGHT_X
            self.ANCHOR_Y = self.SMALL_ANCHOR_Y
            self.CLIP_WIDTH = self.SMALL_CLIP_WIDTH
            self.CLIP_HEIGHT = self.SMALL_CLIP_HEIGHT
            self.DIFF_LIMIT = self.SMALL_DIFF_LIMIT
            self.btn_compare.setGeometry(472, 496, 100, 40)
            self.btn_toggle.setGeometry(576, 496, 100, 40)
        else:
            print("游戏窗体大小匹配错误")
            return

        self.pixmap = QPixmap(self.size())
        self.bgpixmap = QPixmap(self.width(), self.HEIGHT)
        self.bgpixmap.fill(QColor(0, 0, 255))
        self.label.setGeometry(0, self.ANCHOR_Y, self.width(), self.HEIGHT)
        self.label.setPixmap(self.bgpixmap)


    def FindAndShow(self):
        # 截取游戏窗口内容
        self.my_visible = True
        self.DebugTime("init")

        ## 裁剪得到左右的内容图片
        win32gui.ShowWindow(self.game_hwnd, win32con.SW_RESTORE)  # 强行显示界面后才好截图
        win32gui.SetForegroundWindow(self.game_hwnd)  # 将游戏窗口提到最前
        src_image = ImageGrab.grab((self.x(), self.y() + self.ANCHOR_Y, self.x() + self.ANCHOR_RIGHT_X + self.WIDTH,
                                    self.y() + self.ANCHOR_Y + self.HEIGHT))
        left_box = (self.ANCHOR_LEFT_X, 0, self.ANCHOR_LEFT_X + self.WIDTH, self.HEIGHT)
        right_box = (self.ANCHOR_RIGHT_X, 0, self.ANCHOR_RIGHT_X + self.WIDTH, self.HEIGHT)
        image_left = src_image.crop(left_box)
        image_right = src_image.crop(right_box)
        # image_left.show()
        # image_right.show()
        self.DebugTime("拆图完成")

        # 将左右大图裁剪成多个小图分别进行对比
        self.ResetResult()
        for col in range(0, self.WIDTH / self.CLIP_WIDTH):
            for row in range(0, self.HEIGHT / self.CLIP_HEIGHT):
                clip_box = (col * self.CLIP_WIDTH, row * self.CLIP_HEIGHT, (col + 1) * self.CLIP_WIDTH,
                            (row + 1) * self.CLIP_HEIGHT)
                clip_image_left = image_left.crop(clip_box)
                clip_image_right = image_right.crop(clip_box)
                clip_diff = self.compare(clip_image_left, clip_image_right)

                if sum(clip_diff) > self.DIFF_LIMIT:
                    self.result[row][col] = 1

        self.DebugTime("对比")
        self.repaint()
        self.DebugTime("绘制")
        # print "----------------------"
        # for i in range(len(self.result)):        # Y轴循环
        #for j in range(len(self.result[i])):    # X轴循环
        #print self.result[i][j],
        #print
        #print "----------------------"


    def compare(self, image_a, image_b):
        '''返回两图的差异值
        返回两图红绿蓝差值万分比之和'''
        histogram_a = image_a.histogram()
        histogram_b = image_b.histogram()
        if len(histogram_a) != 768 or len(histogram_b) != 768:
            return None

        red_a = 0
        red_b = 0
        for i in range(0, 256):
            red_a += histogram_a[i + 0] * i
            red_b += histogram_b[i + 0] * i
        diff_red = 0
        if red_a + red_b > 0:
            diff_red = abs(red_a - red_b) * 10000 / max(red_a, red_b)

        green_a = 0
        green_b = 0
        for i in range(0, 256):
            green_a += histogram_a[i + 256] * i
            green_b += histogram_b[i + 256] * i
        diff_green = 0
        if green_a + green_b > 0:
            diff_green = abs(green_a - green_b) * 10000 / max(green_a, green_b)

        blue_a = 0
        blue_b = 0
        for i in range(0, 256):
            blue_a += histogram_a[i + 512] * i
            blue_b += histogram_b[i + 512] * i
        diff_blue = 0
        if blue_a + blue_b > 0:
            diff_blue = abs(blue_a - blue_b) * 10000 / max(blue_a, blue_b)

        return diff_red, diff_green, diff_blue

    def DebugTime(self, text=""):
        return

        if self.clock > 0:
            print
            time.clock() - self.clock, text
        self.clock = time.clock()
Beispiel #22
0
class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        settings = QSettings()
        self.setup_trayicon()
        self.setup_variables()
        self.setup_ui()
        self.setup_connections()
        self.show()

    def setup_variables(self):
        settings = QSettings()
        self.configParser = ConfigParser()
        self.configParser.read("config.ini")
        self.workEndTime = QTime(
            settings.value(self.configParser["SETTINGS"]["WorkHours"], 0),
            settings.value(self.configParser["SETTINGS"]["WorkMinutes"], 25),
            settings.value(self.configParser["SETTINGS"]["WorkSeconds"], 0))
        self.restEndTime = QTime(
            settings.value(self.configParser["SETTINGS"]["RestHours"], 0),
            settings.value(self.configParser["SETTINGS"]["RestMinutes"], 5),
            settings.value(self.configParser["SETTINGS"]["RestSeconds"], 0))
        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

    def setup_connections(self):
        """ Create button connections """
        self.startButton.clicked.connect(self.start_timer)
        self.startButton.clicked.connect(
            lambda: self.startButton.setDisabled(True))
        self.startButton.clicked.connect(
            lambda: self.pauseButton.setDisabled(False))
        self.startButton.clicked.connect(
            lambda: self.resetButton.setDisabled(False))
        self.pauseButton.clicked.connect(self.pause_timer)
        self.pauseButton.clicked.connect(
            lambda: self.startButton.setDisabled(False))
        self.pauseButton.clicked.connect(
            lambda: self.pauseButton.setDisabled(True))
        self.pauseButton.clicked.connect(
            lambda: self.resetButton.setDisabled(False))
        self.pauseButton.clicked.connect(
            lambda: self.startButton.setText("continue"))
        self.resetButton.clicked.connect(self.reset_timer)
        self.resetButton.clicked.connect(
            lambda: self.startButton.setDisabled(False))
        self.resetButton.clicked.connect(
            lambda: self.pauseButton.setDisabled(True))
        self.resetButton.clicked.connect(
            lambda: self.resetButton.setDisabled(True))
        self.resetButton.clicked.connect(
            lambda: self.startButton.setText("start"))
        self.acceptTaskButton.pressed.connect(self.insert_task)
        self.deleteTaskButton.pressed.connect(self.delete_task)
        """ Create spinbox  connections """
        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.repetitionsSpinBox.valueChanged.connect(
            self.update_max_repetitions)
        """ Create combobox connections """
        self.modeComboBox.currentTextChanged.connect(self.update_current_mode)
        """ Create tablewidget connections """
        self.tasksTableWidget.cellDoubleClicked.connect(
            self.mark_task_as_finished)

    def setup_ui(self):
        self.size_policy = sizePolicy = QSizePolicy(QSizePolicy.Expanding,
                                                    QSizePolicy.Expanding)
        """ Create tabwidget """
        self.tabWidget = QTabWidget()
        """ Create tab widgets """
        timerWidget = self.setup_timer_tab()
        tasksWidget = self.setup_tasks_tab()
        statisticsWidget = self.setup_statistics_tab()
        """ add tab widgets to tabwidget"""
        self.timerTab = self.tabWidget.addTab(timerWidget,
                                              QIcon("icons/timer.png"),
                                              "Timer")
        self.tasksTab = self.tabWidget.addTab(tasksWidget,
                                              QIcon("icons/tasks.png"),
                                              "Tasks")
        self.statisticsTab = self.tabWidget.addTab(
            statisticsWidget, QIcon("icons/statistics.png"), "Statistics")
        """ Set mainwindows central widget """
        self.setCentralWidget(self.tabWidget)

    def setup_timer_tab(self):
        settings = QSettings()
        self.timerContainer = QWidget(self)
        self.timerContainerLayout = QVBoxLayout(self.timerContainer)
        self.timerContainer.setLayout(self.timerContainerLayout)
        """ Create work groupbox"""
        self.workGroupBox = QGroupBox("Work")
        self.workGroupBoxLayout = QHBoxLayout(self.workGroupBox)
        self.workGroupBox.setLayout(self.workGroupBoxLayout)
        self.workHoursSpinBox = QSpinBox(
            minimum=0,
            maximum=24,
            value=settings.value(self.configParser["SETTINGS"]["WorkHours"],
                                 0),
            suffix="h",
            sizePolicy=self.size_policy)
        self.workMinutesSpinBox = QSpinBox(
            minimum=0,
            maximum=60,
            value=settings.value(self.configParser["SETTINGS"]["WorkMinutes"],
                                 25),
            suffix="m",
            sizePolicy=self.size_policy)
        self.workSecondsSpinBox = QSpinBox(
            minimum=0,
            maximum=60,
            value=settings.value(self.configParser["SETTINGS"]["WorkSeconds"],
                                 0),
            suffix="s",
            sizePolicy=self.size_policy)
        """ Create rest groupbox"""
        self.restGroupBox = QGroupBox("Rest")
        self.restGroupBoxLayout = QHBoxLayout(self.restGroupBox)
        self.restGroupBox.setLayout(self.restGroupBoxLayout)
        self.restHoursSpinBox = QSpinBox(
            minimum=0,
            maximum=24,
            value=settings.value(self.configParser["SETTINGS"]["RestHours"],
                                 0),
            suffix="h",
            sizePolicy=self.size_policy)
        self.restMinutesSpinBox = QSpinBox(
            minimum=0,
            maximum=60,
            value=settings.value(self.configParser["SETTINGS"]["RestMinutes"],
                                 5),
            suffix="m",
            sizePolicy=self.size_policy)
        self.restSecondsSpinBox = QSpinBox(
            minimum=0,
            maximum=60,
            value=settings.value(self.configParser["SETTINGS"]["RestSeconds"],
                                 0),
            suffix="s",
            sizePolicy=self.size_policy)
        self.restGroupBoxLayout.addWidget(self.restHoursSpinBox)
        self.restGroupBoxLayout.addWidget(self.restMinutesSpinBox)
        self.restGroupBoxLayout.addWidget(self.restSecondsSpinBox)
        """ Create other groupbox"""
        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)
        """ Create timer groupbox"""
        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)
        """ Create pause, start and reset buttons"""
        self.buttonContainer = QWidget()
        self.buttonContainerLayout = QHBoxLayout(self.buttonContainer)
        self.buttonContainer.setLayout(self.buttonContainerLayout)
        self.startButton = self.make_button("start", disabled=False)
        self.resetButton = self.make_button("reset")
        self.pauseButton = self.make_button("pause")
        """ Add widgets to container """
        self.workGroupBoxLayout.addWidget(self.workHoursSpinBox)
        self.workGroupBoxLayout.addWidget(self.workMinutesSpinBox)
        self.workGroupBoxLayout.addWidget(self.workSecondsSpinBox)
        self.timerContainerLayout.addWidget(self.workGroupBox)
        self.timerContainerLayout.addWidget(self.restGroupBox)
        self.timerContainerLayout.addWidget(self.otherGroupBox)
        self.timerContainerLayout.addWidget(self.lcdDisplayGroupBox)
        self.buttonContainerLayout.addWidget(self.pauseButton)
        self.buttonContainerLayout.addWidget(self.startButton)
        self.buttonContainerLayout.addWidget(self.resetButton)
        self.timerContainerLayout.addWidget(self.buttonContainer)
        return self.timerContainer

    def setup_tasks_tab(self):
        settings = QSettings()
        """ Create vertical tasks container """
        self.tasksWidget = QWidget(self.tabWidget)
        self.tasksWidgetLayout = QVBoxLayout(self.tasksWidget)
        self.tasksWidget.setLayout(self.tasksWidgetLayout)
        """ Create horizontal input container """
        self.inputContainer = QWidget()
        self.inputContainer.setFixedHeight(50)
        self.inputContainerLayout = QHBoxLayout(self.inputContainer)
        self.inputContainerLayout.setContentsMargins(0, 0, 0, 0)
        self.inputContainer.setLayout(self.inputContainerLayout)
        """ Create text edit """
        self.taskTextEdit = QTextEdit(
            placeholderText="Describe your task briefly.",
            undoRedoEnabled=True)
        """ Create vertical buttons container """
        self.inputButtonContainer = QWidget()
        self.inputButtonContainerLayout = QVBoxLayout(
            self.inputButtonContainer)
        self.inputButtonContainerLayout.setContentsMargins(0, 0, 0, 0)
        self.inputButtonContainer.setLayout(self.inputButtonContainerLayout)
        """ Create buttons """
        self.acceptTaskButton = QToolButton(icon=QIcon("icons/check.png"))
        self.deleteTaskButton = QToolButton(icon=QIcon("icons/trash.png"))
        """ Create tasks tablewidget """
        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(self.configParser["SETTINGS"]["Tasks"], []))
        """ Add widgets to container widgets """
        self.inputButtonContainerLayout.addWidget(self.acceptTaskButton)
        self.inputButtonContainerLayout.addWidget(self.deleteTaskButton)
        self.inputContainerLayout.addWidget(self.taskTextEdit)
        self.inputContainerLayout.addWidget(self.inputButtonContainer)
        self.tasksWidgetLayout.addWidget(self.inputContainer)
        self.tasksWidgetLayout.addWidget(self.tasksTableWidget)
        return self.tasksWidget

    def setup_statistics_tab(self):
        """ Create statistics container """
        self.statisticsContainer = QWidget()
        self.statisticsContainerLayout = QVBoxLayout(self.statisticsContainer)
        self.statisticsContainer.setLayout(self.statisticsContainerLayout)
        """ Create work time groupbox """
        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)
        """ Create rest time groupbox """
        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)
        """ Create total time groupbox """
        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)
        """ Add widgets to container """
        self.statisticsContainerLayout.addWidget(
            self.statisticsTotalTimeGroupBox)
        self.statisticsContainerLayout.addWidget(
            self.statisticsWorkTimeGroupBox)
        self.statisticsContainerLayout.addWidget(
            self.statisticsRestTimeGroupBox)
        return self.statisticsContainer

    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 leaveEvent(self, event):
        super(MainWindow, self).leaveEvent(event)
        self.tasksTableWidget.clearSelection()

    def closeEvent(self, event):
        super(MainWindow, self).closeEvent(event)
        settings = QSettings()
        settings.setValue(self.configParser["SETTINGS"]["WorkHours"],
                          self.workHoursSpinBox.value())
        settings.setValue(self.configParser["SETTINGS"]["WorkMinutes"],
                          self.workMinutesSpinBox.value())
        settings.setValue(self.configParser["SETTINGS"]["WorkSeconds"],
                          self.workSecondsSpinBox.value())
        settings.setValue(self.configParser["SETTINGS"]["RestHours"],
                          self.restHoursSpinBox.value())
        settings.setValue(self.configParser["SETTINGS"]["RestMinutes"],
                          self.restMinutesSpinBox.value())
        settings.setValue(self.configParser["SETTINGS"]["RestSeconds"],
                          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(self.configParser["SETTINGS"]["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 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 exit(self):
        self.close()
        app = QApplication.instance()
        if app:
            app.quit()

    def onActivate(self, reason):
        if reason == QSystemTrayIcon.Trigger:
            self.show()
class Qt4SysTrayIcon:
    def __init__(self):
        self.snapshots = snapshots.Snapshots()
        self.config = self.snapshots.config
        self.decode = None

        if len(sys.argv) > 1:
            if not self.config.setCurrentProfile(sys.argv[1]):
                logger.warning("Failed to change Profile_ID %s"
                               %sys.argv[1], self)

        self.qapp = qt4tools.createQApplication(self.config.APP_NAME)
        translator = qt4tools.translator()
        self.qapp.installTranslator(translator)
        self.qapp.setQuitOnLastWindowClosed(False)

        import icon
        self.icon = icon
        self.qapp.setWindowIcon(icon.BIT_LOGO)

        self.status_icon = QSystemTrayIcon(icon.BIT_LOGO)
        #self.status_icon.actionCollection().clear()
        self.contextMenu = QMenu()

        self.menuProfileName = self.contextMenu.addAction(_('Profile: "%s"') % self.config.profileName())
        qt4tools.setFontBold(self.menuProfileName)
        self.contextMenu.addSeparator()

        self.menuStatusMessage = self.contextMenu.addAction(_('Done'))
        self.menuProgress = self.contextMenu.addAction('')
        self.menuProgress.setVisible(False)
        self.contextMenu.addSeparator()

        self.btnPause = self.contextMenu.addAction(icon.PAUSE, _('Pause snapshot process'))
        action = lambda: os.kill(self.snapshots.pid(), signal.SIGSTOP)
        self.btnPause.triggered.connect(action)

        self.btnResume = self.contextMenu.addAction(icon.RESUME, _('Resume snapshot process'))
        action = lambda: os.kill(self.snapshots.pid(), signal.SIGCONT)
        self.btnResume.triggered.connect(action)
        self.btnResume.setVisible(False)

        self.btnStop = self.contextMenu.addAction(icon.STOP, _('Stop snapshot process'))
        self.btnStop.triggered.connect(self.onBtnStop)
        self.contextMenu.addSeparator()

        self.btnDecode = self.contextMenu.addAction(icon.VIEW_SNAPSHOT_LOG, _('decode paths'))
        self.btnDecode.setCheckable(True)
        self.btnDecode.setVisible(self.config.snapshotsMode() == 'ssh_encfs')
        self.btnDecode.toggled.connect(self.onBtnDecode)

        self.openLog = self.contextMenu.addAction(icon.VIEW_LAST_LOG, _('View Last Log'))
        self.openLog.triggered.connect(self.onOpenLog)
        self.startBIT = self.contextMenu.addAction(icon.BIT_LOGO, _('Start BackInTime'))
        self.startBIT.triggered.connect(self.onStartBIT)
        self.status_icon.setContextMenu(self.contextMenu)

        self.pixmap = icon.BIT_LOGO.pixmap(24)
        self.progressBar = QProgressBar()
        self.progressBar.setMinimum(0)
        self.progressBar.setMaximum(100)
        self.progressBar.setValue(0)
        self.progressBar.setTextVisible(False)
        self.progressBar.resize(24, 6)
        self.progressBar.render(self.pixmap, sourceRegion = QRegion(0, -14, 24, 6), flags = QWidget.RenderFlags(QWidget.DrawChildren))

        self.first_error = self.config.notify()
        self.popup = None
        self.last_message = None

        self.timer = QTimer()
        self.timer.timeout.connect(self.updateInfo)

        self.ppid = os.getppid()

    def prepairExit(self):
        self.timer.stop()

        if not self.status_icon is None:
            self.status_icon.hide()
            self.status_icon = None

        if not self.popup is None:
            self.popup.deleteLater()
            self.popup = None

        self.qapp.processEvents()

    def run(self):
        self.status_icon.show()
        self.timer.start(500)

        logger.info("[qt4systrayicon] begin loop", self)

        self.qapp.exec_()

        logger.info("[qt4systrayicon] end loop", self)

        self.prepairExit()

    def updateInfo(self):
        if not tools.processAlive(self.ppid):
            self.prepairExit()
            self.qapp.exit(0)
            return

        paused = tools.processPaused(self.snapshots.pid())
        self.btnPause.setVisible(not paused)
        self.btnResume.setVisible(paused)

        message = self.snapshots.takeSnapshotMessage()
        if message is None and self.last_message is None:
            message = (0, _('Working...'))

        if not message is None:
            if message != self.last_message:
                self.last_message = message
                if self.decode:
                    message = (message[0], self.decode.log(message[1]))
                self.menuStatusMessage.setText('\n'.join(tools.wrapLine(message[1],\
                                                                         size = 80,\
                                                                         delimiters = '',\
                                                                         new_line_indicator = '') \
                                                                       ))
                self.status_icon.setToolTip(message[1])

        pg = progress.ProgressFile(self.config)
        if pg.fileReadable():
            pg.load()
            percent = pg.intValue('percent')
            if percent != self.progressBar.value():
                self.progressBar.setValue(percent)
                self.progressBar.render(self.pixmap, sourceRegion = QRegion(0, -14, 24, 6), flags = QWidget.RenderFlags(QWidget.DrawChildren))
                self.status_icon.setIcon(QIcon(self.pixmap))

            self.menuProgress.setText(' | '.join(self.getMenuProgress(pg)))
            self.menuProgress.setVisible(True)
        else:
            self.status_icon.setIcon(self.icon.BIT_LOGO)
            self.menuProgress.setVisible(False)


    def getMenuProgress(self, pg):
        d = (('sent',   _('Sent:')), \
             ('speed',  _('Speed:')),\
             ('eta',    _('ETA:')))
        for key, txt in d:
            value = pg.strValue(key, '')
            if not value:
                continue
            yield txt + ' ' + value

    def onStartBIT(self):
        profileID = self.config.currentProfile()
        cmd = ['backintime-qt4',]
        if not profileID == '1':
            cmd += ['--profile-id', profileID]
        proc = subprocess.Popen(cmd)

    def onOpenLog(self):
        dlg = logviewdialog.LogViewDialog(self, systray = True)
        dlg.decode = self.decode
        dlg.cbDecode.setChecked(self.btnDecode.isChecked())
        dlg.exec_()

    def onBtnDecode(self, checked):
        if checked:
            self.decode = encfstools.Decode(self.config)
            self.last_message = None
            self.updateInfo()
        else:
            self.decode = None

    def onBtnStop(self):
        os.kill(self.snapshots.pid(), signal.SIGKILL)
        self.btnStop.setEnabled(False)
        self.btnPause.setEnabled(False)
        self.btnResume.setEnabled(False)
        self.snapshots.setTakeSnapshotMessage(0, 'Snapshot terminated')
Beispiel #24
0
class NomnsParse(QApplication):
    """Application Control."""

    def __init__(self, *args):
        super().__init__(*args)

        # Plugin support
        self.plugins = PluginManager(self)
        self.plugins.discover_plugins(enable_all=config.data['general']['enable_plugins'])
        # End plugin support

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

        self.plugins.hook(Plugin.on_app_start, self)

    def _load_parsers(self):
        self._parsers = [
            parsers.Maps(),
            parsers.Spells()
        ]
        for parser in self._parsers:
            self.plugins.hook(Plugin.on_parser_load, parser)
            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, charname = 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, charname)

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

        # Plugin support for adding menu items
        if self.plugins.has_plugins():
            menu.addSeparator()
        plugin_options = self.plugins.prepare_plugin_menu(menu)
        self.plugins.hook(Plugin.on_menu_display, menu)
        # End plugin support

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

        # Show the menu
        action = menu.exec_(QCursor.pos())

        # Plugin support for handling menu actions
        if plugin_options:
            plugin_options(action)
        self.plugins.hook(Plugin.on_menu_click, action)
        # End plugin support

        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)

            # Plugin support
            self.plugins.hook(Plugin.on_app_quit, self)

            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 #25
0
class Sansimera(QMainWindow):
    def __init__(self, parent=None):
        super(Sansimera, self).__init__(parent)
        self.settings = QSettings()
        self.timer = QTimer(self)
        self.timer_reminder = QTimer(self)
        self.timer_reminder.timeout.connect(self.reminder_tray)
        interval = self.settings.value('Interval') or '1'
        if interval != '0':
            self.timer_reminder.start(int(interval) * 60 * 60 * 1000)
        self.tentatives = 0
        self.gui()
        self.lista = []
        self.lista_pos = 0
        self.eortazontes_shown = False
        self.eortazontes_names = ''

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

    def interval_namedays(self):
        dialog = sansimera_reminder.Reminder(self)
        dialog.applied_signal['QString'].connect(self.reminder)
        if dialog.exec_() == 1:
            print('Apply namedays reminder interval...')

    def reminder(self, time):
        self.settings.setValue('Interval', time)
        if time != '0':
            self.timer_reminder.start(int(time) * 60 * 60 * 1000)
            print('Reminder = ' + time + ' hour(s)')
        else:
            print('Reminder = None')

    def nextItem(self):
        if len(self.lista) >= 1:
            self.browser.clear()
            if self.lista_pos != len(self.lista)-1:
                self.lista_pos += 1
            else:
                self.lista_pos = 0
            self.browser.append(self.lista[self.lista_pos])
            self.browser.moveCursor(QTextCursor.Start)
        else:
            return

    def previousItem(self):
        if len(self.lista) >= 1:
            self.browser.clear()
            if self.lista_pos == 0:
                self.lista_pos = len(self.lista)-1
            else:
                self.lista_pos -= 1
            self.browser.append(self.lista[self.lista_pos])
            self.browser.moveCursor(QTextCursor.Start)
        else:
            return

    def refresh(self):
        try:
            if self.workThread.isRunning():
                return
        except AttributeError:
            pass
        self.menu.hide()
        self.browser.clear()
        self.lista = []
        self.systray.setToolTip('Σαν σήμερα...')
        self.browser.append('Λήψη...')
        self.tentatives = 0
        self.eortazontes_shown = False
        self.download()

    def activate(self, reason):
        self.menu.hide()
        state = self.isVisible()
        if reason == 3:
            if state:
                self.hide()
                return
            else:
                self.show()
                return
        if reason == 1:
            self.menu.hide()
            self.menu.popup(QCursor.pos())

    def download(self):
        self.workThread = WorkThread()
        self.workThread.online_signal[bool].connect(self.status)
        self.workThread.finished.connect(self.window)
        self.workThread.event['QString'].connect(self.addlist)
        self.workThread.names['QString'].connect(self.nameintooltip)
        self.workThread.start()

    def addlist(self, text):
        self.lista.append(text)

    def status(self, status):
        self.status_online = status

    def reminder_tray(self):
        text = self.eortazontes_names.replace('<br/>', '\n')
        urltexts = re.findall('(<a [\S]+php">)', text)
        urltexts.extend(['</a>', '<p>', '<div>'])
        show_notifier_text = text
        for i in urltexts:
            show_notifier_text = show_notifier_text.replace(i, '')
        show_notifier_text = show_notifier_text.replace('\n\n', '\n')
        show_notifier_text = show_notifier_text.replace('www.eortologio.gr)', 'www.eortologio.gr)\n')
        self.systray.showMessage('Εορτάζουν:\n', show_notifier_text)
        self.systray.setToolTip('Εορτάζουν:\n' + show_notifier_text)

    def nameintooltip(self, text):
        self.eortazontes_names = text
        for i in ['<br/>', '<div>']:
            text = text.replace(i, '')
        self.eortazontes_in_window = text
        if self.eortazontes_shown:
            return
        self.reminder_tray()
        self.eortazontes_shown = True

    def window(self):
        self.lista.append('<div class=""></div>' + self.eortazontes_in_window)
        if self.status_online:
            self.browser.clear()
            self.browser.append(self.lista[0])
            self.lista_pos = 0
            return
        else:
            if self.tentatives == 10:
                return
            self.timer.singleShot(5000, self.refresh)
            self.tentatives += 1

    def closeEvent(self, event):
        self.settings.setValue("MainWindow/State", self.saveState())

    def about(self):
        self.menu.hide()
        QMessageBox.about(self, "Εφαρμογή «Σαν σήμερα...»",
                        """<b>sansimera-qt</b> v{0}
                        <p>Δημήτριος Γλενταδάκης <a href="mailto:[email protected]">[email protected]</a>
                        <br/>Ιστοσελίδα: <a href="https://github.com/dglent/sansimera-qt">
                        github sansimera-qt</a>
                        <p>Εφαρμογή πλαισίου συστήματος για την προβολή
                        <br/>των γεγονότων από την ιστοσελίδα <a href="http://www.sansimera.gr">
                        www.sansimera.gr</a><br/>
                        Πηγή εορτολογίου: <a href="http://www.eortologio.gr">
                        www.eortologio.gr</a>, <a href="http://www.synaxari.gr">
                        www.synaxari.gr</a>
                        <p>Άδεια χρήσης: GPLv3 <br/>Python {1} - Qt {2} - PyQt {3} σε {4}""".format(
                        __version__, platform.python_version(),
                        QT_VERSION_STR, PYQT_VERSION_STR, platform.system()))
Beispiel #26
0
class Window(QMainWindow):
    """
    Main GUI class for application
    """

    def __init__(self):
        QWidget.__init__(self)

        # loaind ui from xml
        uic.loadUi(os.path.join(DIRPATH, 'app.ui'), self)

        # self.show_msgbox("Info", "Lan Messenger")

        self.users = {}

        self.host = socket.gethostname()
        self.ip = get_ip_address()

        # button event handlers
        self.btnRefreshBuddies.clicked.connect(self.refreshBuddies)
        self.btnSend.clicked.connect(self.sendMsg)

        self.lstBuddies.currentItemChanged.connect(
                self.on_buddy_selection_changed)

        self.msg_manager = MessageManager()
        self.msg_sender = MessageSender(self.host, self.ip)

        self.message_listener = MessageListener()
        self.message_listener.message_received.connect(self.handle_messages)

        self.send_IAI()

        self.setup_tray_menu()

        # setting up handlers for menubar actions
        self.actionAbout.triggered.connect(self.about)
        self.actionExit.triggered.connect(qApp.quit)
        self.actionPreferences.triggered.connect(self.show_preferences)

    def about(self):
        print("about")
        ad = AboutDialog()
        ad.display()

    def show_preferences(self):
        print("preferences")
        pd = PrefsDialog()
        pd.display()

    def setup_tray_menu(self):

        # setting up QSystemTrayIcon
        self.tray_icon = QSystemTrayIcon(self)
        self.tray_icon.setIcon(
                self.style().standardIcon(QStyle.SP_ComputerIcon))

        # tray actions
        show_action = QAction("Show", self)
        quit_action = QAction("Exit", self)
        hide_action = QAction("Hide", self)

        # action handlers
        show_action.triggered.connect(self.show)
        hide_action.triggered.connect(self.hide)
        quit_action.triggered.connect(qApp.quit)

        # tray menu
        tray_menu = QMenu()
        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()

    def closeEvent(self, event):
        event.ignore()
        self.hide()
        self.tray_icon.showMessage(
            "PyLanMessenger",
            "PyLanMessenger was minimized to Tray",
            QSystemTrayIcon.Information,
            2000
        )

    def handle_messages(self, data):
        log.debug("UI handling message: %s" % data)
        pkt = Packet()
        pkt.json_to_obj(data)
        if pkt.op == "IAI":
            self.handle_IAI(pkt.ip, pkt.host)
        if pkt.op == "MTI":
            self.handle_MTI(pkt.ip, pkt.host)
        if pkt.op == "TCM":
            self.handle_TCM(pkt.ip, pkt.host, pkt.msg)

    def send_IAI(self):
        # broadcast a message that IAI - "I Am In" the n/w
        pkt = Packet(op="IAI", ip=self.ip, host=self.host).to_json()
        self.msg_sender.send_broadcast_message(pkt)

    def send_MTI(self):
        # broadcast a message that MTI - "Me Too In" the n/w
        pkt = Packet(op="MTI", ip=self.ip, host=self.host).to_json()
        self.msg_sender.send_broadcast_message(pkt)

    def handle_IAI(self, ip, host):
        """
        handle "I am In" packet

        reply with MTI for IAI
        me too in when other says "i am in"
        """
        self.send_MTI()

        if host not in self.users:
            print("adding host", host)
            self.users[host] = ip
            self.lstBuddies.addItem(str(host))

    def handle_MTI(self, ip, host):
        """
        handle Me Too In packet
        """

        if host not in self.users:
            self.users[host] = ip
            self.lstBuddies.addItem(str(host))

    def handle_TCM(self, ip, host, msg):
        self.add_chat_msg(ip, host, "%s: %s" % (host, msg))

    def refreshBuddies(self):
        self.lstBuddies.clear()
        self.users = {}
        self.send_IAI()

    def sendMsg(self):
        try:
            receiver_host = self.lstBuddies.currentItem().text()
        except:
            log.warning("no host found from selection")
            return

        msg = self.teMsg.toPlainText()

        receiver_ip = self.users[receiver_host]

        # sending msg to receiver
        self.msg_sender.send_to_ip(receiver_ip, receiver_host, msg.strip())

        # adding my message in chat area in UI
        self.add_chat_msg(
                receiver_ip, receiver_host, "%s: %s" % (self.host, msg))

        # cleaning up textbox for typed message in UI
        self.teMsg.setText("")

    def add_chat_msg(self, ip, other_host, msg):

        self.msg_manager.add_chat_msg(ip, other_host, msg)

        # showing msg in UI
        self.teMsgsList.append(msg)

    def on_buddy_selection_changed(self):
        if self.lstBuddies.count() == 0:
            return

        # no buddy selected
        if not self.lstBuddies.currentItem():
            return

        sel_user = self.lstBuddies.currentItem().text()
        log.debug("You selected buddy is: \"%s\"" % sel_user)

        self.teMsgsList.clear()

        msgs = self.msg_manager.get_message_for_user(sel_user)
        for m in msgs:
            self.teMsgsList.append(m)

    def show_msgbox(self, title, text):
        """
        Function for showing error/info message box
        """
        msg = QMessageBox()
        msg.setIcon(QMessageBox.Information)
        msg.setText(text)
        msg.setWindowTitle(title)
        msg.setStandardButtons(QMessageBox.Ok)
        retval = msg.exec_()
Beispiel #27
0
class MainApp(QMainWindow, Ui_MainWindow):

    _translate = QCoreApplication.translate
    tab_list = []
    # TODO - add dutch translation files

    def __init__(self, isolated, *args):
        super(MainApp, self).__init__(*args)
        Lumberjack.info('spawning the <<< MainApp >>> hey says: I am the Main man here see!')
        self.load_settings()
        self.setup_tray(isolated)
        self.dbhelper = DbHelper()

        self.setupUi(self)
        self.iconize_controls()
        self.load_styling()

        self.tabWidget = QTabWidget(self.centralwidget)
        self.tabWidget.setTabsClosable(True)
        self.tabWidget.setMovable(True)
        self.tabWidget.setTabBarAutoHide(True)
        self.tabWidget.setObjectName("tabWidget")
        self.verticalLayout.addWidget(self.tabWidget)

        builderLabel = QLabel('made by: MazeFX Solutions')
        self.statusbar.addPermanentWidget(builderLabel)

        self.menuPAT.triggered.connect(self.handle_menu_event)
        self.menuLists.triggered.connect(self.handle_menu_event)
        self.menuHelp.triggered.connect(self.handle_menu_event)
        self.tabWidget.tabCloseRequested.connect(self.close_tab)
        self.actionHome.trigger()

        self._retranslateUi(self)

    def iconize_controls(self):
        Lumberjack.info('< MainApp > - -> (iconize_controls)')

        homeIcon = qta.icon('fa.home', color='white')
        self.actionHome.setIcon(homeIcon)
        wrenchIcon = qta.icon('fa.wrench', color='white')
        self.actionSettings.setIcon(wrenchIcon)

        bankIcon = qta.icon('fa.bank', color='white')
        self.actionListBankAccounts.setIcon(bankIcon)

        contractIcon = QIcon(':/app_icons/rc/handshake_icon.svg')
        self.actionListContracts.setIcon(contractIcon)

        atIcon = qta.icon('fa.at', color='white')
        self.actionListEmailAddresses.setIcon(atIcon)

        envelopeIcon = qta.icon('fa.envelope', color='white')
        self.actionListLetters.setIcon(envelopeIcon)

        relationIcon = qta.icon('fa.group', color='white')
        self.actionListRelations.setIcon(relationIcon)

        transactionIcon = qta.icon('fa.money', color='white')
        self.actionListTransactions.setIcon(transactionIcon)

        userIcon = qta.icon('fa.user', color='white')
        self.actionListUsers.setIcon(userIcon)

        helpIcon = qta.icon('fa.question', color='white')
        self.actionHelp.setIcon(helpIcon)

        aboutIcon = qta.icon('fa.info', color='white')
        self.actionAbout.setIcon(aboutIcon)

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

    def handle_tray_event(self, *args):
        Lumberjack.info('< MainApp > - -> (handle_tray_event)')
        print(Fore.MAGENTA + '$! Received a tray action with args: ', args)
        if args[0] == 3:
            self.show()
            return
        elif hasattr(args[0], 'text'):
            print(Fore.MAGENTA + '$! Tray event has text!!')
            if args[0].text() == 'Open PAT':
                self.show()
            elif args[0].text() == 'Exit':
                self.close()

    def _retranslateUi(self, MainWindow):
        pass

    def handle_menu_event(self, *args):
        Lumberjack.info('< MainApp > - -> (handle_menu_event)')
        Lumberjack.debug('(handle_menu_event) - args = {}'.format(args))

        action_text = args[0].text()
        icon = args[0].icon()

        Lumberjack.debug('(handle_menu_event) - Action text selector = {}'.format(action_text))
        print(Fore.MAGENTA + '$! Action text received: ', action_text)

        if action_text == 'Home':
            Lumberjack.info('(handle_menu_event) >User action> :  Adding Home tab to self')
            self.add_tab(HomeTab, 'Home', icon)

        if action_text == 'Settings':
            Lumberjack.info('(handle_menu_event) >User action> :  Showing settings dialog')
            self.show_settings()

        elif action_text == 'Bank accounts':
            Lumberjack.info('(handle_menu_event) >User action> : Adding Bank account List tab to self')
            self.add_tab(BankAccountListTab, 'Bank accounts', icon)

        elif action_text == 'Contracts':
            Lumberjack.info('(handle_menu_event) >User action> : Adding Contracts List tab to self')
            self.add_tab(ContractListTab, 'Contracts', icon)

        elif action_text == 'Email addresses':
            Lumberjack.info('(handle_menu_event) >User action> : Adding EmailAddress List tab to self')
            self.add_tab(EmailAddressListTab, 'Email addresses', icon)

        elif action_text == 'Letters':
            Lumberjack.info('(handle_menu_event) >User action> :  Adding Letter List tab to self')
            self.add_tab(LetterListTab, 'Letters', icon)

        elif action_text == 'Users':
            Lumberjack.info('(handle_menu_event) >User action> :  Adding User List tab to self')
            self.add_tab(UserListTab, 'Users', icon)

        elif action_text == 'Relations':
            Lumberjack.info('(handle_menu_event) >User action> :  Adding Relation List tab to self')
            self.add_tab(RelationListTab, 'Relations', icon)

        elif action_text == 'Transactions':
            Lumberjack.info('(handle_menu_event) >User action> :  Adding Transaction List tab to self')
            self.add_tab(TransactionListTab, 'Transactions', icon)

        elif action_text == 'Help':
            Lumberjack.info('(handle_menu_event) >User action> :  Showing help dialog')
            # TODO - build help dialog and help files

        elif action_text == 'About':
            Lumberjack.info('(handle_menu_event) >User action> :  Showing about dialog')
            # TODO build About dialog.

    def show_settings(self):
        Lumberjack.info('< MainApp > - -> (show_settings)')
        settings_dialog = SettingsDialog()
        settings_dialog.exec_()

    def add_tab(self, tab_cls, tab_name, icon):
        Lumberjack.info('< MainApp > - -> (add_tab)')
        new_tab = tab_cls(self.dbhelper)
        print(Fore.MAGENTA + 'Adding a tab with class: ', str(tab_cls))
        new_tab.setObjectName(str(tab_cls))
        self.tabWidget.addTab(new_tab, icon, self._translate("MainWindow", tab_name))

        print(Fore.MAGENTA + 'New tab added to tab list.')
        self.tabWidget.setCurrentIndex(self.tabWidget.indexOf(new_tab))
        self.tab_list.append(new_tab)

    def close_tab(self, index):
        # TODO - Check if index stays correct when moving tabs around
        requesting_tab = self.tab_list[index]
        print(Fore.MAGENTA + 'requesting tab is: ', requesting_tab)
        if hasattr(requesting_tab, 'form'):
            if requesting_tab.form.edit_mode:
                print(Fore.MAGENTA + 'Tab is in edit mode.')
                requesting_tab.form.toggle_edit_mode(False, None, None)
            if requesting_tab.form.edit_mode is None:
                print(Fore.MAGENTA + 'Tab is now in equil.')
                self.tabWidget.removeTab(index)
                del self.tab_list[index]
        else:
            self.tabWidget.removeTab(index)
            del self.tab_list[index]

    def load_settings(self):
        self.settings = QSettings()

        db_path = self.settings.value('db_base_path')
        db_name = self.settings.value('db_name')

        if db_path is not None and db_name is not None:
            db_file = os.path.join(db_path, db_name)
            Lumberjack.debug('__init__ - db_file = {}'.format(db_file))
            if os.path.exists(db_file):
                return
        Lumberjack.warning('(load_settings) - database not found')
        settings_dialog = SettingsDialog()
        settings_dialog.exec_()
        int_value = self.settings.value('db_type', type=int)
        print(Fore.MAGENTA + "load choosen database setting: %s" % repr(int_value))

    def load_styling(self):
        style.set_window_style(self)

    def closeEvent(self, event):
        print(Fore.MAGENTA + "User has clicked the red x on the main window")
        for tab in self.tab_list:
            if hasattr(tab, 'form'):
                if tab.form.edit_mode:
                    print(Fore.MAGENTA + 'Tab is in edit mode.')
                    tab.form.toggle_edit_mode(False, None, None)

        close_dialog = CloseDialog()
        result = close_dialog.exec_()
        if result == close_dialog.Minimize:
            self.hide()
            event.ignore()
        elif result == close_dialog.Rejected:
            event.ignore()
        elif result == close_dialog.Exit:
            print(Fore.MAGENTA + "Exiting via save dialog, result = ", result)
            self.trayIcon.hide()
            event.accept()
Beispiel #28
0
class ElectrumGui(Logger):
    @profiler
    def __init__(self, config: 'SimpleConfig', daemon: 'Daemon',
                 plugins: 'Plugins'):
        set_language(config.get('language', get_default_language()))
        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-bynd.desktop')
        self.gui_thread = threading.current_thread()
        self.config = config
        self.daemon = daemon
        self.plugins = plugins
        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-bynd.png"))
        # 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()
        # init tray
        self.dark_icon = self.config.get("dark_icon", False)
        self.tray = QSystemTrayIcon(self.tray_icon(), None)
        self.tray.setToolTip('Electrum-BYND')
        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()
        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-BYND"), 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()
        if self.network_dialog:
            self.network_dialog.close()
        if self.lightning_dialog:
            self.lightning_dialog.close()
        if self.watchtower_dialog:
            self.watchtower_dialog.close()
        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.is_lightning_running():
            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(self.daemon.network, self.config,
                                            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()
        # 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' +
                               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 BaseException 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):
        try:
            self.init_network()
        except UserCancelled:
            return
        except GoBack:
            return
        except BaseException as e:
            self.logger.exception('')
            return
        self.timer.start()

        path = self.config.get_wallet_path(use_gui_last_wallet=True)
        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
                if self.config.get('persist_daemon'):
                    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 #29
0
class MainWindow(QMainWindow):
    EXIT_CODE_REBOOT = 520

    def __init__(self):
        super().__init__()
        Config.initialize()
        self.initUI()

    def initUI(self):
        self.setAttribute(Qt.WA_DeleteOnClose)
        self.spliter = QSplitter(Qt.Vertical)
        self.spliter.addWidget(TestUnitArea())
        self.spliter.addWidget(TestResultArea())
        self.spliter.setHandleWidth(1)
        self.setCentralWidget(self.spliter)

        tool_menu = QMenu('工具', self.menuBar())
        tool_menu.addAction('数据监听', self.onDebugWindow)
        tool_menu.addAction('单步测试', self.onSingleStep)
        tool_menu.addAction('记录查询', self.onViewData)
        tool_menu.addAction('条码打印', self.onPrintBarCode)
        tool_menu.addAction('异常信息', self.onExceptionWindow)

        setting_menu = QMenu('选项', self.menuBar())
        setting_menu.addAction('参数设置', self.onSetting)
        # setting_menu.addAction('软件重启', self.onRestart)

        help_menu = QMenu('帮助', self.menuBar())
        help_menu.addAction('关于', self.onAbout)

        self.menuBar().addMenu(setting_menu)
        self.menuBar().addMenu(tool_menu)
        self.menuBar().addMenu(help_menu)

        QApplication.setWindowIcon(QIcon(Config.LOGO_IMG))
        QApplication.instance().aboutToQuit.connect(self.onApplicationQuit)
        QApplication.setOrganizationName(Config.ORGANIZATION)
        QApplication.setApplicationName(Config.APP_NAME)
        QApplication.setApplicationVersion(Config.APP_VERSION)
        self.restoreQSettings()
        self.createSystemTray()

    def onDebugWindow(self):
        if not ui.DebugDialog.prev_actived:
            self.debugWin = ui.DebugDialog()
            self.debugWin.show()
        else:
            QApplication.setActiveWindow(ui.DebugDialog.prev_window)
            ui.DebugDialog.prev_window.showNormal()

    def onSingleStep(self):
        if not ui.SingleStepFrame.prev_actived:
            self.singleWin = ui.SingleStepFrame()
            self.singleWin.show()
        else:
            QApplication.setActiveWindow(ui.SingleStepFrame.prev_window)
            ui.SingleStepFrame.prev_window.showNormal()

    def onViewData(self):
        if not ui.SearchWindow.prev_actived:
            self.searchWin = ui.SearchWindow()
            self.searchWin.show()
        else:
            QApplication.setActiveWindow(ui.SearchWindow.prev_window)
            ui.SearchWindow.prev_window.showNormal()

    def onPrintBarCode(self):
        if not CodeDialog.prev_actived:
            self.codeWin = CodeDialog()
            self.codeWin.show()
        else:
            QApplication.setActiveWindow(CodeDialog.prev_window)
            CodeDialog.prev_window.showNormal()

    def onExceptionWindow(self):
        if not ui.ExceptionWindow.prev_actived:
            self.excptionWin = ui.ExceptionWindow()
            self.excptionWin.show()
        else:
            QApplication.setActiveWindow(ui.ExceptionWindow.prev_window)
            ui.ExceptionWindow.prev_window.showNormal()

    def restoreQSettings(self):
        main_win_geo = Config.QSETTING.value('MainWindow/geometry')
        main_win_centerwgt_state = Config.QSETTING.value(
            'MainWindow/CenterWidget/state')

        if main_win_geo: self.restoreGeometry(main_win_geo)
        if main_win_centerwgt_state:
            self.spliter.restoreState(main_win_centerwgt_state)

    def onSetting(self):
        dlg = ui.SettingDialog(self)
        dlg.move(self.x() + 50, self.y() + 50)
        dlg.exec()

    def onRestart(self):
        QApplication.exit(self.EXIT_CODE_REBOOT)

    def onAbout(self):
        dlg = ui.AboutDialog(Config.ABOUT_HTML)
        dlg.resize(400, 300)
        dlg.exec()

    def createSystemTray(self):
        self.systray = QSystemTrayIcon(self)
        self.systray.setIcon(QIcon(Config.LOGO_IMG))
        self.systray.show()

        trayMenu = QMenu()
        trayMenu.addAction('最大化', self.showMaximized)
        trayMenu.addAction('最小化', self.showMinimized)
        trayMenu.addAction('显示窗口', self.showNormal)
        stayOnTop = QAction('总在最前',
                            trayMenu,
                            checkable=True,
                            triggered=self.stayOnTop)
        trayMenu.addAction(stayOnTop)
        trayMenu.addSeparator()
        trayMenu.addAction('退出', QApplication.quit)

        username = platform.node()
        ip = socket.gethostbyname(socket.gethostname())

        self.systray.setToolTip('用户:{}\nIP:{}'.format(username, ip))
        self.systray.activated.connect(self.onSystemTrayActivated)
        self.systray.setContextMenu(trayMenu)

    def onSystemTrayActivated(self, reason):
        if reason in [QSystemTrayIcon.DoubleClick, QSystemTrayIcon.Trigger]:
            self.showNormal()

    def stayOnTop(self, checked):
        self.setWindowFlag(Qt.WindowStaysOnTopHint, self.sender().isChecked())
        self.show()

    def onApplicationQuit(self):
        Config.QSETTING.setValue('MainWindow/geometry', self.saveGeometry())
        Config.QSETTING.setValue('MainWindow/CenterWidget/state',
                                 self.spliter.saveState())
        Config.finalize()
        self.systray.deleteLater()

    def closeEvent(self, event):
        Config.QSETTING.setValue('MainWindow/geometry', self.saveGeometry())
        Config.QSETTING.setValue('MainWindow/CenterWidget/state',
                                 self.spliter.saveState())

        if self.systray.isVisible():
            self.hide()
            event.ignore()
Beispiel #30
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 #31
0
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 #32
0
class MainWindow(QMainWindow):
	"""The main GUI application."""
	def __init__(self, config):
		"""Initializer for the GUI widgets. Pass in an instance of Config class, so that it may interact with the config."""
		super().__init__()

		self.config = config

		self.setWindowTitle("Livestreamer GUI v{}".format(APPVERSION))

		self.setup_systray()
		self.setup_menu()
		self.setup_geometry()

		self.livestreamer_thread = None
		self.thread_exit_grace_time = 10000 # How long a thread can take to exit in milliseconds
		self.timestamp_format = self.config.get_config_value("timestamp-format")

		self.setup_control_widgets()
		self.update_colors()

		# Load all streaming-related data
		self.selections = {"streamer": None, "channel": None}
		self.load_streamers()
		self.load_channels(self.streamer_input.currentText())
		
		# Do the first configuration, if the application was run for the first time
		self.do_init_config()

		# Finally show the window and the system tray icon, if it should be shown
		self.show()

		self.close_override = False
		self.show_hide_systray()

		self.check_and_do_database_migration()

	def do_init_config(self):
		do_config = self.config.get_config_value("is-configured")
		if do_config == 0:
			self.menu_cmd_configure()
			self.config.set_config_value("is-configured", 1)
		self.insertText("Using config database version '{}'".format(self.config.get_config_value("db-version")))

	def setup_systray(self):
		if not self.config.get_config_value("enable-systray-icon"):
			self.systray = None
			return

		self.systray = QSystemTrayIcon(self)
		self.systray.activated.connect(self.systray_activated)
		main_menu = QMenu(self)

		quit_action = QAction("&Quit", self)
		quit_action.triggered.connect(self.on_close_override)
		main_menu.addAction(quit_action)

		self.systray.setContextMenu(main_menu)

	def systray_activated(self, reason):
		if reason == QSystemTrayIcon.Trigger:
			if self.isVisible():
				self.hide()
			else:
				self.showNormal()

	def check_and_do_database_migration(self):
		current_version = self.config.get_config_value("db-version")
		if self.config.is_migration_needed():
			self.insertText("Detected pending config database upgrade to version '{}'. Awaiting user input...".format(DBVERSION))
			message = "You are using an older version of the application config database.\n\nWould you like to upgrade the database now? Your existing config database will be backed up."

			upgrade_is_mandatory = current_version < MANDATORY_DBVERSION

			if upgrade_is_mandatory:
				message = message + "\n\nWARNING: Your config database is not compatible with this version of Livestreamer GUI. UPDATE IS MANDATORY! If you cancel the update, the application will exit."

			reply = QMessageBox.question(self, "Pending config database upgrade", message, QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes)
			if reply == QMessageBox.Yes:
				self.insertText("Backing up config database...")
				backup = self.config.make_database_backup()
				self.insertText("Current config database backed up to '{}'".format(backup))
				self.insertText("Config database update initialized...")
				self.update()
				self.config.execute_migration()
				new_version = self.config.get_config_value("db-version")
				self.insertText("Config database update from version '{}' to '{}' finished.".format(current_version, new_version))
			elif reply == QMessageBox.No and upgrade_is_mandatory:
				QtCore.QTimer.singleShot(500, self.on_close_override)
				# self.on_close_override() # Calling this in an __init__()-called method doesn't seem to work...
			else:
				self.insertText("Config database update cancelled. No changes were made.")

	def setup_menu(self):
		config_action = QAction("&Configure...", self)
		config_action.triggered.connect(self.menu_cmd_configure)

		quit_action = QAction("&Quit", self)
		quit_action.setShortcut("Ctrl+Q")
		quit_action.triggered.connect(self.on_close_override)

		menu = self.menuBar()
		file_menu = menu.addMenu("&File")
		file_menu.addAction(config_action)
		file_menu.addSeparator()
		file_menu.addAction(quit_action)

	def setup_geometry(self):
		width = self.config.get_config_value("root-width")
		height = self.config.get_config_value("root-height")

		topleft = QApplication.desktop().availableGeometry().topLeft()
		if self.config.get_config_value("remember-window-position"):
			xoffset = self.config.get_config_value("root-xoffset")
			yoffset = self.config.get_config_value("root-yoffset")
			topleft.setX(self.config.get_config_value("root-xoffset"))
			topleft.setY(self.config.get_config_value("root-yoffset"))

		self.resize(width, height)
		self.setMinimumSize(500, 300)
		self.move(topleft)

		# Center the window
		# center_point = QApplication.desktop().availableGeometry().center()
		# frame_geometry = self.frameGeometry()
		# frame_geometry.moveCenter(center_point)
		# self.move(frame_geometry.topLeft())

	def setup_control_widgets(self):
		self.cwidget = QWidget(self)
		self.setCentralWidget(self.cwidget)

		layout = QGridLayout(self.cwidget)
		self.cwidget.setLayout(layout)

		fg_fav = self.config.get_config_value("button-foreground-favorite")
		fg_edit = self.config.get_config_value("button-foreground-edit")
		fg_add = self.config.get_config_value("button-foreground-add")
		fg_delete = self.config.get_config_value("button-foreground-delete")

		control_button_width = 30
		control_button_font_style = "QPushButton { font-family: Arial, sans-serif; font-size: 16px }"

		column = 0
		label_streamer_input = QLabel("Streamer", self.cwidget)
		layout.addWidget(label_streamer_input, 0, column)
		label_channel_input = QLabel("Channel", self.cwidget)
		layout.addWidget(label_channel_input, 1, column)
		label_quality_input = QLabel("Stream quality", self.cwidget)
		layout.addWidget(label_quality_input, 2, column)

		column += 1
		self.streamer_input = QComboBox(self.cwidget)
		self.streamer_input.setEnabled(False)
		self.streamer_input.currentIndexChanged.connect(self.on_streamer_select)
		layout.addWidget(self.streamer_input, 0, column)
		self.channel_input = QComboBox(self.cwidget)
		self.channel_input.setEnabled(False)
		self.channel_input.currentIndexChanged.connect(self.on_channel_select)
		layout.addWidget(self.channel_input, 1, column)
		self.quality_input = QComboBox(self.cwidget)
		self.quality_input.addItem("(auto-refresh is disabled; please refresh manually)")
		self.quality_input.setEnabled(False)
		layout.addWidget(self.quality_input, 2, column)
		layout.setColumnStretch(column, 5)

		column += 1
		self.fav_streamer_button = QPushButton("\u2764", self.cwidget)
		self.fav_streamer_button.setMaximumWidth(control_button_width)
		self.fav_streamer_button.setStyleSheet(":enabled {{ color: {0} }} {1}".format(fg_fav, control_button_font_style))
		self.fav_streamer_button.setEnabled(False)
		self.fav_streamer_button.setToolTip("Set the selected streamer as your most favorite streamer")
		self.fav_streamer_button.clicked.connect(self.cmd_set_favorite_streamer)
		layout.addWidget(self.fav_streamer_button, 0, column)
		self.fav_channel_button = QPushButton("\u2764", self.cwidget)
		self.fav_channel_button.setMaximumWidth(control_button_width)
		self.fav_channel_button.setStyleSheet(':enabled {{ color: {0} }} {1}'.format(fg_fav, control_button_font_style))
		self.fav_channel_button.setEnabled(False)
		self.fav_channel_button.setToolTip("Set the selected channel as your most favorite channel")
		self.fav_channel_button.clicked.connect(self.cmd_set_favorite_channel)
		layout.addWidget(self.fav_channel_button, 1, column)
		self.clear_quality_cache_button = QPushButton("Refresh streams", self.cwidget)
		self.clear_quality_cache_button.setEnabled(False)
		self.clear_quality_cache_button.clicked.connect(self.cmd_refresh_quality_cache)
		layout.addWidget(self.clear_quality_cache_button, 2, column, 1, 4)

		column += 1
		self.edit_streamer_button = QPushButton("\u270E", self.cwidget)
		self.edit_streamer_button.setMaximumWidth(control_button_width)
		self.edit_streamer_button.setStyleSheet(":enabled {{ color: {0} }} {1}".format(fg_edit, control_button_font_style))
		self.edit_streamer_button.setEnabled(False)
		self.edit_streamer_button.setToolTip("Edit data about the selected streamer")
		self.edit_streamer_button.clicked.connect(self.cmd_edit_streamer)
		layout.addWidget(self.edit_streamer_button, 0, column)
		self.edit_channel_button = QPushButton("\u270E", self.cwidget)
		self.edit_channel_button.setMaximumWidth(control_button_width)
		self.edit_channel_button.setStyleSheet(":enabled {{ color: {0} }} {1}".format(fg_edit, control_button_font_style))
		self.edit_channel_button.setToolTip("Edit data about the selected channel")
		self.edit_channel_button.clicked.connect(self.cmd_edit_channel)
		layout.addWidget(self.edit_channel_button, 1, column)

		column += 1
		self.add_streamer_button = QPushButton("\u271A", self.cwidget)
		self.add_streamer_button.setMaximumWidth(control_button_width)
		self.add_streamer_button.setStyleSheet(":enabled {{ color: {0} }} {1}".format(fg_add, control_button_font_style))
		self.add_streamer_button.setEnabled(False)
		self.add_streamer_button.setToolTip("Add a new streamer")
		self.add_streamer_button.clicked.connect(self.cmd_add_streamer)
		layout.addWidget(self.add_streamer_button, 0, column)
		self.add_channel_button = QPushButton("\u271A", self.cwidget)
		self.add_channel_button.setMaximumWidth(control_button_width)
		self.add_channel_button.setStyleSheet(":enabled {{ color: {0} }} {1}".format(fg_add, control_button_font_style))
		self.add_channel_button.setToolTip("Add a new channel")
		self.add_channel_button.clicked.connect(self.cmd_add_channel)
		layout.addWidget(self.add_channel_button, 1, column)

		column += 1
		self.delete_streamer_button = QPushButton("\u2716", self.cwidget)
		self.delete_streamer_button.setMaximumWidth(control_button_width)
		self.delete_streamer_button.setStyleSheet(":enabled {{ color: {0} }} {1}".format(fg_delete, control_button_font_style))
		self.delete_streamer_button.setEnabled(False)
		self.delete_streamer_button.setToolTip("Remove the selected streamer permanently")
		self.delete_streamer_button.clicked.connect(self.cmd_delete_streamer)
		layout.addWidget(self.delete_streamer_button, 0, column)
		self.delete_channel_button = QPushButton("\u2716", self.cwidget)
		self.delete_channel_button.setMaximumWidth(control_button_width)
		self.delete_channel_button.setStyleSheet(":enabled {{ color: {0} }} {1}".format(fg_delete, control_button_font_style))
		self.delete_channel_button.setToolTip("Remove the selected channel permanently")
		self.delete_channel_button.clicked.connect(self.cmd_delete_channel)
		layout.addWidget(self.delete_channel_button, 1, column)

		# Add button for running livestreamer at the fourth row
		self.run_livestreamer_button = QPushButton("Run Livestreamer", self.cwidget)
		self.run_livestreamer_button.setEnabled(False)
		self.run_livestreamer_button.clicked.connect(self.run_livestreamer)
		layout.addWidget(self.run_livestreamer_button, 3, 0)

		self.log_widget = QTextEdit(self.cwidget)
		layout.addWidget(self.log_widget, 4, 0, 1, column+1)
		self.log_widget.setAcceptRichText(False)
		self.log_widget.setReadOnly(True)
		self.log_widget.setTabChangesFocus(True)

	def set_window_icon(self):
		"""Sets the root window's icon, which is also shown in the taskbar."""
		streamer = self.config.get_streamer(self.streamer_input.currentText())
		icon = QIcon(os.path.join(IMAGESROOT, streamer["icon"]))
		self.setWindowIcon(icon)

		if self.systray is not None:
			self.systray.setIcon(icon)

	def closeEvent(self, event):
		"""When the QWidget is closed, QCloseEvent is triggered, and this method catches and handles it."""
		if not self.close_override and self.put_to_systray("close"):
			event.ignore()
			return

		if self.livestreamer_thread is not None and self.livestreamer_thread.keep_running:
			reply = QMessageBox.question(self, "Really quit Livestreamer GUI?", "Livestreamer is still running. Quitting will close it and the opened player.\n\nQuit?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
			if reply == QMessageBox.Yes:
				# Terminate the child process, else it'll keep running even after this application is closed
				if self.livestreamer_thread is not None:
					self.livestreamer_thread.term_process()
					self.livestreamer_thread.wait(self.thread_exit_grace_time)
					self.update()
				event.accept()
			else:
				event.ignore()

		# Explicitly hide the icon, if it remains visible after the application closes
		if self.systray is not None:
			self.systray.hide()

		# Remember the position of the window
		self.remember_window_position()

		event.accept()

	def changeEvent(self, event):
		if type(event) is not QWindowStateChangeEvent:
			return

		# It's one of the window state change events (normal, minimize, maximize, fullscreen, active)
		if self.isMinimized():
			self.put_to_systray("minimize")

	def remember_window_position(self):
		if self.config.get_config_value("remember-window-position"):
			point = self.frameGeometry().topLeft()
			self.config.set_config_value("root-xoffset", point.x())
			self.config.set_config_value("root-yoffset", point.y())
			self.insertText("Window position saved.")

	def show_hide_systray(self):
		if self.systray is None:
			self.setup_systray()
			if self.systray is None:
				return

		if self.config.get_config_value("enable-systray-icon"):
			self.systray.show()
		else:
			self.systray.hide()

	def put_to_systray(self, event):
		if event == "minimize":
			config_value = "minimize-to-systray"
		elif event == "close":
			config_value = "close-to-systray"
		else:
			return False

		if self.systray is not None and self.config.get_config_value(config_value) and self.isVisible():
			self.hide()
			return True
		return False

	def menu_cmd_configure(self):
		streamer = self.config.get_streamer(self.streamer_input.currentText())
		dialog = AppConfigDialog(self, self.config, streamer_icon=os.path.join(IMAGESROOT, streamer["icon"]))
		dialog.exec()
		if dialog.result() == QDialog.Accepted:
			self.show_hide_systray()
			self.update_colors()
		dialog.close()
		dialog = None

	def cmd_set_favorite_streamer(self):
		raise NotImplementedException()
		# self.fav_streamer_button.setEnabled(False)
		# self.config.set_favorite_streamer(self.streamer_input.setCurrentText())
		# self.insertText("Favorited streamer '{}'.".format(self.streamer_input.setCurrentText()))

	def cmd_edit_streamer(self):
		raise NotImplementedException()

	def cmd_add_streamer(self):
		raise NotImplementedException()

	def cmd_delete_streamer(self):
		raise NotImplementedException()

	def cmd_set_favorite_channel(self):
		self.fav_channel_button.setEnabled(False)
		self.config.set_favorite_channel(self.streamer_input.currentText(), self.channel_input.currentText())
		self.insertText("Favorited channel '{}'.".format(self.channel_input.currentText()))

	def cmd_edit_channel(self):
		streamer = self.config.get_streamer(self.streamer_input.currentText())
		streamer_icon = os.path.join(IMAGESROOT, streamer["icon"])
		channel_data = self.config.get_streamer_channel(streamer["name"], self.channel_input.currentText())
		dialog = AddEditChannelsDialog(self, self.config, title="Edit the channel", streamer_icon=streamer_icon, streamer=streamer, channel_data=channel_data)
		dialog.exec()
		result = dialog.result_data
		dialog.close()
		dialog = None
		if result is not None:
			self.insertText("Updated channel name '{old_name}' => '{new_name}, URL '{old_url}' => '{new_url}'".format(old_name=channel_data["name"], new_name=result["name"], old_url=channel_data["url"], new_url=result["url"]))
			self.load_channels(streamer["name"])
			
			# Set the active channel to the previously selected (due to possible name change and sorting)
			self.channel_input.setCurrentIndex(self.channel_input.findText(result["name"]))
			
	def cmd_add_channel(self):
		streamer = self.config.get_streamer(self.streamer_input.currentText())
		streamer_icon = os.path.join(IMAGESROOT, streamer["icon"])
		dialog = AddEditChannelsDialog(self, self.config, title="Add a channel", streamer_icon=streamer_icon, streamer=streamer)
		dialog.exec()
		result = dialog.result_data
		dialog.close()
		dialog = None
		if result is not None:
			self.insertText("Added channel '{}' with URL '{}'".format(result["name"], result["url"]))
			self.load_channels(streamer["name"])

	def cmd_delete_channel(self):
		channel = self.config.get_streamer_channel(self.streamer_input.currentText(), self.channel_input.currentText())
		reply = QMessageBox.question(self, "Delete channel", "Are you sure you want to remove the channel?\nName: {}\nURL: {}".format(channel["name"], channel["url"]), QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
		if reply == QMessageBox.Yes:
			self.config.delete_channel(self.streamer_input.currentText(), channel["name"])
			self.insertText("Removed channel '{}' with URL '{}'".format(channel["name"], channel["url"]))
			self.load_channels(self.streamer_input.currentText())

	def cmd_refresh_quality_cache(self):
		self.insertText("Refreshing cache for channel '{}'.".format(self.channel_input.currentText()))
		self.clear_quality_cache_button.setEnabled(False)
		self.clear_quality_cache_button.repaint() # Loading streams seems to block repainting of the GUI, so force a repaint here
		self.config.clean_quality_cache(self.streamer_input.currentText(), self.channel_input.currentText(), True)
		self.load_streams(True)
		self.clear_quality_cache_button.setEnabled(True)

	def on_close_override(self):
		self.close_override = True
		self.close()

	def on_streamer_select(self, event):
		# If the previously selected item is selected again, don't do anything
		if self.selections["streamer"] == self.streamer_input.currentText():
			return
		self.selections["streamer"] = self.streamer_input.currentText()
		streamer = self.config.get_streamer(self.streamer_input.currentText())
		self.set_window_icon()
		if streamer["favorite"]:
			self.fav_streamer_button.setEnabled(False)
		else:
			self.fav_streamer_button.setEnabled(True)

	def on_channel_select(self, event):
		# If the previously selected item is selected again, don't do anything
		if self.selections["channel"] == self.channel_input.currentText() or not self.channel_input.currentText():
			return
		self.selections["channel"] = self.channel_input.currentText()
		channel = self.config.get_streamer_channel(self.streamer_input.currentText(), self.channel_input.currentText())
		if channel and channel["favorite"]:
			self.fav_channel_button.setEnabled(False)
		else:
			self.fav_channel_button.setEnabled(True)

		self.load_streams()
		self.channel_input.setFocus(True)

	def load_streamers(self):
		streamers = self.config.get_streamers()
		favorite_streamer_index = 0
		streamer_list = []
		for index, streamer in enumerate(streamers):
			streamer_list.append(streamer["name"])
			if streamer["favorite"]:
				favorite_streamer_index = index
		self.streamer_input.clear()
		self.streamer_input.addItems(streamer_list)
		if len(streamer_list) != 0:
			self.streamer_input.setCurrentIndex(favorite_streamer_index)
		self.selections["streamer"] = self.streamer_input.currentText()
		self.fav_streamer_button.setEnabled(False)

	def load_channels(self, streamer_name):
		channels = self.config.get_streamer_channels(streamer_name)
		self.channel_input.clear()
		favorite_channel = None
		channel_list = []
		self.fav_channel_button.setEnabled(False)
		for index, channel in enumerate(channels):
			channel_list.append(channel["name"])
			if channel["favorite"]:
				favorite_channel = channel["name"]
		self.channel_input.addItems(sorted(channel_list))
		if len(channel_list) == 0:
			self.channel_input.addItem("(no channels exist for this streamer)")
			self.fav_channel_button.setEnabled(False)
			self.edit_channel_button.setEnabled(False)
			self.delete_channel_button.setEnabled(False)
			self.clear_quality_cache_button.setEnabled(False)
			self.channel_input.setEnabled(False)
		else:
			self.edit_channel_button.setEnabled(True)
			self.delete_channel_button.setEnabled(True)
			self.clear_quality_cache_button.setEnabled(True)
			self.channel_input.setEnabled(True)
			
			if favorite_channel is None:
				self.channel_input.setCurrentIndex(0)
				self.fav_channel_button.setEnabled(True)
			else:
				self.channel_input.setCurrentIndex(self.channel_input.findText(favorite_channel))

		self.selections["channel"] = self.channel_input.currentText()

	def display_loaded_streams(self, streams, skip_caching=False):
		self.quality_input.clear()
		if len(streams) == 0:
			self.quality_input.addItem("(channel is currently not streaming)")
		else:
			self.run_livestreamer_button.setEnabled(True)
			self.clear_quality_cache_button.setEnabled(True)
			self.quality_input.addItems(sorted(streams))
			self.quality_input.setCurrentIndex(0)
			self.quality_input.setEnabled(True)
			if not skip_caching:
				self.insertText("Cleaning any cached streams for channel '{}'...".format(self.channel_input.currentText()))
				self.config.clean_quality_cache(self.streamer_input.currentText(), self.channel_input.currentText())
				self.insertText("Adding probed streams for channel '{}' to cache...".format(self.channel_input.currentText()))
				self.config.add_quality_to_cache(self.streamer_input.currentText(), self.channel_input.currentText(), streams)
				self.insertText("Done.")

	def load_streams(self, force_refresh=False):
		self.quality_input.clear()
		self.run_livestreamer_button.setEnabled(False)
		self.channel_input.setEnabled(False)
		self.quality_input.setEnabled(False)

		if self.channel_input.count() == 0:
			return

		streams = self.config.get_quality_from_cache(self.streamer_input.currentText(), self.channel_input.currentText())
		if len(streams) > 0:
			self.display_loaded_streams(streams, True)
			self.insertText("Loaded streams for channel '{}' from cache.".format(self.channel_input.currentText()))
		else:
			self.insertText("No cached channel streams found for channel '{}'".format(self.channel_input.currentText()))
			if not force_refresh and self.config.get_config_value('auto-refresh-quality') == 0:
				self.quality_input.addItem("(auto-refresh is disabled; please refresh manually)")
				self.quality_input.setEnabled(False)
			else:
				stream_url = self.get_streamer_url()
				if stream_url is None:
					self.insertText("Failed to form a complete streamer URL (missing streamer/channel/stream)!")
					return
				self.probe_for_streams(stream_url)
		
		self.channel_input.setEnabled(True)

	def probe_for_streams(self, stream_url):
		self.insertText("Probing streamer's channel for live streams: {}".format(stream_url))
		livestreamer = self.config.get_config_value("livestreamer-path")
		if livestreamer is None or livestreamer.strip() == "" or not os.path.isfile(livestreamer):
			self.insertText("Livestreamer path is not configured or file doesn't exist!")
			return
		command_format = self.config.get_config_value("probe-command-format")
		command = command_format.format(livestreamer=livestreamer, url=stream_url)
		self.livestreamer_thread = LivestreamerWorker(shlex.split(command))
		self.livestreamer_thread.statusMessage.connect(self.parse_probed_streams, False)
		self.livestreamer_thread.start()
		self.livestreamer_thread.wait(self.thread_exit_grace_time)

	def parse_probed_streams(self, event):
		streams = []

		message = event.message.lower()
		if "no streams found on this url" in message:
			self.insertText("No streams found. The channel is probably not streaming.")
		else:
			pos = message.find("available streams:")
			if pos == -1:
				return

			if "(best, worst)" in message:
				message = message.replace("(best, worst)", "(best and worst)")
			elif "(worst, best)" in message:
				message = message.replace("(worst, best)", "(worst and best)")
			qualities = message[pos+18:].split(",")
			for item in qualities:
				streams.append(item.strip())
				left_parenthesis = item.find("(")
				if left_parenthesis == -1:
					continue
				if item.find("worst", left_parenthesis) >= left_parenthesis:
					streams.append("worst")
				if item.find("best", left_parenthesis) >= left_parenthesis:
					streams.append("best")
			streams.sort()
			self.insertText("Found {} stream(s): {}".format(len(streams), ", ".join(streams)))

		self.display_loaded_streams(streams)

	def get_streamer_url(self):
		streamer = self.config.get_streamer(self.streamer_input.currentText())
		if streamer is None:
			self.insertText("No streamer selected!")
			return
		if streamer["url"] is None or streamer["url"].strip() == "":
			self.insertText("Invalid streamer URL!")
			return
		if self.channel_input.count() == 0:
			self.insertText("No channels exist!")
			return
		channel = self.config.get_streamer_channel(streamer["name"], self.channel_input.currentText())
		return urljoin(streamer["url"], channel["url"])

	def run_livestreamer(self):
		if self.livestreamer_thread is not None:
			if self.livestreamer_thread.isRunning():
				self.insertText("Livestreamer should still be running!")
				return
			else:
				self.livestreamer_thread.wait(self.thread_exit_grace_time)
				self.livestreamer_thread = None
				self.update()

		if self.livestreamer_thread is None:
			livestreamer = self.config.get_config_value("livestreamer-path")
			if livestreamer is None or livestreamer.strip() == "" or not os.path.isfile(livestreamer):
				self.insertText("Livestreamer path is not configured or file doesn't exist!")
				return
			player = self.config.get_config_value("player-path")
			if player is None or player.strip() == "" or not os.path.isfile(player):
				self.insertText("Player path is not configured or file doesn't exist!")
				return
			stream_url = self.get_streamer_url()
			if stream_url is None:
				self.insertText("Failed to form a complete streamer URL (missing streamer/channel/stream)!")
				return
			command_format = self.config.get_config_value("command-format")
			quality = self.quality_input.currentText()
			if "(" in quality:
				quality = quality[:quality.find("(")].strip()
			command = command_format.format(livestreamer=livestreamer, player=player, url=stream_url, quality=quality)
			self.livestreamer_thread = LivestreamerWorker(shlex.split(command))
			self.insertText("Starting Livestreamer thread.")
			self.livestreamer_thread.finished.connect(self.handle_livestreamer_thread_finished_signal)
			self.livestreamer_thread.statusMessage.connect(self.handle_livestreamer_thread_message_signal)
			self.livestreamer_thread.start()

	@QtCore.pyqtSlot(object)
	def handle_livestreamer_thread_message_signal(self, event):
		self.insertText(event.message, event.add_newline, event.add_timestamp)

	def handle_livestreamer_thread_finished_signal(self):
		self.livestreamer_thread = None

	def update_colors(self):
		foreground_color = self.config.get_config_value("foreground-color")
		background_color = self.config.get_config_value("background-color")
		self.cwidget.setStyleSheet("QWidget QLabel {{ color: {0} }} .QWidget {{ background-color: {1} }}".format(foreground_color, background_color))
		self.cwidget.update()

	def insertText(self, msg, add_newline=True, timestamp=True):
		"""Helper method for outputting text to the text box."""
		text = ""
		if timestamp and self.timestamp_format is not None:
			timestamp = format(datetime.now().strftime(self.timestamp_format))
			text = "{} ".format(timestamp)
		text += msg
		self.log_widget.moveCursor(QTextCursor.End)
		self.log_widget.insertPlainText(text)
		if add_newline:
			self.log_widget.insertPlainText("\n")
		self.log_widget.update()
Beispiel #33
0
class QtSysTrayIcon:
    def __init__(self):
        self.snapshots = snapshots.Snapshots()
        self.config = self.snapshots.config
        self.decode = None

        if len(sys.argv) > 1:
            if not self.config.setCurrentProfile(sys.argv[1]):
                logger.warning("Failed to change Profile_ID %s" % sys.argv[1],
                               self)

        self.qapp = qttools.createQApplication(self.config.APP_NAME)
        translator = qttools.translator()
        self.qapp.installTranslator(translator)
        self.qapp.setQuitOnLastWindowClosed(False)

        import icon
        self.icon = icon
        self.qapp.setWindowIcon(icon.BIT_LOGO)

        self.status_icon = QSystemTrayIcon(icon.BIT_LOGO)
        #self.status_icon.actionCollection().clear()
        self.contextMenu = QMenu()

        self.menuProfileName = self.contextMenu.addAction(
            _('Profile: "%s"') % self.config.profileName())
        qttools.setFontBold(self.menuProfileName)
        self.contextMenu.addSeparator()

        self.menuStatusMessage = self.contextMenu.addAction(_('Done'))
        self.menuProgress = self.contextMenu.addAction('')
        self.menuProgress.setVisible(False)
        self.contextMenu.addSeparator()

        self.btnPause = self.contextMenu.addAction(icon.PAUSE,
                                                   _('Pause snapshot process'))
        action = lambda: os.kill(self.snapshots.pid(), signal.SIGSTOP)
        self.btnPause.triggered.connect(action)

        self.btnResume = self.contextMenu.addAction(
            icon.RESUME, _('Resume snapshot process'))
        action = lambda: os.kill(self.snapshots.pid(), signal.SIGCONT)
        self.btnResume.triggered.connect(action)
        self.btnResume.setVisible(False)

        self.btnStop = self.contextMenu.addAction(icon.STOP,
                                                  _('Stop snapshot process'))
        self.btnStop.triggered.connect(self.onBtnStop)
        self.contextMenu.addSeparator()

        self.btnDecode = self.contextMenu.addAction(icon.VIEW_SNAPSHOT_LOG,
                                                    _('decode paths'))
        self.btnDecode.setCheckable(True)
        self.btnDecode.setVisible(self.config.snapshotsMode() == 'ssh_encfs')
        self.btnDecode.toggled.connect(self.onBtnDecode)

        self.openLog = self.contextMenu.addAction(icon.VIEW_LAST_LOG,
                                                  _('View Last Log'))
        self.openLog.triggered.connect(self.onOpenLog)
        self.startBIT = self.contextMenu.addAction(icon.BIT_LOGO,
                                                   _('Start BackInTime'))
        self.startBIT.triggered.connect(self.onStartBIT)
        self.status_icon.setContextMenu(self.contextMenu)

        self.pixmap = icon.BIT_LOGO.pixmap(24)
        self.progressBar = QProgressBar()
        self.progressBar.setMinimum(0)
        self.progressBar.setMaximum(100)
        self.progressBar.setValue(0)
        self.progressBar.setTextVisible(False)
        self.progressBar.resize(24, 6)
        self.progressBar.render(self.pixmap,
                                sourceRegion=QRegion(0, -14, 24, 6),
                                flags=QWidget.RenderFlags(
                                    QWidget.DrawChildren))

        self.first_error = self.config.notify()
        self.popup = None
        self.last_message = None

        self.timer = QTimer()
        self.timer.timeout.connect(self.updateInfo)

    def prepairExit(self):
        self.timer.stop()

        if not self.status_icon is None:
            self.status_icon.hide()
            self.status_icon = None

        if not self.popup is None:
            self.popup.deleteLater()
            self.popup = None

        self.qapp.processEvents()

    def run(self):
        if not self.snapshots.busy():
            sys.exit()
        self.status_icon.show()
        self.timer.start(500)

        logger.debug("begin loop", self)

        self.qapp.exec_()

        logger.debug("end loop", self)

        self.prepairExit()

    def updateInfo(self):
        if not self.snapshots.busy():
            self.prepairExit()
            self.qapp.exit(0)
            return

        paused = tools.processPaused(self.snapshots.pid())
        self.btnPause.setVisible(not paused)
        self.btnResume.setVisible(paused)

        message = self.snapshots.takeSnapshotMessage()
        if message is None and self.last_message is None:
            message = (0, _('Working...'))

        if not message is None:
            if message != self.last_message:
                self.last_message = message
                if self.decode:
                    message = (message[0], self.decode.log(message[1]))
                self.menuStatusMessage.setText('\n'.join(tools.wrapLine(message[1],\
                                                                         size = 80,\
                                                                         delimiters = '',\
                                                                         new_line_indicator = '') \
                                                                       ))
                self.status_icon.setToolTip(message[1])

        pg = progress.ProgressFile(self.config)
        if pg.fileReadable():
            pg.load()
            percent = pg.intValue('percent')
            if percent != self.progressBar.value():
                self.progressBar.setValue(percent)
                self.progressBar.render(self.pixmap,
                                        sourceRegion=QRegion(0, -14, 24, 6),
                                        flags=QWidget.RenderFlags(
                                            QWidget.DrawChildren))
                self.status_icon.setIcon(QIcon(self.pixmap))

            self.menuProgress.setText(' | '.join(self.getMenuProgress(pg)))
            self.menuProgress.setVisible(True)
        else:
            self.status_icon.setIcon(self.icon.BIT_LOGO)
            self.menuProgress.setVisible(False)

    def getMenuProgress(self, pg):
        d = (('sent',   _('Sent:')), \
             ('speed',  _('Speed:')),\
             ('eta',    _('ETA:')))
        for key, txt in d:
            value = pg.strValue(key, '')
            if not value:
                continue
            yield txt + ' ' + value

    def onStartBIT(self):
        profileID = self.config.currentProfile()
        cmd = [
            'backintime-qt',
        ]
        if not profileID == '1':
            cmd += ['--profile-id', profileID]
        proc = subprocess.Popen(cmd)

    def onOpenLog(self):
        dlg = logviewdialog.LogViewDialog(self, systray=True)
        dlg.decode = self.decode
        dlg.cbDecode.setChecked(self.btnDecode.isChecked())
        dlg.exec_()

    def onBtnDecode(self, checked):
        if checked:
            self.decode = encfstools.Decode(self.config)
            self.last_message = None
            self.updateInfo()
        else:
            self.decode = None

    def onBtnStop(self):
        os.kill(self.snapshots.pid(), signal.SIGKILL)
        self.btnStop.setEnabled(False)
        self.btnPause.setEnabled(False)
        self.btnResume.setEnabled(False)
        self.snapshots.setTakeSnapshotMessage(0, 'Snapshot terminated')
Beispiel #34
0
class App(QtWidgets.QMainWindow, QtWidgets.QTableWidgetItem,
          form_main.Ui_Dialog, QtWidgets.QErrorMessage, QtWidgets.QHeaderView):
    def __init__(self):
        # Это здесь нужно для доступа к переменным, методам
        # и т.д. в файле design.py
        super().__init__()
        self.startEvent = True
        self.setupUi(self)  # Это нужно для инициализации нашего дизайна
        self.showCommentsButton.pressed.connect(self.showCommentsButtonClick)
        self.findNewComButton.pressed.connect(
            self.searchNewCommentsClickInThread)
        self.autoButton.pressed.connect(self.autoClick)
        self.buttonHand.pressed.connect(self.handSearch)
        self.buttonStop.pressed.connect(self.stopClick)
        self.buttonStop.setEnabled(False)
        #иконка трея
        self.tray_icon = QSystemTrayIcon(self)
        self.tray_icon.setIcon(self.style().standardIcon(
            QStyle.SP_ComputerIcon))
        self.tray_icon = QSystemTrayIcon(self)
        self.tray_icon.setIcon(self.style().standardIcon(
            QStyle.SP_ComputerIcon))
        '''
            show - показать окно
            hide - скрыть окно
            exit - выход из программы
        '''
        show_action = QAction("Развернуть", self)
        quit_action = QAction("Выход", self)
        hide_action = QAction("Скрыть", self)
        show_action.triggered.connect(self.show)
        hide_action.triggered.connect(self.hide)
        quit_action.triggered.connect(qApp.quit)
        tray_menu = QMenu()
        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()
        self.newBDItem.triggered.connect(self.btnMediaClick)
        self.newABCItem.triggered.connect(self.showNewABCWindow)
        self.newAddressItem.triggered.connect(self.showNewAddressWindow)
        self.newServerSettingsItem.triggered.connect(
            self.showNewServerSettingsWindow)

    def showNewABCWindow(self):
        self.windowABC = AppABC()  # Создаём объект класса ExampleApp
        self.windowABC.show()  # Показываем окно

    def showNewAddressWindow(self):
        self.windowEmail = AppEmail()  # Создаём объект класса ExampleApp
        self.windowEmail.show()  # Показываем окно

    def showNewServerSettingsWindow(self):
        self.windowServer = AppServerSettings(
        )  # Создаём объект класса ExampleApp
        self.windowServer.show()  # Показываем окно

    #ручной поиск по словам
    def handSearch(self):
        word = self.textBoxSearch.text()
        username = self.comboBoxAcc.currentText()
        self.handler = formHandler(username)
        self.tableComments.clear()
        comments = self.handler.bdAPI.getCommentHandsearch(word)
        self.tableComments.setRowCount(len(comments))
        row = 0
        for comment in comments:
            self.tableComments.setItem(
                row, 0,
                QtWidgets.QTableWidgetItem(
                    datetime.fromtimestamp(int(
                        comment["comment_date"])).strftime("%d-%m-%Y %H:%M")))
            self.tableComments.setItem(
                row, 1, QtWidgets.QTableWidgetItem(comment["comment_text"]))
            self.tableComments.setItem(
                row, 2,
                QtWidgets.QTableWidgetItem(
                    self.handler.bdAPI.getLinkByCommentId(
                        comment["comment_id"])))
            row += 1
        self.setHeaderTittle()
        QMessageBox.about(self, "Уведомление", "Поиск завершен")

    #остановка автопроцесса
    def stopClick(self):
        self.startEvent = False
        self.buttonStop.setEnabled(False)
        QMessageBox.about(
            self, "Уведомление",
            "Остановка авторежима. Программа остановится после завершения цикла проверки."
        )

    def enableButtons(self):
        self.autoButton.setEnabled(True)
        self.findNewComButton.setEnabled(True)

    #обработчик кнопки "Авторежим"
    def autoClick(self):
        self.startEvent = True
        self.buttonStop.setEnabled(True)
        self.autoButton.setEnabled(False)
        self.findNewComButton.setEnabled(False)
        QMessageBox.about(
            self, "Уведомление",
            "Запущен авто-режим. Некоторые действия недоступны. Уведомления об отслеживании будут приходить на почту."
        )
        thread = Thread(target=self.jobThread, daemon=True)
        thread.start()

    #поток авторежима
    def jobThread(self):
        username = self.comboBoxAcc.currentText()
        self.handler = formHandler(username)
        schedule.every(10).minutes.do(self.jobException)
        while self.startEvent == True:
            schedule.run_pending()
            time.sleep(1)
        self.autoButton.setEnabled(True)
        self.findNewComButton.setEnabled(True)

    def jobException(self):
        # try:
        #     self.job()
        # except Exception as e:
        #     self.writeReport("Ошибка в цикле авторежима: "+str(e))
        #     time.sleep(600)
        #subprocess.run(['python','scrap.py'],timeout=1)
        proc = subprocess.Popen(['python', 'scrap.py'])
        try:
            self.writeReport("Начало процесса")
            outs, errs = proc.communicate(timeout=900)
        except subprocess.TimeoutExpired:
            proc.terminate()
            outs, errs = proc.communicate()
            self.writeReport("Процесс завершился по таймауту.")

    def setHeaderTittle(self):
        self.tableComments.setHorizontalHeaderLabels(
            ['Дата', 'Текст', 'Ссылка на запись'])

    #циклы авторежима
    def job(self):
        username = self.comboBoxAcc.currentText()
        self.handler = None
        self.handler = formHandler(username)
        self.handler.bdAPI.commentsUnNew()  #комментарии прочитаны БД
        self.handler.searchAndAddNewMediaItems()
        rows = self.handler.bdAPI.getMediaIds()
        for row in rows:
            commentsOld = self.handler.bdAPI.takeOldCommentsIds(
                row[0])  #старые 50
            commentsOldN = list(map(lambda x: int(x[0]), commentsOld))
            commentsNew = self.handler.f.takeCommentsWithoutCircle(
                row[0], 50)  #новые 50
            for comment in commentsNew:
                if comment['pk'] in commentsOldN:
                    break
                else:
                    self.handler.bdAPI.rComment(
                        comment, row[0])  #добавляем в базу новые комментарии
        self.handler.bdAPI.checkNewComments(
        )  #отмечает все новые комменты по словарю
        rows = self.handler.bdAPI.showGoodNewComments(
        )  #получает новые и отмеченные для единовременной их отправки на почту
        if len(rows) != 0:
            host = "smtp.yandex.ru"
            to_addr = self.handler.bdAPI.getMail()
            from_addr = "*****@*****.**"
            error = 0
            self.writeReport("Найдено (%s) комментариев(комментарий)." %
                             str(len(rows)))
            self.send_email(rows, host, to_addr, from_addr)
        else:
            self.writeReport("Подходящие комментарии не обнаружены.")
        self.handler.bdAPI.commentsUnNew()  #комментарии прочитаны
        self.writeReport("Успешный цикл авторежима.")

    #отправка сообщения на имейл
    def send_email(self, rows, host, to_addr, from_addr):
        to_addr = list(map(lambda x: x[0], to_addr))
        body_text = "На странице были отслежены следующие комментарии:\n"
        for row in rows:
            body_text += "\nТекст комментария:\n%s\nСсылка на запись в инстаграм:\n%s\nВремя публикации:\n%s\n" % (
                row[1], self.handler.bdAPI.getLinkByCommentId(row[0]),
                datetime.fromtimestamp(row[2]).strftime("%d-%m-%Y %H:%M"))
        msg = MIMEText(body_text, 'plain', 'utf-8')
        msg['Subject'] = Header('Обнаружение комментариев', 'utf-8')
        msg['From'] = from_addr
        separator = ", "
        msg['To'] = separator.join(to_addr)
        server = smtplib.SMTP_SSL(host, port=465)
        server.login('*****@*****.**', 'asdqwe123')
        server.sendmail(from_addr, to_addr, msg.as_string())
        server.quit()
        self.writeReport("Успешная рассылка.")

    #отобразить найденные комменты по словарю
    def showCommentsButtonClick(self):
        username = self.comboBoxAcc.currentText()
        self.handler = formHandler(username)
        self.tableComments.clear()
        comments = self.handler.bdAPI.showGoodComments()
        self.tableComments.setRowCount(len(comments))
        row = 0
        for comment in comments:
            self.tableComments.setItem(
                row, 0,
                QtWidgets.QTableWidgetItem(
                    datetime.fromtimestamp(int(
                        comment['comment_date'])).strftime("%d-%m-%Y %H:%M")))
            self.tableComments.setItem(
                row, 1, QtWidgets.QTableWidgetItem(comment['comment_text']))
            self.tableComments.setItem(
                row, 2,
                QtWidgets.QTableWidgetItem(
                    self.handler.bdAPI.getLinkByCommentId(
                        comment['comment_id'])))
            row += 1
        self.setHeaderTittle()
        QMessageBox.about(self, "Уведомление", "Поиск завершен")

    #поиск по новому словарю
    def searchButtonClick(self):
        username = self.comboBoxAcc.currentText()
        self.handler = formHandler(username)
        #QMessageBox.about(self,"Уведомление","Ищем по новому словарю")
        self.handler.bdAPI.checkComments()
        #QMessageBox.about(self,"Уведомление","Поиск завершен")

    #обновление базы
    def btnMediaClick(self):
        self.buttonMedia.setEnabled(False)
        username = self.comboBoxAcc.currentText()
        self.handler = formHandler(username)
        QMessageBox.about(self, "Уведомление",
                          "Старт обновления БД. Не закрывайте окно")
        try:
            self.handler.LoadAll(username)
        except Exception as e:
            self.writeReport(str(e))
            QMessageBox.about(self, "Ошибка получения записей", str(e))
            return
        QMessageBox.about(self, "Успешно",
                          "База данных комментариев успешно обновлена")
        return

    #событие закрытия окна
    def closeEvent(self, event):
        event.ignore()
        self.hide()
        self.tray_icon.showMessage("Программа свернута",
                                   "Программа была свернута в трей.",
                                   QSystemTrayIcon.Information, 2000)

    #поиск новых комментариев

    def searchNewCommentsClickInThread(self):
        try:
            QMessageBox.about(self, "Уведомление", "Поиск новых комментариев")
            thread = Thread(target=self.searchNewCommentsClick, daemon=True)
            thread.start()
        except Exception as e:
            self.writeReport(str(e))
            QMessageBox.about(self, "Уведомление", str(e))
            self.findNewComButton.setEnabled(True)
            self.buttonStop.setEnabled(False)

    def searchNewCommentsClick(self):

        self.findNewComButton.setEnabled(False)
        self.autoButton.setEnabled(False)
        self.tableComments.clear()
        self.setHeaderTittle()
        username = self.comboBoxAcc.currentText()
        self.handler = formHandler(username)
        self.handler.searchAndAddNewMediaItems()

        self.setHeaderTittle()
        rows = self.handler.findAbcAllCommentsAndSendIt()
        self.tableComments.setRowCount(len(rows))
        i = 0
        for comment in rows:
            self.tableComments.setItem(
                i, 0,
                QtWidgets.QTableWidgetItem(
                    datetime.fromtimestamp(int(
                        comment["comment_date"])).strftime("%d-%m-%Y %H:%M")))
            self.tableComments.setItem(
                i, 1, QtWidgets.QTableWidgetItem(comment["comment_text"]))
            self.tableComments.setItem(
                i, 2,
                QtWidgets.QTableWidgetItem(
                    self.handler.bdAPI.getLinkByCommentId(
                        comment['comment_id'])))
            i += 1
        self.setHeaderTittle()
        # QMessageBox.about(self,"Уведомление","Поиск завершен")
        self.findNewComButton.setEnabled(True)
        self.autoButton.setEnabled(True)
        self.searchButtonClick()

    def writeReport(self, text):
        now = datetime.now()
        with open('report.info', 'a') as file:
            file.write(
                '\n%s\t%s' %
                (now.strftime("%d-%m-%Y %H:%M"), text))  #дозапись в файл
Beispiel #35
0
class CMainDlg(tools_dlgbase.CDlgBase, QMainWindow, ui_main.Ui_MainWindow):
    def __init__(self):
        super(CMainDlg, self).__init__()
        self.setupUi(self)
        self.Center()
        self.initData()
        self.initLayer()
        self.initEvent()
        self.show()

    def initData(self):
        self.m_oCurTimeTimer = QTimer()
        self.m_oCloseWindowTimer = QTimer()
        self.m_iCloseTime = 0
        self.m_bStartClose = False

    def initLayer(self):
        self.setFixedSize(self.width(), self.height())
        self.initStyle()
        self.setTuoPang()

    def setTuoPang(self):
        # 创建窗口托盘
        self.pTray = QSystemTrayIcon(self)
        # 设置托盘图标样式
        icon = QIcon()
        icon.addPixmap(QPixmap(":/res/icon.png"))
        self.pTray.setIcon(icon)
        # 显示图标
        self.pTray.show()

        quitAction = QAction("&退出吧", self, triggered=QApplication.instance().quit)  # 退出APP
        self.trayMenu = QMenu(self)
        self.trayMenu.addAction(quitAction)
        self.pTray.setContextMenu(self.trayMenu)
        self.pTray.setToolTip("关机软件")
        self.pTray.showMessage("提示", "开启关机定时器")
        self.pTray.messageClicked.connect(self.onTrayMessageClick)
        # #托盘图标被激活
        self.pTray.activated.connect(self.onTrayActivated)

    # 界面上关闭按钮
    def closeEvent(self, event):
        event.ignore()  # 忽略关闭事件
        self.hide()  # 隐藏窗体

    # 托盘图标事件
    def onTrayActivated(self, reason):
        print("触发托盘图标事件", reason)
        if reason == QSystemTrayIcon.DoubleClick:  # 双击事件
            self.onTrayDoubleClick()
        elif reason == QSystemTrayIcon.Trigger:  # 单击事件
            self.onTrayTrigger()

    def onTrayDoubleClick(self):
        print("双击了托盘")
        if self.isMinimized() or not self.isVisible():
            self.showNormal()  # 正常显示
            self.activateWindow()
        else:
            self.showMinimized()  # 最小化

    def onTrayTrigger(self):
        print("点击了托盘")

    def onTrayMessageClick(self, *args):
        print("点击了托盘信息")

    def initStyle(self):
        qss_file = QFile(":res/style.qss")
        qss_file.open(QFile.ReadOnly)
        qss = str(qss_file.readAll(), encoding='utf-8')
        qss_file.close()
        self.setStyleSheet(qss)

    @pyqtSlot()
    def on_pbDo_clicked(self):
        if self.m_bStartClose:
            self.cancelCloseWindow()
            return
        iSetHour = self.teSetTime.time().hour()
        iSetMin = self.teSetTime.time().minute()
        iYear, iMon, iDay, iHour, iMin, iSec = time.localtime()[:6]
        iDisTime = (iSetHour - iHour) * 3600 + (iSetMin - iMin) * 60 + (0 - iSec)
        if (iSetHour, iSetMin, iSec) <= (iHour, iMin, iSec):
            iCloseTime = iDisTime + 24*60*60
        else:
            iCloseTime = iDisTime
        self.m_iCloseTime = time.time() + iCloseTime
        print(time.time())
        print("剩余关闭时间", iCloseTime, tools_time.getHourMiniSecDes(iCloseTime))

        self.m_oCloseWindowTimer.timeout.connect(self.checkNeedClose)
        self.m_oCloseWindowTimer.start(1000)
        self.pbDo.setText("取消执行")
        self.m_bStartClose = True

    def cancelCloseWindow(self):
        self.m_bStartClose = False
        self.m_iCloseTime = 0
        self.m_oCloseWindowTimer.stop()
        self.pbDo.setText("执行")
        self.txtTip.setText("")

    def initEvent(self):
        self.m_oCurTimeTimer.timeout.connect(self.showCurTime)
        self.m_oCurTimeTimer.start(1000)

    def checkNeedClose(self):
        fCurTime = time.time()
        if fCurTime > self.m_iCloseTime:
            self.m_oCloseWindowTimer.stop()
            self.closeWindow()
        else:
            iLeftTime = int(self.m_iCloseTime - fCurTime)
            sTime = tools_time.getHourMiniSecDes(iLeftTime)
            self.txtTip.setText("{time}后关闭电脑".format(time=sTime))

    def closeWindow(self):
        print("关机了...")
        sCmd = "shutdown -s -t 1"
        subprocess.run(sCmd)

    def showCurTime(self):
        oTime = QDateTime.currentDateTime()
        sTime1 = oTime.toString("hh:mm:ss")
        sTime2 = oTime.toString("yyyy-MM-dd")
        sDay = oTime.toString("ddd")
        self.lbTime.setText(sTime1)
        sTime = "{year}({week})".format(year=sTime2, week=sDay)
        self.lbData.setText(sTime)
Beispiel #36
0
class MainWindow(QMainWindow):
    button_list = [None, None, None, None, None, None]
    hero_list = [
        "slark", "pudge", "legion_commander", "wraith_king", "boldak", "custom"
    ]

    def __init__(self):
        QMainWindow.__init__(self)
        app.setStyle("Fusion")
        self.setFixedSize(QSize(600, 300))
        self.setWindowTitle("CringeSounds")
        self.setWindowIcon(QtGui.QIcon("../source/images/tray_icon.png"))

        self.tray_icon = QSystemTrayIcon(self)
        self.tray_icon.setIcon(QtGui.QIcon("../source/images/tray_icon.png"))
        self.tray_icon.activated.connect(self.showFromTray)

        show_action = QAction("Show", self)
        quit_action = QAction("Exit", self)
        hide_action = QAction("Hide", self)
        show_action.triggered.connect(self.show)
        hide_action.triggered.connect(self.hide)
        quit_action.triggered.connect(qApp.quit)
        tray_menu = QMenu()
        tray_menu.addAction(show_action)
        tray_menu.addAction(quit_action)
        self.tray_icon.setContextMenu(tray_menu)
        self.tray_icon.show()

        central_widget = QWidget(self)
        self.grid = QGridLayout(central_widget)
        self.setCentralWidget(central_widget)

        for button_number in range(5):
            self.new_button(0, button_number, self.hero_list[button_number])

        self.new_button(1, 5, "add_more")
        self.button_list[5].clicked.connect(self.add_clicked)
        self.now_clicked = 0
        self.hero_clicked(0, self.hero_list[0])
        stereoKeyBind()

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

    def showFromTray(self, reason):
        if reason == 3:
            self.show()

    def new_button(self, i, j, name):
        self.button_list[j] = QPushButton(self)
        self.button_list[j].clicked.connect(lambda: self.hero_clicked(j, name))
        self.button_list[j].setIcon(
            QtGui.QIcon('../source/images/{}.png'.format(name)))
        self.button_list[j].setIconSize(QSize(150, 150))
        self.button_list[j].setSizePolicy(QSizePolicy.MinimumExpanding,
                                          QSizePolicy.MinimumExpanding)
        if j > 4:
            col_num = j - 5
        else:
            col_num = j
        self.grid.addWidget(self.button_list[j], i, col_num)

    def hero_clicked(self, j, name):
        self.button_list[self.now_clicked].setIcon(
            QtGui.QIcon('../source/images/{}.png'.format(
                self.hero_list[self.now_clicked])))
        self.now_clicked = j
        self.button_list[j].setIcon(
            QtGui.QIcon('../source/images/{}_active.png'.format(name)))
        self.button_list[j].setIcon(
            QtGui.QIcon('../source/images/{}_active.png'.format(name)))
        heroKeyBind(10, name)

    def add_clicked(self):
        print("add")
Beispiel #37
0
class Parse99(QApplication):

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

    def _settings_valid(self):
        valid = True
        if self.settings.get_value('general', 'first_run') is None:
            self._system_tray.showMessage(
                "Parse99",
                """It looks like this is the first time the program is being
                run. Please setup the application using the Settings option
                once you right click the system tray icon."""
            )
            self.edit_settings()
            self.settings.set_value('general', 'first_run', True)
            valid = False
        elif self.settings.get_value('general', 'eq_directory') is None:
            self._system_tray.showMessage(
                "Parse99",
                "Please enter the General settings and \
                choose the location of your Everquest Installation."
            )
            self.edit_settings()
            valid = False
        elif self.settings.get_value('characters', None) is None:
            self._system_tray.showMessage(
                "Parse99",
                "No characters have been made.  \
                Please create at least one character using settings."
            )
            self.edit_settings(tab="characters")
            valid = False
        elif self.settings.get_value('general', 'current_character') is None:
            self._system_tray.showMessage(
                "Parse99",
                "No character has been selected. \
                Please choose a character from the Character menu."
            )
            valid = False
        return valid

    def toggle(self, switch):
        if switch == 'off':
            if self._thread is not None:
                self._timer.stop()
                self._thread.stop()
                self._thread.join()
        elif switch == 'on':
            if self._settings_valid():
                characters = self.settings.get_value('characters', None)
                log_file = characters[
                    self.settings.get_value('general', 'current_character')
                ]['log_file']
                self._thread = FileReader(
                    log_file,
                    int(self.settings.get_value('general', 'parse_interval'))
                )
                self._thread.start()
                self._timer.start(
                    1000 *
                    int(self.settings.get_value('general', 'parse_interval'))
                )

    def _parse(self):
        for line in self._thread.get_new_lines():
            for plugin in self._plugins.keys():
                if self._plugins[plugin].is_active():
                    self._plugins[plugin].parse(line)

    def _get_menu(self):
        # main menu
        menu = QMenu()
        main_menu_action_group = QActionGroup(menu)
        main_menu_action_group.setObjectName("main")
        # character menu
        map_action = QAction(menu)
        map_action.setText("Toggle Map")
        main_menu_action_group.addAction(map_action)
        separator = QAction(menu)
        separator.setSeparator(True)
        main_menu_action_group.addAction(separator)
        characters_action = QAction(menu)
        characters_action.setText("Switch Characters")
        main_menu_action_group.addAction(characters_action)
        separator = QAction(menu)
        separator.setSeparator(True)
        main_menu_action_group.addAction(separator)
        settings_action = QAction(menu)
        settings_action.setText("Settings")
        main_menu_action_group.addAction(settings_action)
        quit_action = QAction(menu)
        quit_action.setText("Quit")
        main_menu_action_group.addAction(quit_action)
        menu.addActions(main_menu_action_group.actions())
        menu.triggered[QAction].connect(self._menu_actions)
        return menu

    def update_menu(self):
        self._system_tray.contextMenu().disconnect()
        self._system_tray.setContextMenu(self._get_menu())

    def _menu_actions(self, action):
        # ag = action group, at = action text
        ag = action.actionGroup().objectName()
        at = action.text().lower()
        if ag == "main":
            if at == "quit":
                try:
                    self.toggle('off')
                    self._system_tray.setVisible(False)
                    for plugin in self._plugins.keys():
                        self._plugins[plugin].close()
                    self.quit()
                except Exception as e:
                    print("menu actions, quit", e)
            elif at == "settings":
                self.edit_settings(tab="general")
            elif at == "switch characters":
                print("switch characters")
                self.edit_settings(tab="characters")
            elif at == "toggle map":
                self._plugins['maps'].toggle()

    def edit_settings(self, **kwargs):
        try:
            if not self.settings.gui.isVisible():
                self.toggle('off')
                self.settings.gui.set_show_tab(kwargs.get('tab', None))
                self.settings.gui.exec()
                self.toggle('on')
        except Exception as e:
            print("parse99.edit_settings():", e)
Beispiel #38
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

        self.delete_tray_icon()

        # 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, api_port=None):
        QMainWindow.__init__(self)

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

        self.gui_settings = QSettings()
        api_port = api_port or 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.itemSelectionChanged.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.itemSelectionChanged.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)
        self.core_manager.events_manager.credit_mining_signal.connect(
            self.on_credit_mining_error)

        # 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 delete_tray_icon(self):
        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

    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):
        self.tray_show_message(
            "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 tray_set_tooltip(self, message):
        """
        Set a tooltip message for the tray icon, if possible.

        :param message: the message to display on hover
        """
        if self.tray_icon:
            try:
                self.tray_icon.setToolTip(message)
            except RuntimeError as e:
                logging.error("Failed to set tray tooltip: %s", str(e))

    def tray_show_message(self, title, message):
        """
        Show a message at the tray icon, if possible.

        :param title: the title of the message
        :param message: the message to display
        """
        if self.tray_icon:
            try:
                self.tray_icon.showMessage(title, message)
            except RuntimeError as e:
                logging.error("Failed to set tray message: %s", str(e))

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

        self.setAcceptDrops(True)

    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
        is_writable, error = is_dir_writable(destination)
        if not is_writable:
            gui_error_message = "Insufficient write permissions to <i>%s</i> directory. Please add proper " \
                                "write permissions on the directory and add the torrent again. %s" \
                                % (destination, error)
            ConfirmationDialog.show_message(self.window(),
                                            "Download error <i>%s</i>" % uri,
                                            gui_error_message, "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")
        if self.new_version_dialog:
            self.new_version_dialog.close_dialog()
            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 not settings:
            return
        # 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.load_token_balance()
        self.trust_page.load_blocks()
        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_trustchain_statistics,
                                         capture_errors=False)

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

        self.trust_page.received_trustchain_statistics(statistics)

        statistics = statistics["statistics"]
        if 'latest_block' in statistics:
            balance = (statistics["latest_block"]["transaction"]["total_up"] -
                       statistics["latest_block"]["transaction"]["total_down"])
            self.set_token_balance(balance)
        else:
            self.token_balance_label.setText("0 MB")

        # If trust page is currently visible, then load the graph as well
        if self.stackedWidget.currentIndex() == PAGE_TRUST:
            self.trust_page.load_blocks()

    def set_token_balance(self, balance):
        if abs(balance) > 1024**4:  # Balance is over a TB
            balance /= 1024.0**4
            self.token_balance_label.setText("%.1f TB" % balance)
        elif abs(balance) > 1024**3:  # Balance is over a GB
            balance /= 1024.0**3
            self.token_balance_label.setText("%.1f GB" % balance)
        else:
            balance /= 1024.0**2
            self.token_balance_label.setText("%d MB" % balance)

    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)
        add_mdblob_action = QAction('Import Tribler metadata from file', 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)
        add_mdblob_action.triggered.connect(self.on_add_mdblob_browse_file)

        menu.addAction(browse_files_action)
        menu.addAction(browse_directory_action)
        menu.addAction(add_url_action)
        menu.addAction(add_mdblob_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 on_add_mdblob_browse_file(self):
        filenames = QFileDialog.getOpenFileNames(
            self, "Please select the .mdblob file", QDir.homePath(),
            "Tribler metadata files (*.mdblob)")
        if len(filenames[0]) > 0:
            for filename in filenames[0]:
                self.pending_uri_requests.append(u"file:%s" % filename)
            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):
            # If tribler settings is not available, fetch the settings and inform the user to try again.
            if not self.tribler_settings:
                self.fetch_settings()
                ConfirmationDialog.show_error(
                    self, "Download Error",
                    "Tribler settings is not available yet. "
                    "Fetching it now. Please try again later.")
                return
            # Clear any previous dialog if exists
            if self.dialog:
                self.dialog.close_dialog()
                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.close_dialog()
        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.close_dialog()
            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()

        if self.video_player_page.isVisible():
            # If we're adding a torrent from the video player page, go to the home page.
            # This is necessary since VLC takes the screen and the popup becomes invisible.
            self.clicked_menu_button_home()

        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.close_dialog()
            self.dialog = None

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

    def on_download_added(self, result):
        if not result:
            return
        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_credit_mining_error(self, error):
        ConfirmationDialog.show_error(self, "Credit Mining Error",
                                      error[u'message'])

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

            self.delete_tray_icon()
            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()

            # Stop the token balance timer
            if self.token_refresh_timer:
                self.token_refresh_timer.stop()

    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 dragEnterEvent(self, e):
        file_urls = [_qurl_to_path(url) for url in e.mimeData().urls()
                     ] if e.mimeData().hasUrls() else []

        if any(os.path.isfile(filename) for filename in file_urls):
            e.accept()
        else:
            e.ignore()

    def dropEvent(self, e):
        file_urls = ([(_qurl_to_path(url), url.toString())
                      for url in e.mimeData().urls()]
                     if e.mimeData().hasUrls() else [])

        for filename, fileurl in file_urls:
            if os.path.isfile(filename):
                self.start_download_from_uri(fileurl)

        e.accept()

    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 #39
0
class NetDotTsinghuaApplication(QApplication):
    """NetDotTsinghuaApplication"""
    start_worker = pyqtSignal()
    username_changed = pyqtSignal(str)
    update_all = pyqtSignal()

    def __init__(self, argv):
        super().__init__(argv)
        icon = QIcon(":/icon.png")

        self.setQuitOnLastWindowClosed(False)  # Run without windows.
        self.setWindowIcon(icon)
        self.account_setting_dialog = None

        self.worker = Worker()
        self.worker_thread = QThread()
        self.worker.moveToThread(self.worker_thread)

        # For convenience.
        worker = self.worker
        config = worker.config
        acc = worker.account

        # Set up tray menu.
        self.tray = QSystemTrayIcon(icon, self)
        self.tray_menu = QMenu()

        # Status section.
        self.status_action = self.add_unabled_action()
        self.status_changed(worker.account.status)
        self.status = acc.status
        self.last_session = None

        # Account info section.
        self.tray_menu.addSeparator()
        self.username_action = self.add_unabled_action()
        self.usage_action = self.add_unabled_action()
        self.balance_action = self.add_unabled_action()
        self.refresh_username(config['username'])
        self.refresh_account_info(None, None)

        # Sessions section.
        self.sessions = []
        self.session_menus = []
        self.last_check = None

        self.tray_menu.addSeparator()
        self.sessions_title_action = self.add_unabled_action()
        self.last_check_action = self.add_unabled_action()

        self.refresh_sessions([])

        # Actions.
        self.tray_menu.addSeparator()
        self.tray_menu.addAction('上线').triggered.connect(acc.login)
        self.tray_menu.addAction('下线').triggered.connect(acc.logout)
        self.tray_menu.addAction('现在刷新').triggered.connect(acc.update_all)

        # Config section.
        self.tray_menu.addSeparator()
        self.auto_manage_action = self.tray_menu.addAction('自动管理')
        self.auto_manage_action.setCheckable(True)
        self.auto_manage_action.setChecked(config['auto_manage'])
        self.auto_manage_action.toggled.connect(worker.auto_manage_changed)

        self.account_setting_action = self.tray_menu.addAction('账号设置...')
        self.account_setting_action.triggered.connect(self.account_setting)

        # About.
        self.tray_menu.addSeparator()
        self.tray_menu.addAction('关于').triggered.connect(self.show_about)

        # Quit.
        self.tray_menu.addSeparator()
        self.tray_menu.addAction('退出').triggered.connect(self.quit)

        self.tray.setContextMenu(self.tray_menu)
        self.tray.show()

        # Connect signals.
        self.start_worker.connect(worker.setup)
        self.username_changed.connect(self.refresh_username)
        self.username_changed.connect(worker.username_changed)
        self.update_all.connect(acc.update_all)

        acc.status_changed.connect(self.status_changed)
        acc.info_updated.connect(self.refresh_account_info)
        acc.last_session_updated.connect(self.last_session_changed)
        acc.sessions_updated.connect(self.refresh_sessions)

        # About to show.
        self.tray_menu.aboutToShow.connect(self.update_time)
        self.tray_menu.aboutToShow.connect(self.refresh_status)

    def add_unabled_action(self, text=''):
        action = self.tray_menu.addAction(text)
        action.setEnabled(False)
        return action

    def exec(self):
        self.worker_thread.start()
        self.start_worker.emit()  # Start timers & check status.

        logging.debug('GUI thread enters event loop')
        return super().exec()

    def refresh_status(self):
        logging.debug('Refreshing status in the menu')

        s = STATUS_STR[self.status]
        # Show session usage if possible.
        if self.last_session and self.status in ('ONLINE',
                                                 'OTHERS_ACCOUNT_ONLINE'):
            s = s + ' - ' + _usage_str(self.last_session.byte)

        self.status_action.setText(s)

    def refresh_username(self, username):
        logging.debug('Refreshing username in the menu')
        if not username:
            self.username_action.setText('未设置账号')
            self.usage_action.setVisible(False)
            self.balance_action.setVisible(False)
        else:
            self.username_action.setText(username)
            self.usage_action.setVisible(True)
            self.balance_action.setVisible(True)

    def refresh_account_info(self, balance, byte):
        logging.debug('Refreshing account info section in the menu')

        self.usage_action.setText('本月流量:{}'.format(_usage_str(byte)))
        self.balance_action.setText('当前余额:{}'.format(_balance_str(balance)))

    def status_changed(self, status):
        # Show tray message.
        if status == 'ONLINE':
            self.tray.showMessage('当前在线', '本人账号在线')
        elif status == 'OTHERS_ACCOUNT_ONLINE':
            self.tray.showMessage('当前在线', '他人账号在线')
        elif status == 'OFFLINE':
            self.tray.showMessage('当前离线', '可以登录校园网')

        self.status = status

    def last_session_changed(self, session):
        self.last_session = session

    def refresh_sessions(self, sessions):
        logging.debug('Refreshing sessions section in the menu')

        self.sessions = sessions
        self.last_check = datetime.now()

        if len(sessions):
            self.sessions_title_action.setText('当前在线')
        else:
            self.sessions_title_action.setText('无设备在线')

        # Remove old actions
        for menu in self.session_menus:
            self.tray_menu.removeAction(menu.menuAction())
        self.session_menus.clear()

        # Add new actions.
        for session in sessions:
            menu = SessionMenu(self.worker.account, session)
            self.tray_menu.insertMenu(self.last_check_action,
                                      menu).setText(session.device_name)
            self.session_menus.append(menu)

    def update_time(self):
        self.last_check_action.setText(
            '上次更新:{}'.format(_time_passed_str(self.last_check)))

    @pyqtSlot()
    def account_setting(self):
        if self.account_setting_dialog is None:
            self.account_setting_dialog = AccountSettingDialog()
            existed = False
        else:
            existed = True

        dialog = self.account_setting_dialog
        dialog.show()
        dialog.raise_()
        dialog.activateWindow()

        if existed:  # Avoid multiple dialogs.
            return

        if dialog.exec():
            username = dialog.username.text()
            # Set password if needed.
            if username:
                acc = Account(username)
                acc.password = dialog.password.text()

            if username != self.worker.account.username:
                # If username changed, emit signal and clear current account info.
                self.username_changed.emit(username)
                self.refresh_account_info(None, None)
            else:
                # Update all because password might has changed.
                self.update_all.emit()

        self.account_setting_dialog = None

    @pyqtSlot()
    def show_about(self):
        msg = """<p><b>net.tsinghua {}</b><br>
<small>Copyright Ⓒ 2015 Thomas Lee</small></p>

<p><a href="https://github.com/ThomasLee969/net.tsinghua">Github 主页</a>
</p>""".format(__VERSION__)

        QMessageBox.about(None, '关于 net.tsinghua', msg)
Beispiel #40
0
class Window(QWidget):
    w, h = 0, 0
    layoutmode = False
    movetile = None
    ismin = False
    tiles = []

    def __init__(self):
        super().__init__()
        if TRAY_ICON:
            self.inittray()
        self.setWindowTitle('Tilemenu')
        self.initgeometry()
        self.inittiles()
        self.mouseReleaseEvent = self.onClicked
        self.initstyle()
        self.show()

    def inittiles(self):
        for t in TILESET:
            newtile = t.tile(t.xy, parent=self, extra=t.extra)
            self.tiles.append(newtile)

    def testinittiles(self):
        for i in range(self.w):
            for j in range(self.h):
                self.tiles.append(RndColor((i, j), parent=self))
        self.tiles[0] = Close((-1, 0), parent=self)
        self.tiles[1] = DesktopEntry((1, 1), (2, "gedit --new-window", "icons/gedit.png"), self)

    def initgeometry(self):
        self.w = (QApplication.desktop().width() - XPOS - XOFF) // (SUNIT)
        self.h = (QApplication.desktop().height() - YPOS - YOFF) // (SUNIT)
        if ALIGN == 'LB':
            self.dw = 0
            self.dh = (QApplication.desktop().height() - YPOS - YOFF) % (SUNIT)
        elif ALIGN == 'RT':
            self.dw = (QApplication.desktop().width() - XPOS - XOFF) % (SUNIT)
            self.dh = 0
        elif ALIGN == 'RB':
            self.dw = (QApplication.desktop().width() - XPOS - XOFF) % (SUNIT)
            self.dh = (QApplication.desktop().height() - YPOS - YOFF) % (SUNIT)
        else:
            self.dw = 0
            self.dh = 0
        self.usegeometry()

    def usegeometry(self):
        self.move(XPOS + self.dw, YPOS + self.dh)
        self.setFixedSize(self.w * SUNIT + SPC, self.h * SUNIT + SPC)

    def hideAll(self):
        for i in range(1, len(self.tiles)):
            self.tiles[i].hide()

    def showAll(self):
        for i in range(1, len(self.tiles)):
            self.tiles[i].show()

    def initstyle(self):
        self.setWindowIcon(QIcon('icon.gif'))
        self.setcolort(BACKGROUND_COLOR)
        if not TESTMODE:
            self.setWindowFlags(Qt.FramelessWindowHint |
                                Qt.NoDropShadowWindowHint |
                                Qt.WindowStaysOnBottomHint)
        elif TESTMODE == 1:
            self.setWindowFlags(Qt.FramelessWindowHint |
                                Qt.NoDropShadowWindowHint)
        elif TESTMODE == 2:
            pass
        if BACKGROUND_TRANSPARENT:
            self.setAutoFillBackground(False)
            self.setAttribute(Qt.WA_TranslucentBackground)
        if FULSCREEN:
            self.showFullScreen()

    def setcolort(self, colort):
        self.colort = colort
        self.setStyleSheet("background-color:rgba" + self.colort + ";")

    def onClicked(self, event):
        if self.layoutmode and self.movetile != None:
            self.movetile.x = (self.cursor().pos().x() - self.x()) // SUNIT
            self.movetile.y = (self.cursor().pos().y() - self.y()) // SUNIT
            self.movetile.move(self.movetile.x * SUNIT + SPC, self.movetile.y * SUNIT + SPC)
            self.movetile.sethold(0, self.movetile.dragholdcolor)
            self.movetile.movemode = False
            self.movetile = None

    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Delete and self.layoutmode:
            self.movetile.close()
            self.tiles.remove(self.movetile)
            self.movetile = None
            self.repaint()
        else:
            pass

    def genImports(self, filename):
        out = open(filename, 'w')
        out.write(IMPORTS_BEGIN)
        for tile in self.tiles:
            out.write(tile.getParamsStr())

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

    def mClose(self):
        self.genImports('imports.py')
        self.close()

    def mLayoutMode(self):
        self.layoutmode = not self.layoutmode
        # if self.layoutmode:
        #     pass
        #     #self.lmaction.setText('[*] Layout mode')
        # else:
        #     pass
        #     #self.lmaction.setText('[ ] Layout mode')

    def mAddTile(self):
        d = addTileDialog()
        if d.exec() == QDialog.Accepted:
            tile = d.output[0]
            xy = (d.output[1][0], d.output[1][1])
            ext = d.output[1][2]
            newtile = tile(xy, parent=self, extra=ext)
            self.tiles.append(newtile)
            self.tiles[-1].show()
            self.repaint()
        else:
            pass

    def mAddDE(self):
        d = addDEDialog()
        if d.exec() == QDialog.Accepted:
            tile = DesktopEntry
            xy = (-2, -2)
            ext = d.output
            newtile = tile(xy, parent=self, extra=ext)
            self.tiles.append(newtile)
            self.tiles[-1].show()
            self.repaint()
        else:
            pass

    def trayclicked(self, reason):
        if reason == QSystemTrayIcon.Context:
            if self.layoutmode:
                self.lmaction.setText('[*] Layout mode')
            else:
                self.lmaction.setText('[ ] Layout mode')
        if reason == QSystemTrayIcon.Trigger:
            if self.ismin:
                self.ismin = False
                if FULSCREEN:
                    self.showFullScreen()
                else:
                    self.show()
            else:
                self.ismin = True
                self.hide()
Beispiel #41
0
class Example(QMainWindow):

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

        self.initUI()
        self.trayIcon = None
        self.kill = False
        self.setupTrayIcon()
        try:
            cherrypy_server.start_server()
        except Exception as e:
            logging.getLogger(__name__).warn("Problem starting print server! {}".format(e))
            traceback.print_exc()

    def reallyClose(self):
        self.kill = True
        self.close()

    def openConsole(self):
        from web.server import cfg
        url = r'https://localhost:{}'.format(cfg.port)
        webbrowser.open(url)


    def initUI(self):

        openAction = QAction("&Open Console", self)
        openAction.setShortcut('Ctrl+O')
        openAction.setStatusTip('Open Console')
        openAction.triggered.connect(self.openConsole)

        exitAction = QAction(QIcon(os.path.join('web', 'static', 'img', 'exit-icon-3.png')), '&Exit', self)
        exitAction.setShortcut('Ctrl+Q')
        exitAction.setStatusTip('Exit application')
        exitAction.triggered.connect(self.reallyClose)

        self.statusBar()

        menubar = self.menuBar()
        fileMenu = menubar.addMenu('&File')
        fileMenu.addAction(openAction)
        fileMenu.addSeparator()
        fileMenu.addAction(exitAction)

        wrapper = QWidget()
        txt = QTextEdit()
        txt.setReadOnly(True)
        txt.setLineWrapMode(QTextEdit.NoWrap)
        txt.setUndoRedoEnabled(False)
        txt.setAcceptDrops(False)
        txt.setAcceptRichText(False)

        font = txt.font()
        font.setFamily("Courier")
        font.setPointSize(9)
        txt.setFont(font)

        policy = txt.sizePolicy()
        policy.setVerticalStretch(1)
        txt.setSizePolicy(policy)

        layout = QGridLayout()
        layout.addWidget(txt)
        wrapper.setLayout(layout)

        self.setCentralWidget(wrapper)

        def _write(s):
            txt.moveCursor(QTextCursor.End)
            txt.insertPlainText(str(s))
            txt.moveCursor(QTextCursor.End)

        setup_logging(_write)

        self.setGeometry(300, 300, 800, 600)
        self.setWindowTitle('Antix Print Server')

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

    def closeEvent(self, evnt):
        if self.kill:
            self.trayIcon.hide()
            try:
                cherrypy_server.stop_server()
            except Exception as e:
                logging.getLogger(__name__).warn("Problem stopping server! {}".format(e))
            qApp.exit(0)
        else:
            evnt.ignore()
            self.hide()
Beispiel #42
0
class SVApplication(QApplication):

    # Signals need to be on a QObject
    create_new_window_signal = pyqtSignal(str, object)
    cosigner_received_signal = pyqtSignal(object, object)
    labels_changed_signal = pyqtSignal(object, object)
    window_opened_signal = pyqtSignal(object)
    window_closed_signal = pyqtSignal(object)
    # Async tasks
    async_tasks_done = pyqtSignal()
    # Logging
    new_category = pyqtSignal(str)
    new_log = pyqtSignal(object)
    # Preferences updates
    fiat_ccy_changed = pyqtSignal()
    custom_fee_changed = pyqtSignal()
    op_return_enabled_changed = pyqtSignal()
    num_zeros_changed = pyqtSignal()
    base_unit_changed = pyqtSignal()
    fiat_history_changed = pyqtSignal()
    fiat_balance_changed = pyqtSignal()
    update_check_signal = pyqtSignal(bool, object)
    # Contact events
    contact_added_signal = pyqtSignal(object, object)
    contact_removed_signal = pyqtSignal(object)
    identity_added_signal = pyqtSignal(object, object)
    identity_removed_signal = pyqtSignal(object, object)

    def __init__(self, argv):
        super().__init__(argv)
        self.windows = []
        self.log_handler = SVLogHandler()
        self.log_window = None
        self.net_dialog = None
        self.timer = QTimer()
        self.exception_hook = None
        # A floating point number, e.g. 129.1
        self.dpi = self.primaryScreen().physicalDotsPerInch()

        # init tray
        self.dark_icon = app_state.config.get("dark_icon", False)
        self.tray = QSystemTrayIcon(self._tray_icon(), None)
        self.tray.setToolTip('ElectrumSV')
        self.tray.activated.connect(self._tray_activated)
        self._build_tray_menu()
        self.tray.show()

        # FIXME Fix what.. what needs to be fixed here?
        set_language(app_state.config.get('language', get_default_language()))

        logs.add_handler(self.log_handler)
        self._start()

    def _start(self):
        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-sv.desktop')
        self.setWindowIcon(read_QIcon("electrum-sv.png"))
        self.installEventFilter(OpenFileEventFilter(self.windows))
        self.create_new_window_signal.connect(self.start_new_window)
        self.async_tasks_done.connect(app_state.async_.run_pending_callbacks)
        self.num_zeros_changed.connect(
            partial(self._signal_all, 'on_num_zeros_changed'))
        self.fiat_ccy_changed.connect(
            partial(self._signal_all, 'on_fiat_ccy_changed'))
        self.base_unit_changed.connect(
            partial(self._signal_all, 'on_base_unit_changed'))
        self.fiat_history_changed.connect(
            partial(self._signal_all, 'on_fiat_history_changed'))
        # Toggling of showing addresses in the fiat preferences.
        self.fiat_balance_changed.connect(
            partial(self._signal_all, 'on_fiat_balance_changed'))
        self.update_check_signal.connect(
            partial(self._signal_all, 'on_update_check'))
        ColorScheme.update_from_widget(QWidget())

    def _signal_all(self, method, *args):
        for window in self.windows:
            getattr(window, method)(*args)

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

    def close_window(self, window):
        app_state.daemon.stop_wallet_at_path(
            window.parent_wallet.get_storage_path())
        self.windows.remove(window)
        self.window_closed_signal.emit(window)
        self._build_tray_menu()
        # save wallet path of last open window
        if not self.windows:
            app_state.config.save_last_wallet(window.parent_wallet)
            self._last_window_closed()

    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.parent_wallet.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 ElectrumSV"), self._close)
        self.tray.setContextMenu(m)

    def _tray_icon(self):
        if self.dark_icon:
            return read_QIcon('electrumsv_dark_icon.png')
        else:
            return read_QIcon('electrumsv_light_icon.png')

    def _toggle_tray_icon(self):
        self.dark_icon = not self.dark_icon
        app_state.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 new_window(self, path, uri=None):
        # Use a signal as can be called from daemon thread
        self.create_new_window_signal.emit(path, uri)

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

    def show_log_viewer(self):
        if self.log_window is None:
            self.log_window = SVLogWindow(None, self.log_handler)
        self.log_window.show()

    def _last_window_closed(self):
        for dialog in (self.net_dialog, self.log_window):
            if dialog:
                dialog.accept()

    def _maybe_choose_server(self):
        # Show network dialog if config does not exist
        if app_state.daemon.network and app_state.config.get(
                'auto_connect') is None:
            try:
                wizard = InstallWizard()
                wizard.init_network(app_state.daemon.network)
                wizard.terminate()
            except Exception as e:
                if not isinstance(e, (UserCancelled, GoBack)):
                    logger.exception("")
                self.quit()

    def on_label_change(self, wallet: Abstract_Wallet, name: str,
                        text: str) -> None:
        self.label_sync.set_label(wallet, name, text)

    def _create_window_for_wallet(self, parent_wallet: ParentWallet):
        w = ElectrumWindow(parent_wallet)
        self.windows.append(w)
        self._build_tray_menu()
        self._register_wallet_events(parent_wallet)
        self.window_opened_signal.emit(w)
        return w

    def _register_wallet_events(self, wallet: ParentWallet) -> None:
        wallet.contacts._on_contact_added = self._on_contact_added
        wallet.contacts._on_contact_removed = self._on_contact_removed
        wallet.contacts._on_identity_added = self._on_identity_added
        wallet.contacts._on_identity_removed = self._on_identity_removed

    def _on_identity_added(self, contact: ContactEntry,
                           identity: ContactIdentity) -> None:
        self.identity_added_signal.emit(contact, identity)

    def _on_identity_removed(self, contact: ContactEntry,
                             identity: ContactIdentity) -> None:
        self.identity_removed_signal.emit(contact, identity)

    def _on_contact_added(self, contact: ContactEntry,
                          identity: ContactIdentity) -> None:
        self.contact_added_signal.emit(contact, identity)

    def _on_contact_removed(self, contact: ContactEntry) -> None:
        self.contact_removed_signal.emit(contact)

    def get_wallet_window(self, path: str) -> Optional[ElectrumWindow]:
        for w in self.windows:
            if w.parent_wallet.get_storage_path() == path:
                return w

    def get_wallet_window_by_id(self,
                                wallet_id: int) -> Optional[ElectrumWindow]:
        for w in self.windows:
            for child_wallet in w.parent_wallet.get_child_wallets():
                if child_wallet.get_id() == wallet_id:
                    return w

    def start_new_window(self, path, uri, is_startup=False):
        '''Raises the window for the wallet if it is open.  Otherwise
        opens the wallet and creates a new window for it.'''
        for w in self.windows:
            if w.parent_wallet.get_storage_path() == path:
                w.bring_to_top()
                break
        else:
            try:
                parent_wallet = app_state.daemon.load_wallet(path, None)
                if not parent_wallet:
                    wizard = InstallWizard()
                    try:
                        if wizard.select_storage(path, is_startup=is_startup):
                            parent_wallet = wizard.run_and_get_wallet()
                    except UserQuit:
                        pass
                    except UserCancelled:
                        pass
                    except GoBack as e:
                        logger.error(
                            '[start_new_window] Exception caught (GoBack) %s',
                            e)
                    finally:
                        wizard.terminate()
                    if not parent_wallet:
                        return
                    app_state.daemon.start_wallet(parent_wallet)
            except Exception as e:
                logger.exception("")
                error_str = str(e)
                if '2fa' in error_str:
                    error_str = _('2FA wallets are not supported')
                msg = '\n'.join(
                    (_('Cannot load wallet "{}"').format(path), error_str))
                MessageBox.show_error(msg)
                return
            w = self._create_window_for_wallet(parent_wallet)
        if uri:
            w.pay_to_URI(uri)
        w.bring_to_top()
        w.setWindowState(w.windowState() & ~QtCore.Qt.WindowMinimized
                         | QtCore.Qt.WindowActive)

        # this will activate the window
        w.activateWindow()
        return w

    def update_check(self) -> None:
        if (not app_state.config.get('check_updates', True)
                or app_state.config.get("offline", False)):
            return

        def f():
            import requests
            try:
                response = requests.request(
                    'GET',
                    "https://electrumsv.io/release.json",
                    headers={'User-Agent': 'ElectrumSV'},
                    timeout=10)
                result = response.json()
                self._on_update_check(True, result)
            except Exception:
                self._on_update_check(False, sys.exc_info())

        t = threading.Thread(target=f)
        t.setDaemon(True)
        t.start()

    def _on_update_check(self, success: bool, result: dict) -> None:
        if success:
            when_checked = datetime.datetime.now().astimezone().isoformat()
            app_state.config.set_key('last_update_check', result)
            app_state.config.set_key('last_update_check_time', when_checked,
                                     True)
        self.update_check_signal.emit(success, result)

    def initial_dialogs(self) -> None:
        '''Suppressible dialogs that are shown when first opening the app.'''
        dialogs.show_named('welcome-ESV-1.3.0a1')
        # This needs to be reworked or removed, as non-advanced users aren't sure whether
        # it is safe, and likely many people aren't quite sure if it should be done.
        # old_items = []
        # headers_path = os.path.join(app_state.config.path, 'blockchain_headers')
        # if os.path.exists(headers_path):
        #     old_items.append((_('the file "blockchain_headers"'), os.remove, headers_path))
        # forks_dir = os.path.join(app_state.config.path, 'forks')
        # if os.path.exists(forks_dir):
        #     old_items.append((_('the directory "forks/"'), shutil.rmtree, forks_dir))
        # if old_items:
        #     main_text = _('Delete the following obsolete items in <br>{}?'
        #                   .format(app_state.config.path))
        #     info_text = '<ul>{}</ul>'.format(''.join('<li>{}</li>'.format(text)
        #                                              for text, *rest in old_items))
        #     if dialogs.show_named('delete-obsolete-headers', main_text=main_text,
        #                           info_text=info_text):
        #         try:
        #             for _text, rm_func, *args in old_items:
        #                 rm_func(*args)
        #         except OSError as e:
        #             logger.exception('deleting obsolete files')
        #             dialogs.error_dialog(_('Error deleting files:'), info_text=str(e))

    def event_loop_started(self) -> None:
        self.cosigner_pool = CosignerPool()
        self.label_sync = LabelSync()
        if app_state.config.get("show_crash_reporter", default=True):
            self.exception_hook = Exception_Hook(self)
        self.timer.start()
        signal.signal(signal.SIGINT, lambda *args: self.quit())
        self.initial_dialogs()
        self._maybe_choose_server()
        app_state.config.open_last_wallet()
        path = app_state.config.get_wallet_path()
        if not self.start_new_window(
                path, app_state.config.get('url'), is_startup=True):
            self.quit()

    def run_app(self) -> None:
        when_started = datetime.datetime.now().astimezone().isoformat()
        app_state.config.set_key('previous_start_time',
                                 app_state.config.get("start_time"))
        app_state.config.set_key('start_time', when_started, True)
        self.update_check()

        threading.current_thread().setName('GUI')
        self.timer.setSingleShot(False)
        self.timer.setInterval(500)  # msec
        self.timer.timeout.connect(app_state.device_manager.timeout_clients)

        QTimer.singleShot(0, self.event_loop_started)
        self.exec_()

        logs.remove_handler(self.log_handler)
        # 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.sendEvent(self.clipboard(), event)
        self.tray.hide()

    def run_coro(self, coro, *args, on_done=None):
        '''Run a coroutine.  on_done, if given, is passed the future containing the reuslt or
        exception, and is guaranteed to be called in the context of the GUI thread.
        '''
        def task_done(future):
            self.async_tasks_done.emit()

        future = app_state.async_.spawn(coro, *args, on_done=on_done)
        future.add_done_callback(task_done)
        return future

    def run_in_thread(self, func, *args, on_done=None):
        '''Run func(*args) in a thread.  on_done, if given, is passed the future containing the
        reuslt or exception, and is guaranteed to be called in the context of the GUI
        thread.
        '''
        return self.run_coro(run_in_thread, func, *args, on_done=on_done)
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 #44
0
class Alfred(QMainWindow):
    def __init__(self):
        QMainWindow.__init__(self)
        self.show_splash()
        self.init_tray_icon()

        self.web_bridge = WebBridge()
        self.main_widget = MainWidget(self.web_bridge)
        self.main_widget.text_changed.connect(self.process_text)

        self.main_window = MainWindow()
        self.main_window.setWindowIcon(QIcon(":/icon_alfred"))
        self.modules_mgr = ModuleManager.instance()

        self.main_window.signal_list_modules.connect(
            self.modules_mgr.fetch_data)

        self.modules_mgr.conn_err.connect(
            self.main_window.handle_connection_error)

        self.modules_mgr.signal_train.connect(Classifier().train)

        Classifier().train_thread.finished.connect(
            self.show_widgets_if_visible)

        self.modules_mgr.data_fetched.connect(self.main_window.list_modules)
        self.curr_module = None
        self.widget_man = WidgetManager(self.main_widget)

        self.curr_sentence = ''

        self.web_bridge.signal_wrong_module.connect(self.add_new_sentence)

    def show_splash(self):
        image = QPixmap(':/loading_image')
        splash = QSplashScreen(image)
        splash.setAttribute(Qt.WA_DeleteOnClose)
        splash.setMask(image.mask())
        splash.show()

        QCoreApplication.processEvents()
        Parser([])

        splash.finish(self)

    def init_tray_icon(self):
        self.show_widget = QAction("Show Main Widget", self)
        self.restore_action = QAction("Show Main Window", self)
        self.quit_action = QAction("Quit", self)

        self.show_widget.triggered.connect(self.show_main_widget)
        self.restore_action.triggered.connect(self.show_main_window)
        self.quit_action.triggered.connect(QCoreApplication.instance().quit)

        tray_icon_menu = QMenu(self)
        tray_icon_menu.addAction(self.show_widget)
        tray_icon_menu.addAction(self.restore_action)
        tray_icon_menu.addSeparator()
        tray_icon_menu.addAction(self.quit_action)

        self.tray_icon = QSystemTrayIcon(QIcon(":/icon_alfred"), self)
        self.tray_icon.setContextMenu(tray_icon_menu)
        self.tray_icon.show()
        self.tray_icon.showMessage('Alfred', 'I am alive',
                                   QSystemTrayIcon.Information, 5000)
        self.tray_icon.activated.connect(self.tray_icon_activated)

    def tray_icon_activated(self, reason):
        if reason == QSystemTrayIcon.Trigger:
            self.show_main_widget()

    def show_main_widget(self):
        self.main_widget.lineEdit.setText("")
        if Classifier().train_thread.isRunning():
            self.main_widget.set_status_icon_busy(True)
            self.main_widget.show_busy_state_widget()
            self.main_widget.showFullScreen()
        else:
            self.main_widget.clear_view()

            if self.modules_mgr.modules_count == 0:
                self.main_widget.show_no_modules_view()
            else:
                self.widget_man.prepare_widgets()

            self.main_widget.showFullScreen()

    def show_widgets_if_visible(self):
        Logger().info("Showing widgets")
        if self.main_widget.isVisible():
            self.widget_man.prepare_widgets()

    def show_main_window(self):
        self.main_window.show()

    @pyqtSlot('QString')
    def process_text(self, text):
        if Classifier().train_thread.isRunning():
            self.main_widget.show_busy_state_widget()
            return

        self.curr_sentence = text
        self.main_widget.set_status_icon_busy(True)

        module_id = Classifier().predict(text)
        module_info = ModuleInfo.find_by_id(module_id)

        if not module_info:
            return

        self.main_widget.show_module_running_widget(module_info.name)

        entities_dict = {}
        try:
            needed_entities = module_info.needed_entities()
            Parser([]).set_entities_types(needed_entities)
            entities_dict = Parser([]).parse(text)
        except:
            pass
            # Logger().err('Could not extract named entities')
        Logger().info("Extracted Entities are {}".format(entities_dict))

        self.set_module(module_info, entities_dict)
        self.main_widget.set_status_icon_busy(False)

    @pyqtSlot()
    def add_new_sentence(self):
        self.main_widget.hide()
        modules = dict(map(lambda m: (m.name, m), ModuleInfo.all()))
        item, ok = QInputDialog.getItem(self,
                                        self.curr_sentence, "Select Module:",
                                        modules.keys(), 0, False)
        print(item, ok)

        mi = modules[item]
        if ok and self.curr_module.module_info.id != mi.id:
            extra_train_file_path = mi.extra_training_sentences_json_file_path(
            )

            sentences = []
            if os.path.exists(extra_train_file_path):
                with open(extra_train_file_path, 'r+') as extra_train_file:
                    sentences = json.load(extra_train_file)

            sentences.append(self.curr_sentence)

            with open(extra_train_file_path, 'w') as extra_train_file:
                extra_train_file.write(json.dumps(sentences))
                extra_train_file.truncate()

            Classifier().train()

            self.show_main_widget()
        else:
            self.main_widget.showFullScreen()
            self.main_widget.lineEdit.setText(self.curr_sentence)

    def set_module(self, module_info, entities_dict):
        if module_info.root() in sys.path:
            sys.path.remove(module_info.root())
        sys.path.append(module_info.root())

        package_name = module_info.package_name()
        module = __import__('{}.{}'.format(package_name, package_name),
                            fromlist=package_name)

        if module.__name__ in sys.modules:
            reload(module)

        if self.curr_module is not None:
            self.web_bridge.signal_event_triggered.disconnect(
                self.curr_module.event_triggered)
            self.web_bridge.signal_form_submitted.disconnect(
                self.curr_module.form_submitted)

        self.curr_module = getattr(module,
                                   module_info.class_name())(module_info,
                                                             entities_dict)

        self.curr_module.signal_view_changed.connect(self.main_widget.set_view)

        self.curr_module.signal_remove_component.connect(
            self.main_widget.remove_component)
        self.curr_module.signal_append_content.connect(
            self.main_widget.append_content)

        self.web_bridge.signal_event_triggered.connect(
            self.curr_module.event_triggered)
        self.web_bridge.signal_form_submitted.connect(
            self.curr_module.form_submitted)

        self.curr_module.start()
Beispiel #45
0
class DemoImpl(QDialog):
	
	
    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 changeEvent(self, event):
        if event.type() == QEvent.WindowStateChange:
            if self.isMinimized():
                self.hide()
                self.trayicon.show()
                if self.traymsg_firstshow:
                    self.trayicon.showMessage('', 'YYDict is running', QSystemTrayIcon.Information, 2000)
                    self.traymsg_firstshow = False
            else:
                self.trayicon.hide()
				
    def closeEvent(self, event):
        self.trayicon.hide()

    @pyqtSlot(QSystemTrayIcon.ActivationReason)
    def on_systemTrayIcon_activated(self, reason):
        if reason == QSystemTrayIcon.DoubleClick:
            self.activateWindow()	
            self.showNormal()



    @pyqtSlot()
    def searchword(self):
        word = self.lineEdit.text().strip()
        if not len(word):
            self.plainTextEdit.setPlainText('')
            self.lineEdit.setFocus(Qt.MouseFocusReason)
        else:
            self.workThread = WorkThread(word, self.comboBox.currentIndex())
            self.workThread.received.connect(self.updateResult)
            self.workThread.start()
            self.workThread.wait()


    @pyqtSlot('QString')
    def updateResult(self, rt):
        self.plainTextEdit.setPlainText(rt)
        self.lineEdit.selectAll()
        self.lineEdit.setFocus(Qt.MouseFocusReason)
Beispiel #46
0
class ElectrumGui(PrintError):
    @profiler
    def __init__(self, config, daemon, plugins):
        set_language(config.get('language', get_default_language()))
        # 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('exos-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.print_error('Error setting dark theme: {}'.format(
                    repr(e)))
        # 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_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'''
        try:
            wallet = self.daemon.load_wallet(path, None)
        except BaseException as e:
            traceback.print_exc(file=sys.stdout)
            d = QMessageBox(QMessageBox.Warning, _('Error'),
                            _('Cannot load wallet') + ' (1):\n' + str(e))
            d.exec_()
            if app_is_starting:
                # do not return so that the wizard can appear
                wallet = None
            else:
                return
        if not wallet:
            wizard = InstallWizard(self.config, self.app, self.plugins, None)
            try:
                if wizard.select_storage(path, self.daemon.get_wallet):
                    wallet = wizard.run_and_get_wallet()
            except UserCancelled:
                pass
            except GoBack as e:
                self.print_error(
                    '[start_new_window] Exception caught (GoBack)', e)
            except (WalletFileException, BitcoinException) as e:
                traceback.print_exc(file=sys.stderr)
                d = QMessageBox(QMessageBox.Warning, _('Error'),
                                _('Cannot load wallet') + ' (2):\n' + str(e))
                d.exec_()
                return
            finally:
                wizard.terminate()
            if not wallet:
                return

            if not self.daemon.get_wallet(wallet.storage.path):
                # wallet was not in memory
                wallet.start_network(self.daemon.network)
                self.daemon.add_wallet(wallet)
        try:
            for w in self.windows:
                if w.wallet.storage.path == wallet.storage.path:
                    break
            else:
                w = self.create_window_for_wallet(wallet)
        except BaseException as e:
            traceback.print_exc(file=sys.stdout)
            d = QMessageBox(
                QMessageBox.Warning, _('Error'),
                _('Cannot create window for wallet') + ':\n' + str(e))
            d.exec_()
            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:
            w.pay_to_URI(uri)
        w.bring_to_top()
        w.setWindowState(w.windowState() & ~QtCore.Qt.WindowMinimized
                         | QtCore.Qt.WindowActive)

        # this will activate the window
        w.activateWindow()
        return w

    def close_window(self, window):
        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,
                                       None)
                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:
            traceback.print_exc(file=sys.stdout)
            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.print_error('closing GUI')
        self.app.quit()
Beispiel #47
0
class Ui_MainWindow(object):
    def setupUi(self, mainwindow):
        icon = QtGui.QIcon()
        icon.addPixmap(QtGui.QPixmap("icone.png"), QtGui.QIcon.Normal,
                       QtGui.QIcon.Off)
        mainwindow.setObjectName("mainwindow")
        mainwindow.resize(800, 600)
        mainwindow.setMinimumSize(QtCore.QSize(800, 600))
        mainwindow.setMaximumSize(QtCore.QSize(800, 600))
        mainwindow.setWindowIcon(icon)

        self.centralwidget = QtWidgets.QWidget(mainwindow)
        self.centralwidget.setObjectName("centralwidget")

        self.botaosair = QtWidgets.QToolButton(self.centralwidget)
        self.botaosair.setGeometry(QtCore.QRect(20, 540, 200, 40))

        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed,
                                           QtWidgets.QSizePolicy.Fixed)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(
            self.botaosair.sizePolicy().hasHeightForWidth())

        self.botaosair.setSizePolicy(sizePolicy)
        self.botaosair.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
        self.botaosair.setFocusPolicy(QtCore.Qt.TabFocus)
        self.botaosair.setStyleSheet("font: bold 15px \"MS Shell Dlg 2\";\n"
                                     "background-color: rgb(252, 254, 235);\n"
                                     "border-style: outset;\n"
                                     "border-width: 2px;\n"
                                     "border-radius: 10px;\n"
                                     "border-color: red;")
        self.botaosair.setObjectName("botaosair")
        self.botaosair.clicked.connect(escondeprincipal)

        self.caixa_texto = QtWidgets.QLineEdit(self.centralwidget)
        self.caixa_texto.setGeometry(QtCore.QRect(445, 490, 345, 90))
        self.caixa_texto.setStyleSheet("font:bold;\n"
                                       "font-size:15px;\n"
                                       "border-style: outset;\n"
                                       "border-width: 2px;\n"
                                       "border-radius: 10px;\n"
                                       "border-color: purple;")
        self.caixa_texto.setAlignment(QtCore.Qt.AlignJustify
                                      | QtCore.Qt.AlignTop)
        self.caixa_texto.setObjectName("caixa_texto")
        self.caixa_texto.returnPressed.connect(self.botaoenviar_click)

        self.maid = QtWidgets.QLabel(self.centralwidget)
        self.maid.setGeometry(QtCore.QRect(445, 70, 345, 400))
        self.maid.setMinimumSize(QtCore.QSize(345, 400))
        self.maid.setMaximumSize(QtCore.QSize(345, 400))
        self.maid.setStyleSheet("border-style: outset;\n"
                                "border-width: 3px;\n"
                                "border-color: green;\n")
        self.maid.setText("")
        self.maid.setPixmap(QtGui.QPixmap("index.png"))
        self.maid.setObjectName("maid")
        mainwindow.setCentralWidget(self.centralwidget)

        self.label1 = QtWidgets.QLabel(self.centralwidget)
        self.label1.setGeometry(QtCore.QRect(445, 10, 345, 56))
        self.label1.setStyleSheet("font: bold 20px \"MS Shell Dlg 2\";\n"
                                  "background-color: rgb(252, 254, 235);\n"
                                  "border-style: outset;\n"
                                  "border-width: 3px;\n"
                                  "border-radius: 5px;\n"
                                  "border-color: green;")
        self.label1.setAlignment(QtCore.Qt.AlignCenter)
        self.label1.setObjectName("label1")

        self.textBrowser = QtWidgets.QTextBrowser(self.centralwidget)
        self.textBrowser.setGeometry(QtCore.QRect(10, 10, 431, 461))
        self.textBrowser.setStyleSheet(
            "background-color: rgb(252, 254, 235);\n"
            "border-style: outset;\n"
            "border-width: 3px;\n"
            "border-radius: 10px;\n"
            "border-color: green")
        self.textBrowser.setObjectName("textBrowser")

        self.botao2 = QtWidgets.QToolButton(self.centralwidget)
        self.botao2.setGeometry(QtCore.QRect(230, 490, 200, 40))
        self.botao2.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
        self.botao2.setStyleSheet("font: bold 15px \"MS Shell Dlg 2\";\n"
                                  "background-color: rgb(252, 254, 235);\n"
                                  "border-style: outset;\n"
                                  "border-width: 2px;\n"
                                  "border-radius: 10px;\n"
                                  "border-color: green;")
        self.botao2.setObjectName("botao2")
        self.botao2.clicked.connect(self.limparregistro)

        self.botaoitens = QtWidgets.QToolButton(self.centralwidget)
        self.botaoitens.setGeometry(QtCore.QRect(230, 540, 200, 40))
        self.botaoitens.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
        self.botaoitens.setStyleSheet("font: bold 15px \"MS Shell Dlg 2\";\n"
                                      "background-color: rgb(252, 254, 235);\n"
                                      "border-style: outset;\n"
                                      "border-width: 2px;\n"
                                      "border-radius: 10px;\n"
                                      "border-color: green;")
        self.botaoitens.setObjectName("botaoitens")
        self.botaoitens.clicked.connect(self.removeItems)

        self.botao1 = QtWidgets.QToolButton(self.centralwidget)
        self.botao1.setGeometry(QtCore.QRect(20, 490, 200, 40))

        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed,
                                           QtWidgets.QSizePolicy.Fixed)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(
            self.botao1.sizePolicy().hasHeightForWidth())

        self.botao1.setSizePolicy(sizePolicy)
        self.botao1.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
        self.botao1.setFocusPolicy(QtCore.Qt.TabFocus)
        self.botao1.setStyleSheet("font: bold 15px \"MS Shell Dlg 2\";\n"
                                  "background-color: rgb(252, 254, 235);\n"
                                  "border-style: outset;\n"
                                  "border-width: 2px;\n"
                                  "border-radius: 10px;\n"
                                  "border-color: green;")
        self.botao1.setObjectName("botao1")
        self.botao1.clicked.connect(mostraconfigs)

        self.listWidget = QtWidgets.QListWidget(self.centralwidget)
        self.listWidget.setGeometry(QtCore.QRect(20, 200, 411, 261))
        self.listWidget.setStyleSheet(
            "border-style: outset;\n"
            "alternate-background-color: rgb(247, 255, 255);\n"
            "background-color: rgb(225, 248, 253);\n"
            "border-width: 3px;\n"
            "border-radius: 10px;\n"
            "border-color: green")
        self.listWidget.setObjectName("listWidget")
        self.listWidget.setSelectionMode(
            QtWidgets.QAbstractItemView.MultiSelection)

        menu = QMenu()

        openAction = menu.addAction("Abrir")
        openAction.triggered.connect(mostraprincipal)
        settingAction = menu.addAction("Opções")
        settingAction.triggered.connect(mostraconfigs)

        self.tray = QSystemTrayIcon()
        self.tray.setIcon(icon)
        self.tray.setContextMenu(menu)
        self.tray.show()
        self.tray.setToolTip("Maid-sama")
        self.tray.showMessage("Atenção!", "Maid-chan iniciando a todo vapor!!")
        self.tray.activated.connect(mostraprincipal)

        self.botaoitens.raise_()
        self.maid.raise_()
        self.label1.raise_()
        self.botaosair.raise_()
        self.caixa_texto.raise_()
        self.botao2.raise_()
        self.botao1.raise_()
        self.textBrowser.raise_()
        self.listWidget.raise_()

        def registropadrao():
            self.listWidget.addItem('Mestre este é o Registro!')
            self.listWidget.addItem(
                'Por aqui você pode ver o que foi enviado anteriormente!')
            self.listWidget.addItem(
                'Para excluir algum item basta seleciona-lo e apertar Excluir Selecionados!'
            )
            self.listWidget.addItem(
                'O botão "Limpar Tela" exclui todas as mensagens no Registro!')
            self.listWidget.addItem('\n ~ Maid chan')

        registropadrao()

        self.retranslateUi(mainwindow)
        QtCore.QMetaObject.connectSlotsByName(mainwindow)

    def retranslateUi(self, mainwindow):
        _translate = QtCore.QCoreApplication.translate
        mainwindow.setWindowTitle(_translate("mainwindow", "Maid chan"))
        self.botaosair.setText(_translate("mainwindow", "Ocultar"))
        self.label1.setText(_translate("mainwindow", " Maid-chan"))
        self.textBrowser.setHtml(
            _translate(
                "mainwindow",
                "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
                "<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
                "p, li { white-space: pre-wrap; }\n"
                "</style></head><body style=\" font-family:\'MS Shell Dlg 2\'; font-size:8.25pt; font-weight:400; font-style:normal;\">\n"
                "<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:7pt;\">Maid-chan 0.2</span></p>\n"
                "<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:7pt;\">Todos os direitos reservados</span></p>\n"
                "<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:7pt;\">Sugestões: [email protected]</span></p>\n"
                "<p style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><br /></p>\n"
                "<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:8pt;\">MEUS PRINCIPAIS COMANDOS: </span></p>\n"
                "<p style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;\"><br /></p>\n"
                "<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:8pt;\">Abrir ou Executar: ( Executo o app digitado, lembre-se de usar os nomes padrões!)</span></p>\n"
                "<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">Abrir Site ou Site: ( Abro o site digitado usando o seu navegador padrão!)</p>\n"
                "<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:8pt;\">Tempo: ( Apresento diversas informações sobre o tempo da cidade requisitada!)</span></p>\n"
                "<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:8pt;\">Pesquisar ou Pesquise: ( Realizo qualquer pesquisa digitada usando o google!)</span></p>\n"
                "<p style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;\"><br /></p>\n"
                "<p style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;\"><br /></p>\n"
                "<p style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;\"><br /></p>\n"
                "<p align=\"right\" style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:8pt;\">-Maid-chan   </span></p></body></html>"
            ))
        self.botao2.setText(_translate("mainwindow", "Limpar Tela"))
        self.botao1.setText(_translate("mainwindow", "Opções"))
        self.botaoitens.setText(
            _translate("mainwindow", "Excluir Selecionados"))

    def botaoenviar_click(self):
        enviado = self.caixa_texto.text()
        enviado = enviado.strip()

        if enviado == "":
            self.caixa_texto.setText("")
            winsound.PlaySound('SystemExclamation',
                               winsound.SND_ALIAS | winsound.SND_ASYNC)
            QMessageBox.about(
                MainWindow, "Alguma coisa deu errada!",
                "Não consegui encontrar nenhum texto para enviar!")

        if enviado != "":
            self.listWidget.addItem(enviado)
            self.caixa_texto.clear()
            enviado = enviado.lower()

            if enviado[0:9] == "executar:":
                try:
                    enviado = enviado.replace("executar:", "")
                    enviado = enviado.strip()
                    os.startfile(enviado)
                    self.caixa_texto.clear()
                except OSError as err:
                    winsound.PlaySound('SystemExclamation',
                                       winsound.SND_ALIAS | winsound.SND_ASYNC)
                    QMessageBox.about(MainWindow, "Alguma coisa deu errada!",
                                      "OS error: {0}".format(err))
                    self.caixa_texto.clear()

            if enviado[0:6] == "abrir:":
                try:
                    enviado = enviado.replace("abrir:", "")
                    enviado = enviado.strip()
                    os.startfile(enviado)
                    self.caixa_texto.clear()
                except OSError as err:
                    winsound.PlaySound('SystemExclamation',
                                       winsound.SND_ALIAS | winsound.SND_ASYNC)
                    QMessageBox.about(MainWindow, "Alguma coisa deu errada!",
                                      "OS error: {0}".format(err))
                    self.caixa_texto.clear()

            if enviado[0:11] == "abrir site:":
                enviado = enviado.replace("abrir site:", "")
                enviado = enviado.strip()

                if "." not in enviado:
                    os.startfile("www." + enviado + ".com")
                    self.caixa_texto.clear()
                else:
                    os.startfile("http:" + enviado)
                    self.caixa_texto.clear()

            if enviado[0:5] == "site:":
                enviado = enviado.replace("site:", "")
                enviado = enviado.strip()

                if "." not in enviado:
                    os.startfile("www." + enviado + ".com")
                    self.caixa_texto.clear()
                else:
                    os.startfile("http:" + enviado)
                    self.caixa_texto.clear()

            if enviado[0:9] == "pesquise:":
                try:
                    enviado = enviado.replace("pesquise:", "")
                    enviado = enviado.strip()

                    enviado = enviado.replace(" ", "+")
                    os.startfile("www.google.com/search?&q=" + enviado +
                                 "&ie=UTF-8&oe=UTF-8")
                    self.caixa_texto.clear()

                except OSError as err:
                    winsound.PlaySound('SystemExclamation',
                                       winsound.SND_ALIAS | winsound.SND_ASYNC)
                    QMessageBox.about(MainWindow, "Alguma coisa deu errada!",
                                      "OS error: {0}".format(err))
                    self.caixa_texto.clear()

            if enviado[0:10] == "pesquisar:":
                try:
                    enviado = enviado.replace("pesquisar:", "")
                    enviado = enviado.strip()

                    enviado = enviado.replace(" ", "+")
                    os.startfile("www.google.com/search?&q=" + enviado +
                                 "&ie=UTF-8&oe=UTF-8")
                    self.caixa_texto.clear()

                except OSError as err:
                    winsound.PlaySound('SystemExclamation',
                                       winsound.SND_ALIAS | winsound.SND_ASYNC)
                    QMessageBox.about(MainWindow, "Alguma coisa deu errada!",
                                      "OS error: {0}".format(err))
                    self.caixa_texto.clear()

            if enviado[0:6] == "tempo:":
                try:
                    enviado = enviado.replace("tempo:", "")
                    enviado = enviado.strip()
                    cidade = enviado
                    requisicao = requests.get(
                        "http://api.openweathermap.org/data/2.5/weather?q=" +
                        cidade +
                        "&lang=pt_br&appid=1be8401c6f45bbe58134a735f9eece28")

                    tempo = json.loads(requisicao.text)

                    ventospeed = tempo['wind']['speed']
                    tempmax = tempo['main']['temp_max'] - 273.15
                    tempmin = tempo['main']['temp_min'] - 273.15
                    tempgraus = tempo['main']['temp'] - 273.15

                    localizacaowindow.show()
                    localizacaowindow.Labelnome.setText(
                        str(tempo['name'] + ', ' + tempo['sys']['country']))
                    localizacaowindow.Labelclima.setText(
                        str(tempo['weather'][0]['description']))
                    localizacaowindow.Labelmax.setText(
                        str('{0:.1f}').format(tempmax) + ' °')
                    localizacaowindow.Labelmin.setText(
                        str('{0:.1f}').format(tempmin) + ' °')
                    localizacaowindow.Labelumidade.setText(
                        str(tempo['main']['humidity']) + ' %')
                    localizacaowindow.Labeltemp.setText(
                        str('{0:.1f}').format(tempgraus) + ' °')
                    localizacaowindow.Labelventospeed.setText(
                        str(ventospeed) + ' m/s')

                except KeyError:
                    winsound.PlaySound('SystemExclamation',
                                       winsound.SND_ALIAS | winsound.SND_ASYNC)
                    QMessageBox.about(
                        MainWindow, "Alguma coisa deu errada!",
                        "Mestre, Não consegui identificar a cidade!\n\n"
                        "Por Favor confira se o nome foi inserido corretamente!"
                    )

    def limparregistro(self):
        self.listWidget.clear()
        winsound.PlaySound('SystemExclamation',
                           winsound.SND_ALIAS | winsound.SND_ASYNC)
        QMessageBox.about(MainWindow, "Grande sucesso!",
                          "Registro limpo com sucesso!")

    def removeItems(self):
        for item in self.listWidget.selectedItems():
            self.listWidget.takeItem(self.listWidget.row(item))
Beispiel #48
0
 def showNotification(title, body):
     trayIcon = QSystemTrayIcon()
     trayIcon.setIcon(QIcon(getResource("speech.png")))
     trayIcon.show()
     icon = QSystemTrayIcon.MessageIcon(QSystemTrayIcon.Information)
     trayIcon.showMessage(title, body, icon)
Beispiel #49
0
class MarksTimeTracker(QMainWindow, Ui_MainWindow):
    runningEvent = None

    def __init__(self, parent=None):
        super(MarksTimeTracker, self).__init__(parent)
        self.setupUi(self)
        self.tabWidget.tabBar().hide()
        self.setupStatusIcon()

        # config
        self.config_path = os.path.join(os.path.expanduser('~'), '.config', 'markstimetracker')
        dir_util.mkpath(self.config_path)
        self.readConfig()

        # Setup DB
        engine = create_engine('sqlite:///' + os.path.join(self.config_path, 'markstimetracker.db'))
        init_db(engine)
        self.db = sessionmaker(bind=engine)()

        self.updateTaskList()
        self.updateTasksComboBox()
        self.checkForRunningTask()

        # Timers
        timer = QTimer(self)
        timer.timeout.connect(self.updateTimeSpent)
        timer.start(1000)

        self.idleTimeTimer = QTimer()
        self.idleTimeTimer.timeout.connect(self.detectIdleTime)
        self.checkIdleTime()

        self.remindTimer = QTimer()
        self.remindTimer.timeout.connect(self.remindTracking)
        self.checkRemind()

        self.redmineSyncTimer = QTimer()
        self.redmineSyncTimer.timeout.connect(self.doRedmineSync)
        self.checkRedmineSync()

        # Events
        self.startButton.clicked.connect(self.toggleEventButton)
        self.eventsPeriodComboBox.currentIndexChanged.connect(self.eventsPeriodChanged)
        self.editDurationSpinBox.valueChanged.connect(self.updateEditStartEndTime)
        self.editStartDateTimeEdit.dateTimeChanged.connect(self.updateDurationSpinBoxEndTime)
        self.editEndDateTimeEdit.dateTimeChanged.connect(self.updateDurationSpinBox)
        self.editButtonBox.accepted.connect(self.saveEvent)
        self.editButtonBox.rejected.connect(lambda: self.tabWidget.setCurrentIndex(TAB_MAIN))
        self.settingsButtonBox.accepted.connect(self.saveSettings)
        self.settingsButtonBox.rejected.connect(lambda: self.tabWidget.setCurrentIndex(TAB_MAIN))
        self.settingsPushButton.clicked.connect(
            lambda: self.tabWidget.setCurrentIndex(TAB_SETTINGS))
        self.redmineSyncPushButton.clicked.connect(lambda: self.doRedmineSync(check=False))
        self.addEventPushButton.clicked.connect(self.addEvent)

        self.setupDbus()

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

    def setupDbus(self):
        dbus_loop = DBusQtMainLoop(set_as_default=True)
        self.bus = dbus.SessionBus(mainloop=dbus_loop)
        signals = [('org.freedesktop.ScreenSaver', '/org/freedesktop/ScreenSaver', 'ActiveChanged'),
                   ('com.canonical.Unity', '/com/canonical/Unity/Session', 'Locked')]
        for org, path, event in signals:
            screensaver = self.bus.get_object(org, path)
            screensaver.connect_to_signal(event, self.checkLockScreen)

    def updateTasksComboBox(self):
        self.tasksComboBox.clear()
        self.editTaskListComboBox.clear()
        self.tasksComboBox.addItem('')
        self.tasksComboBox.lineEdit().setPlaceholderText("What are you going to do?")
        for task in self.db.query(Task).all():
            if task.active:
                self.tasksComboBox.addItem(task.description)
            self.editTaskListComboBox.addItem(task.description)

    def updateTimeSpent(self):
        if self.runningEvent:
            spent_time = self.runningEvent.spent_time
            m, s = divmod(spent_time, 60)
            h, m = divmod(m, 60)
            self.timeLabel.setText("{h:02d}:{m:02d}:{s:02d}".format(h=h, m=m, s=s))

            period = self.eventsPeriodComboBox.currentText()
            start, end = self.getStartEndForPeriod(period)
            total = Event.get_spent_time_period(self.db, start, end)
            self.totalTimeLabel.setText("{}h".format(total))

    def getStartEndForPeriod(self, period):
        if period == "Today":
            start = datetime.datetime.now().replace(hour=0, minute=0)
            end = start + relativedelta.relativedelta(days=1)
        elif period == "Yesterday":
            end = datetime.datetime.now().replace(hour=0, minute=0)
            start = end - relativedelta.relativedelta(days=1)
        elif period == "This week":
            today = datetime.datetime.now().replace(hour=0, minute=0)
            start = today - relativedelta.relativedelta(days=today.weekday())
            end = today + relativedelta.relativedelta(days=6 - today.weekday())
        else:
            raise Exception("Don't know this period {}".format(period))

        return start, end

    def updateTaskList(self):
        while self.timeEntriesLayout.count() > 0:
            self.timeEntriesLayout.takeAt(0).widget().deleteLater()

        period = self.eventsPeriodComboBox.currentText()
        start, end = self.getStartEndForPeriod(period)

        events = self.db.query(Event).filter(Event.start.between(start, end))\
            .order_by(Event.start.desc())

        for event in events:
            if not event.end:
                continue
            widget = EventWidget(event.id, event.task.description, event.spent_time, parent=self)
            widget.clicked.connect(self.eventClicked)
            widget.show()
            self.timeEntriesLayout.addWidget(widget)

    def updateEditStartEndTime(self):
        hours = self.editDurationSpinBox.value()
        startTime = self.editStartDateTimeEdit.dateTime().toPyDateTime()
        newEndTime = startTime + relativedelta.relativedelta(hours=hours)
        self.editEndDateTimeEdit.setDateTime(newEndTime)

    def updateDurationSpinBox(self):
        seconds = float((self.editEndDateTimeEdit.dateTime().toPyDateTime() -
                         self.editStartDateTimeEdit.dateTime().toPyDateTime()).seconds)
        hours = seconds / 3600
        self.editDurationSpinBox.setValue(hours)

    def updateDurationSpinBoxEndTime(self):
        self.updateDurationSpinBox()
        self.updateEditStartEndTime()

    def checkForRunningTask(self):
        self.runningEvent = self.db.query(Event).filter(Event.end == None).first()
        if self.runningEvent:
            self.tasksComboBox.setCurrentIndex(
                [self.tasksComboBox.itemText(x) for x in range(self.tasksComboBox.count())]
                .index(self.runningEvent.task.description))
            self.startButton.setText("Stop")
            self.tasksComboBox.setEnabled(False)

    def toggleEventButton(self):
        if self.runningEvent:
            self.stopEvent()
        else:
            self.startEvent()

    def eventsPeriodChanged(self):
        self.updateTaskList()

    def eventClicked(self, event_id):
        event = self.db.query(Event).get(event_id)
        self.editTaskListComboBox.setCurrentIndex(
            [self.editTaskListComboBox.itemText(x)
             for x in range(self.editTaskListComboBox.count())]
            .index(event.task.description))

        self.editDurationSpinBox.setValue(float(event.spent_time) / 3600)
        self.editStartDateTimeEdit.setDateTime(event.start_date)
        self.editEndDateTimeEdit.setDateTime(event.end_date)

        self.tabWidget.setCurrentIndex(TAB_EDIT_EVENT)

        self.editingEvent = event

    def startEvent(self, event=None):
        if not event:
            event = self.tasksComboBox.currentText()

        self.tasksComboBox.setEnabled(False)

        self.startButton.setText("Stop")

        if not event:
            return

        if re.match(r'\d+ - .+', event):
            tracker_id, name = re.findall(r'(\d+) - (.+)', event)[0]
        else:
            tracker_id = None
            name = event

        # Update DB
        task = Task.get_or_create(self.db, task_id=tracker_id, name=name, parent=None)

        if self.runningEvent:
            self.runningEvent.end = datetime.datetime.now()

        self.runningEvent = Event(task_id=task.task_id, comment="", start=datetime.datetime.now())
        self.db.add(self.runningEvent)
        self.db.commit()

        self.tasksComboBox.lineEdit().setText(self.runningEvent.task.description)

        self.checkForRunningTask()

    def addEvent(self):
        self.editDurationSpinBox.setValue(1)
        self.editStartDateTimeEdit.setDateTime(datetime.datetime.now())
        self.editEndDateTimeEdit.setDateTime(datetime.datetime.now() +
                                             relativedelta.relativedelta(hours=1))

        self.tabWidget.setCurrentIndex(TAB_EDIT_EVENT)
        self.editingEvent = Event()
        self.db.add(self.editingEvent)

    def stopEvent(self):
        self.tasksComboBox.setEnabled(True)

        self.runningEvent.end = datetime.datetime.now()
        self.db.commit()

        self.runningEvent = None

        self.updateTaskList()

        self.startButton.setText("Start")
        self.timeLabel.setText("00:00:00")

        self.updateTasksComboBox()

    def saveEvent(self):

        self.editingEvent.task_id = self.editTaskListComboBox.currentText().split(' - ')[0]
        self.editingEvent.start = self.editStartDateTimeEdit.dateTime().toPyDateTime()
        self.editingEvent.end = self.editEndDateTimeEdit.dateTime().toPyDateTime()

        self.db.commit()

        self.tabWidget.setCurrentIndex(TAB_MAIN)
        self.updateTaskList()

    def saveSettings(self):
        self.config = {'enable_detect_idle_time': self.detectIdleTimecheckBox.checkState(),
                       'detect_idle_time': self.detectIdleTimeSpinBox.value(),
                       'enable_remind': self.remindCheckBox.checkState(),
                       'remind_time': self.remindSpinBox.value(),
                       'stop_on_lock_screen': self.stopLockScreencheckBox.checkState(),
                       'enabled_redmine_sync': self.syncRedmineCheckBox.checkState(),
                       'redmine_sync_time': self.redmineSyncTimeSpinBox.value(),
                       'redmine_apikey': self.redmineApikeyLineEdit.text(),
                       'redmine_url': self.redmineUrlLineEdit.text(),
                       'redmine_user': self.redmineUserLineEdit.text()}
        self.writeConfig()

    def readConfig(self):
        config_file_path = os.path.join(self.config_path, 'config.json')
        if os.path.exists(config_file_path):
            with open(config_file_path, 'r') as f:
                self.config = json.loads(f.read())
        else:
            self.config = {}

        self.detectIdleTimecheckBox.setCheckState(self.config.get('enable_detect_idle_time', True))
        self.detectIdleTimeSpinBox.setValue(self.config.get('detect_idle_time'))
        self.remindCheckBox.setCheckState(self.config.get('enable_remind', True))
        self.remindSpinBox.setValue(self.config.get('remind_time'))
        self.stopLockScreencheckBox.setCheckState(self.config.get('stop_on_lock_screen', True))
        self.syncRedmineCheckBox.setCheckState(self.config.get('enabled_redmine_sync'))
        self.redmineSyncTimeSpinBox.setValue(self.config.get('redmine_sync_time'))
        self.redmineApikeyLineEdit.setText(self.config.get('redmine_apikey'))
        self.redmineUrlLineEdit.setText(self.config.get('redmine_url'))
        self.redmineUserLineEdit.setText(self.config.get('redmine_user'))

    def writeConfig(self):
        with open(os.path.join(self.config_path, 'config.json'), 'w') as f:
            f.write(json.dumps(self.config))
        self.tabWidget.setCurrentIndex(TAB_MAIN)

        self.checkIdleTime()
        self.checkRemind()
        self.checkRedmineSync()

    def checkIdleTime(self):
        self.idleTimeTimer.stop()
        if self.config.get("enable_detect_idle_time", True):
            self.idleTimeTimer.start(self.config.get("detect_idle_time", 5) * 60000)

    def detectIdleTime(self):

        # do something

        self.checkIdleTime()

    def checkRemind(self):
        self.remindTimer.stop()
        if self.config.get("enable_remind", True):
            self.remindTimer.start(self.config.get("remind_time", 5) * 60000)

    def remindTracking(self):

        # do something

        self.checkRemind()

    def checkRedmineSync(self):
        self.redmineSyncTimer.stop()
        if self.config.get("enabled_redmine_sync"):
            self.redmineSyncTimer.start(self.config.get("redmine_sync_time", 5) * 60000)

        self.redmineSyncPushButton.setVisible(self.config.get("enabled_redmine_sync", False))

    def doRedmineSync(self, check=True):
        logging.info("Doing redmine sync")
        thread = RedmineSyncThread(self.config,
                                   'sqlite:///' +
                                   os.path.join(self.config_path, 'markstimetracker.db'))

        def updateTaskWidgets():
            self.updateTaskList()
            self.updateTasksComboBox()
            self.checkForRunningTask()

        thread.finished.connect(updateTaskWidgets)
        thread.start()

        self.checkRedmineSync()

    def checkLockScreen(self, is_locked=True):
        if is_locked and self.config.get("stop_on_lock_screen"):
            self.stopEvent()
Beispiel #50
0
class bridgePanel(QMainWindow, QObject):
    start = pyqtSignal()
    stop = pyqtSignal()

    def __init__(self, app):
        super().__init__()
        self.__v2rayshellConfigFile = {
            "preferences": {
                "v2ray-core": "",
                "v2ray-coreFilePath": "",
                "connection": {
                    "connect": "switch",
                    "interval": 45,
                    "timeout": 3,
                    "enable": True,
                    "trytimes": 3
                }
            },
            "configFiles": [{
                "enable": True,
                "hostName": "",
                "configFileName": ""
            }]
        }
        self.bridgetreasureChest = bridgetreasureChest.bridgetreasureChest()
        self.app = app
        self.translate = QCoreApplication.translate
        self.__v2rayshellVersion = "20180204"
        self.__windowTitile = "V2Ray-shell" + " " + self.__v2rayshellVersion
        self.runv2raycore = False
        self.iconStart = QIcon()
        self.iconStop = QIcon()
        self.__iconSize = QSize(32, 32)
        self.iconStart.addPixmap(QPixmap(filePath + "/icons/start.png"),
                                 QIcon.Normal, QIcon.On)
        self.iconStop.addPixmap(QPixmap(filePath + "/icons/stop.png"),
                                QIcon.Disabled, QIcon.On)
        self.currentRowRightClicked = False
        self.v2rayshellTrayIcon = QSystemTrayIcon()
        self.v2rayshellTrayIcon.setIcon(self.iconStart)
        self.v2rayshellTrayIcon.show()

        self.radioButtonGroup = QButtonGroup()

        self.setV2RayshellLanguage()
        self.trytimes = self.bridgetreasureChest.getConnectiontrytimes()
        self.interval = self.bridgetreasureChest.getConnectioninterval()
        self.proxyTryConnect = proxyTryconnect()
        if v2rayshellDebug: self.proxyTryConnect.setresetTime(6, 3)
        else: self.proxyTryConnect.setresetTime(self.interval, self.trytimes)
        self.labelBridge = (self.translate("bridgePanel", "Start/Stop"),
                            self.translate("bridgePanel", "Host Name"),
                            self.translate("bridgePanel", "Config Name"),
                            self.translate("bridgePanel", "Proxy"),
                            self.translate("bridgePanel", "Time Lag"))

        self.createBridgePanel()

    def createBridgePanel(self):
        self.setWindowTitle(self.__windowTitile)
        self.setWindowIcon(self.iconStart)
        menubar = self.menuBar()
        self.statusBar()

        self.actionNewV2rayConfigFile = QAction(
            self.translate("bridgePanel", "Add V2Ray-core Config File"), self)
        self.actionNewV2rayConfigFile.setShortcut(QKeySequence.New)
        self.actionNewV2rayConfigFile.setStatusTip(
            self.translate("bridgePanel", "Add V2Ray-core Config File"))

        self.actionSaveV2rayshellConfigFile = QAction(
            self.translate("bridgePanel", "Save V2Ray-shell Config File"),
            self)
        self.actionSaveV2rayshellConfigFile.setShortcut(QKeySequence.Save)
        self.actionSaveV2rayshellConfigFile.setStatusTip(
            self.translate("bridgePanel", "Save V2Ray-shell Config File"))

        self.actionReloadV2rayshellConfigFile = QAction(
            self.translate("bridgePanel", "Open V2Ray-shell Config File"),
            self)
        self.actionReloadV2rayshellConfigFile.setShortcut(QKeySequence.Open)
        self.actionReloadV2rayshellConfigFile.setStatusTip(
            self.translate("bridgePanel", "Open V2Ray-shell Config File"))

        self.actionQuitV2rayshellPanel = QAction(
            self.translate("bridgePanel", "Quit"), self)
        if sys.platform.startswith('win'):
            self.actionQuitV2rayshellPanel.setShortcut("Ctrl+Q")
        else:
            self.actionQuitV2rayshellPanel.setShortcut(QKeySequence.Quit)
        self.actionQuitV2rayshellPanel.setStatusTip(
            self.translate("bridgePanel", "Quit V2Ray-shell"))

        fileMenu = menubar.addMenu(self.translate("bridgePanel", "&File"))
        fileMenu.addAction(self.actionNewV2rayConfigFile)
        fileMenu.addSeparator()
        fileMenu.addAction(self.actionReloadV2rayshellConfigFile)
        fileMenu.addAction(self.actionSaveV2rayshellConfigFile)
        fileMenu.addSeparator()
        fileMenu.addAction(self.actionQuitV2rayshellPanel)

        self.texteditBridge = QTextEdit(self)
        self.texteditBridge.setReadOnly(True)

        self.tableWidgetBridge = QTableWidget()
        self.tableWidgetBridge.setRowCount(0)
        self.tableWidgetBridge.setColumnCount(5)
        self.tableWidgetBridge.setHorizontalHeaderLabels(self.labelBridge)
        self.tableWidgetBridge.setSelectionMode(
            QAbstractItemView.SingleSelection)
        self.tableWidgetBridge.setSelectionBehavior(
            QAbstractItemView.SelectRows)
        self.tableWidgetBridge.setEditTriggers(
            QAbstractItemView.NoEditTriggers)
        #self.tableWidgetBridge.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        self.tableWidgetBridge.setContextMenuPolicy(Qt.CustomContextMenu)

        self.popMenu = popMenu = QMenu(self.tableWidgetBridge)
        self.actionpopMenuAddV2rayConfigFile = QAction(
            self.translate("bridgePanel", "Add V2Ray Config File"), self)
        self.actionpopMenuAddV2rayConfigFile.setShortcut("Ctrl+n")
        self.actionpopMenuEditV2rayConfigFile = QAction(
            self.translate("bridgePanel", "Edit V2Ray Config File"), self)
        self.actionpopMenuProxyCheckTimeLag = QAction(
            self.translate("bridgePanel", "Proxy Time Lag Check..."), self)
        self.actionpopMenuDeleteRow = QAction(
            self.translate("bridgePanel", "Delete"), self)

        popMenu.addAction(self.actionpopMenuAddV2rayConfigFile)
        popMenu.addAction(self.actionpopMenuEditV2rayConfigFile)
        popMenu.addAction(self.actionpopMenuProxyCheckTimeLag)
        popMenu.addAction(self.actionpopMenuDeleteRow)

        self.actionopenV2rayshellPreferencesPanel = QAction(
            self.translate("bridgePanel", "preferences"), self)
        self.actionopenV2rayshellPreferencesPanel.setStatusTip(
            self.translate("bridgePanel", "Setting V2Ray-shell"))

        optionMenu = menubar.addMenu(self.translate("bridgePanel", "&options"))
        optionMenu.addAction(self.actionpopMenuProxyCheckTimeLag)
        optionMenu.addAction(self.actionopenV2rayshellPreferencesPanel)

        helpMenu = menubar.addMenu(self.translate("bridgePanel", "&help"))
        self.actioncheckv2raycoreupdate = QAction(
            self.translate("bridgePanel", "check V2Ray-core update"), self)
        self.actionv2rayshellBugreport = QAction(
            self.translate("bridgePanel", "Bug Report"), self)
        self.actionaboutv2rayshell = QAction(
            self.translate("bridgePanel", "About"), self)

        helpMenu.addAction(self.actioncheckv2raycoreupdate)
        helpMenu.addAction(self.actionv2rayshellBugreport)
        helpMenu.addAction(self.actionaboutv2rayshell)

        toolBar = QToolBar()
        self.actionV2rayStart = QAction(self.translate("bridgePanel", "Start"))
        self.actionV2rayStart.setIcon(self.style().standardIcon(
            getattr(QStyle, "SP_MediaPlay")))
        self.actionV2rayStop = QAction(self.translate("bridgePanel", "Stop"))
        self.actionV2rayStop.setIcon(self.style().standardIcon(
            getattr(QStyle, "SP_MediaStop")))
        toolBar.addAction(self.actionV2rayStart)
        toolBar.addAction(self.actionV2rayStop)
        self.addToolBar(toolBar)

        self.trayIconMenu = QMenu()
        self.v2rayshellTrayIcon.setContextMenu(self.trayIconMenu)

        self.trayIconMenushowhidePanel = QAction(
            self.translate("bridgePanel", "Show/Hide"))
        self.trayIconMenuclosePanel = QAction(
            self.translate("bridgePanel", "Quit"))

        self.trayIconMenu.addAction(self.trayIconMenushowhidePanel)
        self.trayIconMenu.addSeparator()
        self.trayIconMenu.addAction(self.trayIconMenuclosePanel)

        self.splitterBridge = QSplitter(Qt.Vertical)
        self.splitterBridge.addWidget(self.tableWidgetBridge)
        self.splitterBridge.addWidget(self.texteditBridge)

        self.setCentralWidget(self.splitterBridge)

        self.createBridgePanelSignals()

        self.onloadV2rayshellConfigFile(init=True)

        self.onv2raycoreStart()

        self.autocheckv2raycoreUpdate()

    def createBridgePanelSignals(self):
        self.actionNewV2rayConfigFile.triggered.connect(
            self.tableWidgetBridgeAddNewV2rayConfigFile)
        self.actionReloadV2rayshellConfigFile.triggered.connect(
            self.onloadV2rayshellConfigFile)
        self.actionSaveV2rayshellConfigFile.triggered.connect(
            self.onsaveV2rayshellConfigFile)
        self.actionopenV2rayshellPreferencesPanel.triggered.connect(
            self.createBridgepreferencesPanel)
        self.actionpopMenuAddV2rayConfigFile.triggered.connect(
            self.tableWidgetBridgeAddNewV2rayConfigFile)
        self.actionpopMenuEditV2rayConfigFile.triggered.connect(
            self.oncreatenauticalChartPanel)
        self.actionpopMenuDeleteRow.triggered.connect(
            self.tableWidgetBridgeDelete)
        self.actionpopMenuProxyCheckTimeLag.triggered.connect(
            self.onproxyserverTimeLagTest)
        self.actioncheckv2raycoreupdate.triggered.connect(
            self.onopenv2rayupdatePanel)
        self.actionv2rayshellBugreport.triggered.connect(self.bugReportPanel)
        self.actionQuitV2rayshellPanel.triggered.connect(self.close)
        self.actionV2rayStart.triggered.connect(self.onv2raycoreStart)
        self.actionV2rayStop.triggered.connect(self.onv2raycoreStop)
        self.actionaboutv2rayshell.triggered.connect(self.about)
        self.radioButtonGroup.buttonClicked.connect(self.onradioButtonClicked)
        self.tableWidgetBridge.cellDoubleClicked.connect(
            self.ontableWidgetBridgecellDoubleClicked)
        self.tableWidgetBridge.customContextMenuRequested.connect(
            self.ontableWidgetBridgeRightClicked)
        self.v2rayshellTrayIcon.activated.connect(self.restorebridgePanel)
        self.trayIconMenushowhidePanel.triggered.connect(
            self.onsystemTrayIconMenushowhidebridgePanel)
        self.trayIconMenuclosePanel.triggered.connect(self.close)
        self.proxyTryConnect.reconnectproxy.connect(self.swapNextConfigFile)
        self.start.connect(self.onupdateinstallFinishedstartNewV2raycore)
        self.stop.connect(self.onv2raycoreStop)

    def setV2RayshellLanguage(self):
        self.trans = QTranslator()
        language = self.bridgetreasureChest.getLanguage()
        allLanguages = self.bridgetreasureChest.getAllLanguage()
        if language and allLanguages:
            if language in allLanguages:
                self.trans.load(allLanguages[language])
                self.app.installTranslator(self.trans)

    def autocheckv2raycoreUpdate(self):
        self.v2rayshellautoUpdate = updatePanel.updateV2ray()
        self.bridgeSingal = (self.start, self.stop)
        self.trycheckUpdate = QTimer()
        self.trycheckUpdate.timeout.connect(
            lambda: self.v2rayshellautoUpdate.enableUpdateSchedule(
                self.bridgetreasureChest, self.bridgeSingal))

        self.trycheckUpdate.start(1000 * 60 * 60 *
                                  4)  ### Check every four hours
        self.trycheckUpdate.singleShot(  ### Check when the script is started
            1000 * 15,  ### fifty seconds
            lambda: self.v2rayshellautoUpdate.enableUpdateSchedule(
                self.bridgetreasureChest, self.bridgeSingal))

    def event(self, event):
        if (event.type() == QEvent.WindowStateChange and self.isMinimized()):
            self.setWindowFlags(self.windowFlags() & ~Qt.Tool)
            self.v2rayshellTrayIcon.show()
            return True
        else:
            return super(bridgePanel, self).event(event)

    def onsystemTrayIconMenushowhidebridgePanel(self):
        if self.isHidden():
            self.showNormal()
        elif self.isVisible():
            self.hide()

    def restorebridgePanel(self, reason):
        if reason == QSystemTrayIcon.Trigger:
            if self.isVisible():
                self.hide()
            elif self.isHidden():
                self.showNormal()

    def close(self):
        super(bridgePanel, self).close()
        self.v2rayshellTrayIcon.hide()
        self.onv2raycoreStop()

    def closeEvent(self, event):
        self.close()
        event.accept()
        sys.exit(self.app.exec_())

    def onv2raycoreStop(self):
        if (self.runv2raycore):
            self.runv2raycore.stop.emit()
        try:
            ### force stop checking proxy time lag
            del self.autoCheckTimer
        except Exception:
            pass

    def onupdateinstallFinishedstartNewV2raycore(self):
        self.onloadV2rayshellConfigFile(init=True)
        self.onv2raycoreStart()

    def onv2raycoreStart(self):
        currentActiveRow = False
        rowCount = self.tableWidgetBridge.rowCount()
        for i in range(rowCount):
            currentActiveRow = self.tableWidgetBridge.cellWidget(i, 0)
            if currentActiveRow.isChecked():
                self.texteditBridge.clear()
                option = self.tableWidgetBridge.item(i, 2)
                if option:
                    option = '-config="{}" -format=json'.format(option.text())
                else:
                    option = ""
                filePath = self.bridgetreasureChest.getV2raycoreFilePath()
                if (filePath == False or filePath == ""):
                    filePath = "v2ray"
                self.runv2raycore = runV2raycore.runV2raycore(
                    outputTextEdit=self.texteditBridge,
                    v2rayPath=filePath,
                    v2rayOption=option,
                    bridgetreasureChest=self.bridgetreasureChest)

                self.runv2raycore.start.emit()
                self.autocheckProxy(i)
                break
            else:
                del currentActiveRow

    def autocheckProxy(self, row):
        ### TODO
        """
        Frequent access to the server may cause suspicion of DDOS attacks, 
        which may put the VPS server at risk.
        """
        enableAutoCheck = self.bridgetreasureChest.getConnectionEnable()

        if (enableAutoCheck):
            self.proxyStatus = proxyTest.proxyStatus()
            self.autoCheckTimer = QTimer()
            invervalTime = self.bridgetreasureChest.getConnectioninterval()
            timeout = self.bridgetreasureChest.getConnectiontimeout()
            proxyAddress = self.getProxyAddressFromTableWidget(row)

            if proxyAddress:
                self.autoCheckTimer.timeout.connect(
                    lambda: self.startCheckProxy(timeout=timeout,
                                                 proxyAddress=proxyAddress,
                                                 row=row,
                                                 proxyStatus=self.proxyStatus))

                self.bridgetreasureChest.setProxy(proxyAddress)

                if v2rayshellDebug: self.autoCheckTimer.start(6000)
                else: self.autoCheckTimer.start(1000 * invervalTime)

                self.autoCheckTimer.singleShot(
                    100,
                    lambda: self.startCheckProxy(timeout=timeout,
                                                 proxyAddress=proxyAddress,
                                                 row=row,
                                                 proxyStatus=self.proxyStatus))

    def setTableWidgetTimelag(self, row, proxyStatus):
        newlabelTimelag = self.setlabelTimeLagColor(proxyStatus)
        oldlabelTimelag = self.tableWidgetBridge.cellWidget(row, 4)
        del oldlabelTimelag
        self.tableWidgetBridge.setCellWidget(row, 4, newlabelTimelag)
        self.tableWidgetBridge.resizeColumnsToContents()

    def startCheckProxy(self, timeout, proxyAddress, row, proxyStatus):
        if (proxyAddress):
            proxyStatus.clear()
            proxyStatus.signal.connect(
                lambda: self.setTableWidgetTimelag(row, proxyStatus))

            self.proxy = proxyTest.proxyTest(proxyprotocol=proxyAddress[0],
                                             proxyhostname=proxyAddress[1],
                                             proxyhostport=int(
                                                 proxyAddress[2]),
                                             getproxyStatus=proxyStatus,
                                             timeout=int(timeout))

    def setlabelTimeLagColor(self, proxyStatus=False):
        labelTimeLag = QLabel()
        if (proxyStatus and proxyStatus.getProxyError() == False):
            labelFont = QFont()
            labelFont.setPointSize(12)
            labelFont.setBold(True)
            labelTimeLag.setFont(labelFont)
            forestGreen = "QLabel {color: rgb(34, 139, 34)}"
            darkOrange = "QLabel {color: rgb(255, 140, 0)}"
            red = "QLabel {color: rgb(194,24,7)}"

            if (proxyStatus.getElapsedTime() < 260):
                labelTimeLag.setStyleSheet(forestGreen)
            elif (proxyStatus.getElapsedTime() > 420):
                labelTimeLag.setStyleSheet(red)
            else:
                labelTimeLag.setStyleSheet(darkOrange)
            labelTimeLag.setText("{} ms".format(
                str(proxyStatus.getElapsedTime())))

            return labelTimeLag

        elif (proxyStatus and proxyStatus.getProxyError()):
            labelTimeLag.setText("{}:{}".format(
                proxyStatus.getProxyErrorString(),
                proxyStatus.getProxyErrorCode()))

            self.proxyTryConnect.trytimesDecrease()
            return labelTimeLag

    def swapNextConfigFile(self):
        self.onv2raycoreStop()
        try:
            self.trytimes = self.bridgetreasureChest.getConnectiontrytimes()
            self.interval = self.bridgetreasureChest.getConnectioninterval()
            self.proxyTryConnect.stopperiodicCheckProxyStatus()
            if v2rayshellDebug: self.proxyTryConnect.setresetTime(6, 3)
            else:
                self.proxyTryConnect.setresetTime(self.interval, self.trytimes)
        except Exception:
            self.proxyTryConnect.setresetTime(60, 3)

        if (self.bridgetreasureChest.connectionisSwitch()):
            ### swap next row's configFile
            buttons = self.radioButtonGroup.buttons()
            buttonsNumber = len(buttons)
            activeRow = False
            for i in range(buttonsNumber):
                if buttons[i].isChecked():
                    buttons[i].setChecked(False)
                    if i == buttonsNumber - 1:
                        buttons[0].setChecked(True)
                        activeRow = 0
                        break
                    else:
                        buttons[i + 1].setChecked(True)
                        activeRow = i + 1
                    break

            ### change the row icons
            for i in range(buttonsNumber):
                widget = self.tableWidgetBridge.cellWidget(i, 0)
                if (widget):
                    widget.setIcon(self.iconStop)
                    widget.setIconSize(self.__iconSize)
                    if (widget.isChecked()):
                        pass
            widget = self.tableWidgetBridge.cellWidget(activeRow, 0)
            if widget:
                widget.setIcon(self.iconStart)
                widget.setIconSize(self.__iconSize)

        self.onv2raycoreStart()

    def onopenv2rayupdatePanel(self):
        currentActiveRow = False
        rowCount = self.tableWidgetBridge.rowCount()
        currentRow = False
        for i in range(rowCount):
            currentActiveRow = self.tableWidgetBridge.cellWidget(i, 0)
            if currentActiveRow.isChecked():
                currentRow = i
                break

        if (currentActiveRow and currentActiveRow.isChecked()):
            proxy = self.tableWidgetBridge.item(currentRow, 3)
            proxy = proxy.text().split(":")
            protocol = QNetworkProxy.Socks5Proxy
            if (proxy[0] == "socks"):
                protocol = QNetworkProxy.Socks5Proxy
            elif (proxy[0] == "http"):
                protocol = QNetworkProxy.HttpProxy
            hostName = proxy[1]
            hostPort = int(proxy[2])

            v2rayAPI = updatePanel.v2rayAPI()
            self.createupdatePanel = updatePanel.v2rayUpdatePanel(
                v2rayapi=v2rayAPI,
                protocol=protocol,
                proxyhostName=hostName,
                port=hostPort,
                bridgetreasureChest=self.bridgetreasureChest)
            self.createupdatePanel.createPanel()
            self.createupdatePanel.setAttribute(Qt.WA_DeleteOnClose)
            self.createupdatePanel.setWindowIcon(self.iconStart)
            self.createupdatePanel.setWindowTitle(
                self.translate("bridgePanel", "Check V2Ray-core update"))
            self.createupdatePanel.resize(QSize(1024, 320))
            self.createupdatePanel.move(
                QApplication.desktop().screen().rect().center() -
                self.createupdatePanel.rect().center())
            self.createupdatePanel.show()
            self.createupdatePanel.exec_()
        else:
            self.noPoxyServerRunning()

    def ontableWidgetBridgeRightClicked(self, pos):
        index = self.tableWidgetBridge.indexAt(pos)
        clickedRow.rightClickedRow = index.row()
        clickedRow.mousePos = QCursor().pos()
        self.popMenu.move(QCursor().pos())
        self.popMenu.show()

    def ontableWidgetBridgecellDoubleClicked(self, row, column):
        if (column == 1):
            hostName, ok = QInputDialog.getText(
                self, self.translate("bridgePanel", 'Host Name'),
                self.translate("bridgePanel", 'Enter Host Name:'))
            if (ok):
                self.tableWidgetBridge.setItem(row, column,
                                               QTableWidgetItem(str(hostName)))
                self.tableWidgetBridge.resizeColumnsToContents()
        elif (column == 2):
            fileNames = self.onopenV2rayConfigJSONFile()
            if (fileNames):
                for fileName in fileNames:
                    self.tableWidgetBridge.setItem(
                        row, column, QTableWidgetItem(str(fileName)))
                    self.tableWidgetBridge.resizeColumnsToContents()
        elif (column == 3):
            self.onproxyserverTimeLagTest()
        elif (column == 4):
            self.onproxyserverTimeLagTest()

    def getProxyAddressFromTableWidget(self, row):
        proxy = self.tableWidgetBridge.item(row, 3)
        try:
            proxy = proxy.text().split(":")
        except Exception:
            return False

        if (proxy[0] == "socks"):
            proxy[0] = QNetworkProxy.Socks5Proxy
        elif (proxy[0] == "http"):
            proxy[0] = QNetworkProxy.HttpProxy

        if len(proxy) < 3: return False
        else: return proxy

    def onproxyserverTimeLagTest(self):
        proxyStatus = proxyTest.proxyStatus()
        """
        right clicked mouse button pop a menu check proxy
        """
        currentActiveRow = False
        rowCount = self.tableWidgetBridge.rowCount()
        currentRow = False
        for i in range(rowCount):
            currentActiveRow = self.tableWidgetBridge.cellWidget(i, 0)
            if currentActiveRow.isChecked():
                currentRow = i
                break
        if (currentActiveRow and currentActiveRow.isChecked()):
            proxy = self.getProxyAddressFromTableWidget(currentRow)
            protocol = proxy[0]
            hostName = proxy[1]
            hostPort = int(proxy[2])

            proxy = proxyTest.proxyTestPanel(proxyhostname=hostName,
                                             proxyhostport=hostPort,
                                             proxyprotocol=protocol,
                                             getproxyStatus=proxyStatus)
            proxy.createproxyTestPanel()
            proxy.setAttribute(Qt.WA_DeleteOnClose)
            proxy.setWindowTitle(
                self.translate("bridgePanel", "Proxy Time Lag Check"))
            proxy.setWindowIcon(self.iconStart)
            proxy.resize(QSize(600, 480))
            proxy.move(QApplication.desktop().screen().rect().center() -
                       proxy.rect().center())
            proxy.show()
            proxy.exec_()
        else:
            self.noPoxyServerRunning()

    def noPoxyServerRunning(self):
        warningPanel = QDialog()
        warningPanel.setAttribute(Qt.WA_DeleteOnClose)
        warningPanel.setWindowTitle(self.translate("bridgePanel",
                                                   "Warnnig..."))
        warningPanel.setWindowIcon(self.iconStop)
        labelMsg = QLabel(
            self.translate(
                "bridgePanel",
                "There no any server is running, \n[File]->[Add V2Ray-core Config File] (Ctrl+n) add a config.json."
            ))
        vbox = QVBoxLayout()
        vbox.addWidget(labelMsg)
        warningPanel.setLayout(vbox)
        warningPanel.move(QApplication.desktop().screen().rect().center() -
                          warningPanel.rect().center())
        warningPanel.open()
        warningPanel.exec_()

    def getProxyAddressFromJSONFile(self, filePath):
        from bridgehouse.editMap.port import treasureChest, openV2rayJSONFile
        tempTreasureChest = treasureChest.treasureChest()
        openV2rayJSONFile.openV2rayJSONFile(
            filePath, tempTreasureChest, disableLog=True).initboundJSONData()
        inbound = tempTreasureChest.getInbound()
        if (inbound):
            protocol = inbound["protocol"]
            ipAddress = inbound["listen"]
            port = inbound["port"]
            if (protocol == "socks" or protocol == "http"):
                return "{}:{}:{}".format(protocol, ipAddress, port)
            else:
                return False
        else:
            return False

    def onradioButtonClicked(self, e):
        rowCount = self.tableWidgetBridge.rowCount()
        #radioButtonClickedRow = 0
        for i in range(rowCount):
            widget = self.tableWidgetBridge.cellWidget(i, 0)
            if (widget):
                widget.setIcon(self.iconStop)
                widget.setIconSize(self.__iconSize)
                if (widget.isChecked()):
                    #radioButtonClickedRow = i
                    pass
        e.setIcon(self.iconStart)
        e.setIconSize(self.__iconSize)

    def onloadV2rayshellConfigFile(self, init=False):
        """
        when the script first start, and auto load v2ray-shell config file.
        """
        if init:
            self.settingv2rayshelltableWidget()
        else:

            def openV2rayshellConfigFile():
                options = QFileDialog.Options()
                filePath, _ = QFileDialog.getOpenFileName(
                    self,
                    self.translate("bridgePanel",
                                   "Open V2Ray-sehll Config File"),
                    "",
                    "V2Ray-shell config file (*.v2rayshell)",
                    options=options)
                if (filePath):
                    self.bridgetreasureChest.clear()
                    self.tableWidgetBridge.setRowCount(0)
                    self.bridgetreasureChest.initbridgeJSONData(
                        v2rayshellConfigFileName=filePath)
                    self.settingv2rayshelltableWidget()

            openV2rayshellConfigFile()

    def onopenV2rayConfigJSONFile(self):
        """
        open a new v2ray config file to tabelWidget
        """
        options = QFileDialog.Options()
        filePaths, _ = QFileDialog.getOpenFileNames(
            self,
            self.translate("bridgePanel", "Open V2Ray-core Config File"),
            "",
            """
                                                  V2Ray config file (*.json);;
                                                  All File (*);;
                                                  """,
            options=options)

        if (filePaths):
            return filePaths
        else:
            return False

    def createBridgepreferencesPanel(self):
        self.createpreferencesPanel = bridgePreference.bridgepreferencesPanel(
            self.bridgetreasureChest)
        self.createpreferencesPanel.setAttribute(Qt.WA_DeleteOnClose)
        self.createpreferencesPanel.createpreferencesPanel()
        self.createpreferencesPanel.setWindowIcon(self.iconStart)
        self.createpreferencesPanel.move(
            QApplication.desktop().screen().rect().center() -
            self.createpreferencesPanel.rect().center())
        self.createpreferencesPanel.open()
        self.createpreferencesPanel.exec_()

    def settingv2rayshelltableWidget(self):
        v2rayConfigFiles = self.bridgetreasureChest.getV2raycoreconfigFiles()
        if not v2rayConfigFiles: return
        v2rayConfigFilesNumber = len(v2rayConfigFiles)
        if (v2rayConfigFilesNumber > 0):
            self.tableWidgetBridge.setRowCount(0)
            for i in range(v2rayConfigFilesNumber):
                try:
                    enable = bool(v2rayConfigFiles[i]["enable"])
                    hostName = str(v2rayConfigFiles[i]["hostName"])
                    configFileName = str(v2rayConfigFiles[i]["configFileName"])
                except Exception:
                    pass

                radioButtonStopStart = QRadioButton(self)
                radioButtonStopStart.setIcon(
                    self.iconStop if not enable else self.iconStart)
                radioButtonStopStart.setChecked(True if enable else False)
                radioButtonStopStart.setIconSize(self.__iconSize)
                self.radioButtonGroup.addButton(radioButtonStopStart)
                self.tableWidgetBridge.setRowCount(i + 1)
                self.tableWidgetBridge.setCellWidget(i, 0,
                                                     radioButtonStopStart)
                self.tableWidgetBridge.setItem(i, 1,
                                               QTableWidgetItem(hostName))
                self.tableWidgetBridge.setItem(
                    i, 2, QTableWidgetItem(configFileName))
                self.tableWidgetBridge.setItem(
                    i, 3,
                    QTableWidgetItem(
                        self.getProxyAddressFromJSONFile(configFileName)))
                self.tableWidgetBridge.resizeColumnsToContents()
                #self.tableWidgetBridge.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)

    def onsaveV2rayshellConfigFile(self):
        self.bridgetreasureChest.clearconfigFiles()
        rowCount = self.tableWidgetBridge.rowCount()
        for i in range(rowCount):
            enable = self.tableWidgetBridge.cellWidget(i, 0)
            if enable and enable.isChecked():
                enable = True
            else:
                enable = False

            hostName = self.tableWidgetBridge.item(i, 1)
            if hostName:
                hostName = hostName.text()
            else:
                hostName = ""

            config = self.tableWidgetBridge.item(i, 2)
            if config:
                config = config.text()
            else:
                config = ""
            self.bridgetreasureChest.setV2raycoreconfigFiles(
                enable, hostName, configFileName=config)
        self.bridgetreasureChest.save.emit()

    def oncreatenauticalChartPanel(self):
        v2rayConfigFileName = self.tableWidgetBridge.item(
            clickedRow.rightClickedRow, 2)
        if (v2rayConfigFileName):
            nc = nauticalChartPanel.nauticalChartPanel(
                v2rayConfigFileName.text())
            nc.setAttribute(Qt.WA_DeleteOnClose)
            nc.createPanel()
            nc.setWindowTitle(
                self.translate("bridgePanel", "V2Ray config file edit"))
            nc.setWindowIcon(self.iconStart)
            nc.setGeometry(0, 0, 1024, 768)
            ### move widget to center
            nc.move(QApplication.desktop().screen().rect().center() -
                    nc.rect().center())
            nc.show()
            nc.exec_()

    def tableWidgetBridgeAddNewV2rayConfigFile(self):
        configFileNames = self.onopenV2rayConfigJSONFile()
        if (configFileNames):
            for configFileName in configFileNames:
                rowCount = self.tableWidgetBridge.rowCount()
                radioButtonStopStart = QRadioButton(self)
                radioButtonStopStart.setIcon(self.iconStop)
                radioButtonStopStart.setIconSize(self.__iconSize)
                self.radioButtonGroup.addButton(radioButtonStopStart)

                self.tableWidgetBridge.setRowCount(rowCount + 1)
                self.tableWidgetBridge.setCellWidget(rowCount, 0,
                                                     radioButtonStopStart)
                self.tableWidgetBridge.setItem(rowCount, 1,
                                               QTableWidgetItem(""))
                self.tableWidgetBridge.setItem(
                    rowCount, 2, QTableWidgetItem(configFileName))
                self.tableWidgetBridge.setItem(
                    rowCount, 3,
                    QTableWidgetItem(
                        self.getProxyAddressFromJSONFile(configFileName)))
                self.tableWidgetBridge.resizeColumnsToContents()
        else:
            pass

    def tableWidgetBridgeDelete(self):
        self.tableWidgetBridge.removeRow(clickedRow.rightClickedRow)

    def validateV2rayJSONFile(self, JSONData):
        """
        simply validate a V2Ray json file.
        """
        try:
            JSONData["inbound"]
            JSONData["outbound"]
        except KeyError:
            return False
        else:
            return True

    def about(self):
        NineteenEightySeven = QLabel(
            self.translate(
                "bridgePanel",
                """Across the Great Wall, we can reach every corner in the world."""
            ))  ### Crossing the Great Wall to Join the World
        Timeless = QLabel(
            self.translate(
                "bridgePanel",
                """You weren't thinking about that when you were creating it.\nBecause if you did? You never would have gone through with it."""
            ))
        DwayneRichardHipp = QLabel(
            self.translate(
                "bridgePanel",
                """May you do good and not evil.\nMay you find forgiveness for yourself and forgive others.\nMay you share freely, never taking more than you give."""
            ))
        vbox = QVBoxLayout()
        vbox.addWidget(NineteenEightySeven)
        vbox.addWidget(Timeless)
        vbox.addWidget(DwayneRichardHipp)

        dialogAbout = QDialog()
        dialogAbout.setAttribute(Qt.WA_DeleteOnClose)
        dialogAbout.setWindowTitle(
            self.translate("bridgePanel", "About V2Ray-shell"))
        dialogAbout.setWindowIcon(self.iconStart)
        dialogAbout.move(QApplication.desktop().screen().rect().center() -
                         dialogAbout.rect().center())
        dialogAbout.setLayout(vbox)
        dialogAbout.open()
        dialogAbout.exec_()

    def bugReportPanel(self):
        self.bugReport = bugReport.bugReport()
        self.bugReport.setAttribute(Qt.WA_DeleteOnClose)
        self.bugReport.setWindowTitle(
            self.translate("bridgePanel", "Bug Report"))
        self.bugReport.setWindowIcon(self.iconStart)
        self.bugReport.createPanel()
        self.bugReport.show()
        self.bugReport.setGeometry(250, 150, 1024, 768)
Beispiel #51
0
class Coropata(QLabel):
    clicked = pyqtSignal()

    def __init__(self): # Really not meant to have a parent yet
        QLabel.__init__(self, None)

        self.quitAction = None
        self.trayIconMenu = None
        self.trayIcon = None
        self.createTrayIcon()

        self.setWindowFlags(Qt.FramelessWindowHint |
                            Qt.WindowStaysOnTopHint |
                            Qt.BypassWindowManagerHint)
        self.setAttribute(Qt.WA_TranslucentBackground)

        self.m_hoverbox = Hoverbox()
        self.m_hoverbox.show()

        self.m_animation_timer = QTimer(self)
        self.m_animation_timer.setInterval(100)

        self.m_physics_timer = QTimer(self)
        self.m_physics_timer.setInterval(30)
        self.m_physics_timer.timeout.connect(self.physicsTimerEvent)

        self.screen_width = QDesktopWidget().screenGeometry().width()
        self.screen_height = QDesktopWidget().screenGeometry().height()

        # Initialize properties
        self.m_velocity = QPoint(0, 0)
        self.m_pos = QPoint(self.screen_width / 2, (5.0 / 6.0) * self.screen_height)
        self.m_offset = QPoint(0, 0)
        self.m_size = QSize(0, 0)

        self._velocity = self.m_velocity
        self._pos = self.m_pos
        self._offset = self.m_offset
        self._size = self.m_size

        self.stateMachine = xmlToStateMachine(self, 'coropata.xml')
        self.stateMachine.start()

        self.m_animation_timer.start()
        self.m_physics_timer.start()

    # Properties ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    @pyqtProperty(QSize)
    def _size(self):
        return self.m_size

    @_size.write
    def _size(self, value):
        self.m_size = value
        self.setFixedSize(value)

    @pyqtProperty(QPoint)
    def _offset(self):
        return self.m_offset

    @_offset.write
    def _offset(self, value):
        self.m_offset = value
        self.move(self.m_pos + self.m_offset)

    @pyqtProperty(QPoint)
    def _pos(self):
        return self.m_pos

    @_pos.write
    def _pos(self, value):
        self.m_pos = value
        self.move(self.m_pos + self.m_offset)
        self.m_hoverbox.move(self.m_pos - QPoint(60, 70))

    @pyqtProperty(QPoint)
    def _velocity(self):
        return self.m_velocity

    @_velocity.write
    def _velocity(self, value):
        self.m_velocity = value
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    def mousePressEvent(self, e):
        self.clicked.emit()

    def closeEvent(self, e):
        QApplication.instance().closeAllWindows()

    def keyPressEvent(self, e):
        if e.key() == Qt.Key_Q or e.key() == Qt.Key_Escape:
            QApplication.instance().closeAllWindows()

    def physicsTimerEvent(self):
        p = self.m_pos + self.m_velocity

        if p.x() < -self._size.width():
            p.setX(self.screen_width)
        elif p.x() > self.screen_width + 10:
            p.setX(-self._size.width())

        self._pos = p

    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 #52
0
class MainWindow(QMainWindow):
    tray_icon = None
    note_expire = pyqtSignal()
    current_state_signal = pyqtSignal(object)
    processing = pyqtSignal()
    state_changed = pyqtSignal()
    focus_in = pyqtSignal()
    access_links_result = pyqtSignal(bool, str)

    def __init__(self, config, island_manager, setup, torrent_manager):
        super(MainWindow, self).__init__()
        icon = QIcon()
        icon.addPixmap(QPixmap(":/images/icons/island128.png"))
        self.tray_menu = QMenu()
        self.menu_actions = self.prepare_tray_menu(self.tray_menu)
        self.tray_icon = QSystemTrayIcon(self)
        self.tray_icon.setContextMenu(self.tray_menu)
        self.tray_icon.setIcon(icon)
        self.tray_icon.activated.connect(self.process_tray_icon_activation)
        self.tray_icon.show()
        self.exiting = False
        self.config = config
        self.setup = setup
        self.island_manager = island_manager
        self.states = self.prepare_states()
        self.current_state = States.UNKNOWN
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.key_manager = KeyManager(self.config)
        self.assign_handlers()
        self.setup_window = None
        self.torrent_manager = torrent_manager
        self.set_state(States.UNKNOWN)
        self.refresh_island_status()
        self.set_main_window_title()
        # island status refresh counter
        self.local_access = None
        self.admin_access = None
        self.refresh_count = 0
        log.debug("Main window controller initialized.")
        self.launch_background_links_updater()
        self.pending_state = False
        adjust_window_size(self)

    def event(self, event):
        if event.type() == QEvent.ActivationChange:
            log.debug("Focus In detected")
            self.refresh_island_status()
            return True

        if event.type() == QEvent.Close:
            log.debug("Close event caught!")
            self.process_close(event)
            return True

        return super().event(event)

    def process_close(self, event):
        if not self.exiting:
            event.ignore()
            self.showMinimized()

    def refresh_island_status(self):
        if self.pending_state:
            log.debug("pending state...")
            return
        try:
            if self.setup.is_setup_required():
                self.set_state(States.SETUP_REQUIRED)
            else:
                self.island_manager.emit_islands_current_state(
                    self.state_emitter)
        except Exception as e:
            log.error("Error refreshing island statuus: %s" % str(e))
            self.set_state(States.UNKNOWN)

    def prepare_states(self):
        return {
            States.SETUP_REQUIRED: self.set_setup_required,
            States.RUNNING: self.set_running,
            States.NOT_RUNNING: self.set_not_running,
            States.STARTING_UP: self.set_starting_up,
            States.SHUTTING_DOWN: self.set_shutting_down,
            States.RESTARTING: self.set_restarting,
            States.UNKNOWN: self.set_unknown
        }

    def request_to_run_setup(self):
        message = "Islands setup is required. Would you like to launch it now?"
        res = QM.question(self, '', message, QM.Yes | QM.No)
        if res == QM.Yes:
            self.launch_setup()

    # Connects UI element with handler
    def assign_handlers(self):
        """
        Assigns handlers for all UI elements events
        :return:
        """
        self.ui.launchIslandButton.clicked.connect(
            self.get_main_control_handler("launch"))
        self.ui.shutdownIslandButton.clicked.connect(
            self.get_main_control_handler("stop"))
        self.ui.restartIslandButton.clicked.connect(
            self.get_main_control_handler("restart"))
        self.ui.button_launch_setup.clicked.connect(self.launch_setup)
        self.current_state_signal.connect(self.set_state)
        self.ui.actionInfo.triggered.connect(self.show_app_info)
        self.ui.actionClose.triggered.connect(self.quit_app)
        self.ui.actionMinimize_2.triggered.connect(self.minimize_main_window)
        self.processing.connect(self.set_working)
        self.state_changed.connect(self.refresh_island_status)
        self.ui.act_islandsimage_authoring.triggered.connect(self.author_image)
        self.ui.act_my_torrents.triggered.connect(self.show_my_torrents)
        self.ui.act_trusted_keys.triggered.connect(
            lambda: self.open_keys_form())
        self.ui.act_view_logs.triggered.connect(self.open_logs_form)
        self.ui.act_my_keys.triggered.connect(
            lambda: self.open_keys_form(True))
        self.ui.act_open_config.triggered.connect(lambda: self.open_config())
        self.ui.act_update_vm.triggered.connect(self.open_update)
        self.ui.act_help.triggered.connect(self.open_user_guide)
        self.access_links_result.connect(self.set_links_on_signal)
        self.ui.btn_go_to_island.clicked.connect(self.open_island_link)
        self.ui.btn_admin.clicked.connect(
            lambda: self.open_island_link(admin=True))
        self.note_expire.connect(self.hide_note)

        self.ui.btn_close_hint.clicked.connect(self.hide_note)

    def open_user_guide(self):
        help_form = Helpform(self)
        help_form.exec()

    def open_logs_form(self):
        logs_form = LogsForm(self, self.config["manager_data_folder"])
        logs_form.exec()

    def author_image(self):
        log.debug("Opening image authoring form")
        form = ImageAuthoringForm(parent=self,
                                  key_manager=self.key_manager,
                                  config=self.config,
                                  torrent_manager=self.torrent_manager)
        form.exec()
        log.debug("Image authoring form closed")

    def state_emitter(self, state):
        """
        Given a state emits it to PYQT so it could update all UI elements accordingly.
        :param state:
        :return:
        """
        self.current_state_signal.emit(state)

    def get_main_control_handler(self, cmd):
        cmds = {
            "launch": "launch_island",
            "stop": "stop_island",
            "restart": "restart_island",
        }
        params = {
            "Quiet": "",
            "Normal": ", False",
            "Soft": "",
            "Force": ", True"
        }

        def get_param(cmd):
            if cmd == "launch" or cmd == "restart":
                return params[self.ui.launchMode.currentText()]
            elif cmd == "stop":
                return params[self.ui.stopMode.currentText()]

        if cmd not in cmds:
            raise KeyError

        def handler():
            self.set_working()
            try:
                param = get_param(cmd)
                command = (
                    "self.island_manager.{cmd}(self.state_emitter{param})".
                    format(cmd=cmds[cmd], param=param if param else ""))
                res = eval(command)
            except Exception as e:
                print("Error occured")
                print(e)

        return handler

    def open_config(self):
        self.activateWindow()
        log.debug("Opening")
        config = ConfigForm(self, self.config, self.setup, self.island_manager)
        config.exec()
        self.refresh_island_status()
        self.state_changed.emit()

    def open_update(self):
        log.debug("opening update form")
        update_form = UpdateForm(self, self.config, self.island_manager,
                                 self.setup)
        update_form.exec()
        sleep(1)
        self.state_changed.emit()

    def launch_setup(self):
        self.setup_window = SetupWindow(self, self.config, self.island_manager,
                                        self.setup)
        self.set_setup_window_onclose_handler(self.setup_window)
        self.setup_window.set_vbox_checker(self.setup.is_vbox_set_up)
        self.setup_window.set_islands_vm_checker(
            self.setup.is_islands_vm_exist)
        res = self.setup_window.exec()
        print("WIZARD RESULT IS {res}".format(res=res))
        self.refresh_island_status()

    def open_keys_form(self, is_private_keys=False):
        keys_form = KeysForm(self, self.key_manager, self.config,
                             is_private_keys)
        keys_form.exec()

    def show_my_torrents(self):
        self.activateWindow()
        torrents_dialog = TorrentsForm(self, self.torrent_manager, self.config)
        torrents_dialog.exec()

    def set_setup_window_onclose_handler(self, window):
        def handler():
            self.refresh_island_status()

        window.on_close(handler)

    def set_state(self, state):
        if state not in self.states:
            raise KeyError("Invalid main window state.")
        try:
            self.states[state]()
        except Exception as e:
            log.error("Error setting state %s: %s" % (state, str(e)))

    """MENU HANDLERS"""

    def minimize_main_window(self):
        self.showMinimized()

    def show_app_info(self):
        self.show()
        QM.about(
            self, "",
            "Islands Virtual Machine Manager\nVersion: %s" % get_version())

    def on_close(self):
        self.close()

    def set_main_window_title(self):
        self.setWindowTitle("Islands Manager %s" % "v" + get_version())

    """ STATE SETTERS """

    def toggle_loading_animation(self, activate):
        self.loading_animation = QMovie("resources/icons/loading.gif")
        self.ui.lbl_loading.setMovie(self.loading_animation)
        self.loading_animation.start()
        log.debug("Toggling loading animation")
        self.ui.lbl_loading.setVisible(activate)
        if sys.platform == "darwin":
            self.repaint()

    def set_setup_required(self):
        self.current_state = States.SETUP_REQUIRED
        self.pending_state = False
        self.menu_actions["start"].setEnabled(False)
        self.menu_actions["stop"].setEnabled(False)
        self.menu_actions["restart"].setEnabled(False)
        self.ui.btn_go_to_island.setVisible(False)
        self.ui.btn_admin.setVisible(False)
        reason = self.setup.is_setup_required()
        self.ui.islandStatus.setText("Setup required")
        self.ui.islandStatus.setStyleSheet('color: orange')
        self.ui.restartIslandButton.setEnabled(False)
        self.ui.shutdownIslandButton.setEnabled(False)
        self.ui.launchIslandButton.setEnabled(False)
        self.ui.groupBox.setEnabled(True)
        self.ui.setup_required_reason.setText(reason)
        self.ui.groupBox.show()
        self.ui.launchMode.setEnabled(False)
        self.ui.stopMode.setEnabled(False)
        self.toggle_loading_animation(False)
        if sys.platform == "darwin":
            self.repaint()

    def set_running(self):
        self.current_state = States.RUNNING
        self.toggle_loading_animation(False)
        self.pending_state = False
        self.menu_actions["start"].setEnabled(False)
        self.menu_actions["stop"].setEnabled(True)
        self.menu_actions["restart"].setEnabled(True)
        self.load_access_links()
        self.ui.islandStatus.setText("Running")
        self.ui.islandStatus.setStyleSheet('color: green')
        self.ui.restartIslandButton.setEnabled(True)
        self.ui.shutdownIslandButton.setEnabled(True)
        self.ui.launchIslandButton.setEnabled(False)
        self.ui.groupBox.setEnabled(False)
        self.ui.groupBox.hide()
        self.ui.launchMode.setEnabled(False)
        self.ui.stopMode.setEnabled(True)
        if sys.platform == "darwin":
            self.repaint()

    def set_starting_up(self):
        self.current_state = States.STARTING_UP
        self.ui.btn_go_to_island.setVisible(False)
        self.toggle_loading_animation(True)
        self.ui.btn_admin.setVisible(False)
        self.pending_state = True
        self.menu_actions["start"].setEnabled(False)
        self.menu_actions["stop"].setEnabled(False)
        self.menu_actions["restart"].setEnabled(False)
        self.ui.islandStatus.setText("Starting up...")
        self.ui.islandStatus.setStyleSheet('color: blue')
        self.ui.restartIslandButton.setEnabled(False)
        self.ui.shutdownIslandButton.setEnabled(False)
        self.ui.launchIslandButton.setEnabled(False)
        self.ui.groupBox.setEnabled(False)
        self.ui.groupBox.hide()
        self.ui.launchMode.setEnabled(False)
        self.ui.stopMode.setEnabled(False)
        if sys.platform == "darwin":
            self.repaint()

    def set_shutting_down(self):
        self.current_state = States.SHUTTING_DOWN
        self.ui.btn_go_to_island.setVisible(False)
        self.ui.btn_admin.setVisible(False)
        self.pending_state = True
        self.menu_actions["start"].setEnabled(False)
        self.menu_actions["stop"].setEnabled(False)
        self.menu_actions["restart"].setEnabled(False)
        self.ui.islandStatus.setText("Shutting down...")
        self.ui.islandStatus.setStyleSheet('color: orange')
        self.ui.groupBox.setEnabled(False)
        self.ui.groupBox.hide()
        self.set_working()
        if sys.platform == "darwin":
            self.repaint()

    def set_not_running(self):
        self.ui.btn_go_to_island.setVisible(False)
        self.ui.btn_admin.setVisible(False)
        self.toggle_loading_animation(False)
        self.current_state = States.NOT_RUNNING
        self.pending_state = False
        self.menu_actions["start"].setEnabled(True)
        self.menu_actions["stop"].setEnabled(False)
        self.menu_actions["restart"].setEnabled(False)
        self.ui.islandStatus.setText("Not running")
        self.ui.islandStatus.setStyleSheet('color: red')
        self.ui.restartIslandButton.setEnabled(False)
        self.ui.shutdownIslandButton.setEnabled(False)
        self.ui.launchIslandButton.setEnabled(True)
        self.ui.groupBox.setEnabled(False)
        self.ui.groupBox.hide()
        self.ui.launchMode.setEnabled(True)
        self.ui.stopMode.setEnabled(False)
        if sys.platform == "darwin":
            self.repaint()

    def set_unknown(self):
        self.ui.btn_go_to_island.setVisible(False)
        self.ui.btn_admin.setVisible(False)
        self.current_state = States.UNKNOWN
        self.toggle_loading_animation(False)
        self.pending_state = False
        self.menu_actions["start"].setEnabled(False)
        self.menu_actions["stop"].setEnabled(False)
        self.menu_actions["restart"].setEnabled(False)
        self.ui.islandStatus.setText("Unknown")
        self.ui.islandStatus.setStyleSheet('color: gray')
        self.ui.restartIslandButton.setEnabled(False)
        self.ui.shutdownIslandButton.setEnabled(False)
        self.ui.launchIslandButton.setEnabled(False)
        self.ui.groupBox.setEnabled(False)
        self.ui.groupBox.hide()
        self.ui.launchMode.setEnabled(False)
        self.ui.stopMode.setEnabled(False)
        if sys.platform == "darwin":
            self.repaint()

    def set_restarting(self):
        self.ui.btn_go_to_island.setVisible(False)
        self.ui.btn_admin.setVisible(False)
        self.current_state = States.RESTARTING
        self.pending_state = True
        self.toggle_loading_animation(True)
        self.menu_actions["start"].setEnabled(False)
        self.menu_actions["stop"].setEnabled(False)
        self.menu_actions["restart"].setEnabled(False)
        self.ui.islandStatus.setText("Restarting...")
        self.ui.islandStatus.setStyleSheet('color: blue')
        self.set_working()
        if sys.platform == "darwin":
            self.repaint()

    def set_working(self):
        self.pending_state = True
        self.toggle_loading_animation(True)
        self.menu_actions["start"].setEnabled(False)
        self.menu_actions["stop"].setEnabled(False)
        self.menu_actions["restart"].setEnabled(False)
        self.ui.restartIslandButton.setEnabled(False)
        self.ui.shutdownIslandButton.setEnabled(False)
        self.ui.launchIslandButton.setEnabled(False)
        self.ui.launchMode.setEnabled(False)
        self.ui.stopMode.setEnabled(False)
        if sys.platform == "darwin":
            self.repaint()

    # HELPERS
    def hide_note(self):
        self.ui.grp_note_container.setVisible(False)
        if sys.platform == "darwin":
            self.repaint()

    def show_note(self, msg, lifespan=20):
        self.ui.lbl_hint.setText(msg)
        self.ui.grp_note_container.setVisible(True)
        if sys.platform == "darwin":
            self.repaint()

        def expire_note_on_timeout():
            sleep(lifespan)
            self.note_expire.emit()

        t = Thread(target=expire_note_on_timeout)
        t.start()

    def set_links_on_signal(self, result, link):
        log.debug("Link result signal received: result: %s, link: %s" %
                  (str(result), str(link)))
        if self.current_state != States.RUNNING:
            log.debug("Got the links, but the VM is not running. Returning")
            self.ui.btn_admin.setVisible(False)
            self.ui.btn_go_to_island.setVisible(False)
            return
        elif not result:
            log.warning(
                "Unable to get connection links to access island. Network configuration is invalid"
            )
            self.ui.btn_admin.setVisible(False)
            self.ui.btn_go_to_island.setVisible(False)
            self.show_note(
                "Unable to connect to island. Network configuration is invalid."
            )
            return
        admin_exist = bool(is_admin_registered(self.config["data_folder"]))
        log.debug("admin exists: %s, data folder: %s" %
                  (str(admin_exist), self.config["data_folder"]))
        self.ui.btn_admin.setVisible(True)
        if admin_exist:
            self.ui.btn_go_to_island.setVisible(True)
        else:
            self.show_note(
                "Looks like you haven't set up your master password yet. Please click on \"Admin\" button and follow the instructions in the browser. You will be able to use your island once master password is set"
            )
        connstr = "http://%s:%s" % (link, self.config["local_access_port"])
        self.local_access = connstr
        self.admin_access = "%s/%s" % (connstr, "admin")

    def open_island_link(self, admin=False):
        if self.current_state == States.RUNNING:
            webbrowser.open(self.admin_access) if admin else webbrowser.open(
                self.local_access)
        else:
            log.error("Attempt to open a link while island is not running")

    def load_access_links(self):
        self.ui.btn_go_to_island.setVisible(False)
        self.ui.btn_admin.setVisible(False)
        worker = self._get_link_loader_worker()
        t = Thread(target=worker)
        t.start()

    def launch_background_links_updater(self):
        worker = self._get_link_loader_worker()

        def background_updater():
            while True:
                if self.current_state == States.RUNNING:
                    worker()
                    log.debug("links updated!")
                for _ in range(20):
                    if self.exiting:
                        return
                    sleep(.5)

        t = Thread(target=background_updater)
        t.start()

    def _get_link_loader_worker(self):
        def worker():
            counter = 0
            iterations = 5
            while self.current_state == States.RUNNING and counter < iterations:
                if self.exiting:
                    return
                vm_info = self.setup.get_vm_info()
                if vm_info is None:
                    log.error(
                        "VM info not found! That means that virtual machine is not registered with Virtualbox"
                    )
                    self.access_links_result.emit(False, "")
                    return
                if "Net1" in vm_info:
                    self.access_links_result.emit(True, vm_info["Net1"][1])
                    return
                else:
                    log.debug("Links not yet found. Retrying...")
                    counter += 1
                    sleep(2 + counter)
            if self.current_state == States.RUNNING:
                self.access_links_result.emit(False, "")

        return worker

    """ ~ END STATES """

    def show_notification(self, text):
        QM.warning(self, None, "Warning", text, buttons=QM.Ok)

    def quit_app(self, silent=False):
        if silent:
            self.exiting = True
            self.close()
            return

        if QM.question(self, "Exit confirm", "Quit the Island Manager?",
                       QM.Yes | QM.No) == QM.Yes:
            log.debug("Quitting the islands manager...")
            self.exiting = True
            self.close()

    def show_hide(self):
        if self.isMinimized():
            self.activateWindow()
            self.showNormal()
        else:
            self.showMinimized()

    def process_tray_icon_activation(self, reason):
        log.debug("Tray icon activation detected!")
        if reason == QSystemTrayIcon.DoubleClick:
            log.debug("Tray icon double click detected")
            self.show_hide()

    def prepare_tray_menu(self, menu: QMenu):
        actions = {}
        quit_act = QAction("Quit", self)
        show_hide_act = QAction("Show / Hide", self)
        torrents_act = QAction("Torrents", self)
        start_island = QAction("Start Island", self)
        stop_island = QAction("Stop Island", self)
        restart_island = QAction("Restart Island", self)
        config_act = QAction("Config...", self)

        quit_act.setIcon(QIcon("resources/icons/exit.svg"))
        show_hide_act.setIcon(QIcon("resources/icons/plus-minus.png"))
        torrents_act.setIcon(QIcon("resources/icons/torrents.png"))
        start_island.setIcon(QIcon("resources/icons/play.png"))
        stop_island.setIcon(QIcon("resources/icons/stop.png"))
        restart_island.setIcon(QIcon("resources/icons/reload.png"))
        config_act.setIcon(QIcon("resources/icons/settings.png"))

        menu.addAction(show_hide_act)
        menu.addSeparator()
        menu.addActions([start_island, stop_island, restart_island])
        menu.addSeparator()
        menu.addAction(torrents_act)
        menu.addSeparator()
        menu.addAction(config_act)
        menu.addSeparator()
        menu.addAction(quit_act)

        torrents_act.triggered.connect(self.show_my_torrents)
        config_act.triggered.connect(self.open_config)
        quit_act.triggered.connect(self.quit_app)
        show_hide_act.triggered.connect(self.show_hide)
        start_island.triggered.connect(self.get_main_control_handler("launch"))
        stop_island.triggered.connect(self.get_main_control_handler("stop"))
        restart_island.triggered.connect(
            self.get_main_control_handler("restart"))

        start_island.setEnabled(False)
        stop_island.setEnabled(False)
        restart_island.setEnabled(False)

        actions["start"] = start_island
        actions["stop"] = stop_island
        actions["restart"] = restart_island
        actions["config"] = config_act
        actions["torrent"] = torrents_act
        actions["show"] = show_hide_act
        actions["quit"] = quit_act
        return actions
Beispiel #53
0
class TrayIcon(QObject):

    def __init__(self):
        super().__init__()
        self._supported = QSystemTrayIcon.isSystemTrayAvailable()
        self._context_menu = None
        self._enabled = False
        self._trayicon = None
        self._state_icons = {}
        for state in (
            'disconnected',
            'disabled',
            'enabled',
        ):
            icon = QIcon(':/state-%s.svg' % state)
            if hasattr(icon, 'setIsMask'):
                icon.setIsMask(True)
            self._state_icons[state] = icon
        self._machine = None
        self._machine_state = 'disconnected'
        self._is_running = False
        self._update_state()

    def set_menu(self, menu):
        self._context_menu = menu
        if self._enabled:
            self._trayicon.setContextMenu(menu)

    def log(self, level, message):
        if self._enabled:
            if level <= log.INFO:
                icon = QSystemTrayIcon.Information
                timeout = 10
            elif level <= log.WARNING:
                icon = QSystemTrayIcon.Warning
                timeout = 15
            else:
                icon = QSystemTrayIcon.Critical
                timeout = 25
            self._trayicon.showMessage(__software_name__.capitalize(),
                                       message, icon, timeout * 1000)
        else:
            if level <= log.INFO:
                icon = QMessageBox.Information
            elif level <= log.WARNING:
                icon = QMessageBox.Warning
            else:
                icon = QMessageBox.Critical
            msgbox = QMessageBox()
            msgbox.setText(message)
            msgbox.setIcon(icon)
            msgbox.exec_()

    def is_supported(self):
        return self._supported

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

    def disable(self):
        if not self._enabled:
            return
        self._trayicon.hide()
        self._trayicon = None
        self._enabled = False

    def is_enabled(self):
        return self._enabled

    def update_machine_state(self, machine, state):
        self._machine = machine
        self._machine_state = state
        self._update_state()

    def update_output(self, enabled):
        self._is_running = enabled
        self._update_state()

    clicked = pyqtSignal()

    def _update_state(self):
        if self._machine_state not in ('initializing', 'connected'):
            state = 'disconnected'
        else:
            state = 'enabled' if self._is_running else 'disabled'
        icon = self._state_icons[state]
        if not self._enabled:
            return
        machine_state = _('{machine} is {state}').format(
            machine=_(self._machine),
            state=_(self._machine_state),
        )
        if self._is_running:
            output_state = _('output is enabled')
        else:
            output_state = _('output is disabled')
        self._trayicon.setIcon(icon)
        self._trayicon.setToolTip(
            'Plover:\n- %s\n- %s' % (output_state, machine_state)
        )

    def _on_activated(self, reason):
        if reason == QSystemTrayIcon.Trigger:
            self.clicked.emit()
Beispiel #54
0
class mainwindow(QtWidgets.QMainWindow, main1):
    hk = SystemHotkey()
    config = ConfigObj("setup.ini", encoding='UTF8')
    s1 = config['keyboard_shortcut']['screenshot']
    s2 = config['keyboard_shortcut']['minimodel']
    s3 = config['background']['path']
    s4 = config['setting']['minimize']
    s5 = config['setting']['welcome']
    hotkey_sig = pyqtSignal()
    hotkey_sig0 = pyqtSignal()
    hotkey_sig1 = pyqtSignal()
    hotkey_sig2 = pyqtSignal()

    def __init__(self):
        super(mainwindow, self).__init__()
        self.hotkey_use()
        self.setFixedSize(757, 634)
        self.initUI()

    def initUI(self):
        self.config = ConfigObj("setup.ini", encoding='UTF8')
        self.s1 = self.config['keyboard_shortcut']['screenshot']
        self.s2 = self.config['keyboard_shortcut']['minimodel']
        self.s3 = self.config['background']['path']
        self.s4 = self.config['setting']['minimize']
        self.s5 = self.config['setting']['welcome']
        #设置背景图片
        window_pale = QPalette()
        pix = QPixmap(self.s3)
        pix.scaled(self.width(), self.height())
        window_pale.setBrush(self.backgroundRole(), QBrush(pix))
        self.setPalette(window_pale)
        #
        self.hotkey_use_1(self.s1)
        self.hotkey_use_2(self.s2)
        self.setupUi(self)
        self.pushButton.clicked.connect(self.prt_sc)
        self.pushButton_5.clicked.connect(self.translate)
        self.pushButton_4.clicked.connect(self.setup)
        self.pushButton_6.clicked.connect(self.search_single)
        self.pushButton_2.clicked.connect(self.open_min)
        self.pushButton_3.clicked.connect(self.copy)
        self.setWindowIcon(QIcon('image/ico.png'))
        self.setWindowTitle("翻译软件测试版")

    #定义设置按钮的功能
    def setup(self):
        self.dia = Ui_Dialog()
        self.dia.sig.connect(self.initUI)
        self.dia.sig1.connect(lambda: self.hotkey_use_1)
        self.dia.sig2.connect(lambda: self.hotkey_use_2)
        self.dia.show()

    #定义min模式按钮
    def open_min(self):
        self.mi = MINI()
        self.mi.show()
        self.tray_min()
        self.hide()

    #复制
    def copy(self):
        strt = self.textBrowser_2.toPlainText()
        clipboard = QApplication.clipboard()
        clipboard.setText(str(strt))

    #截屏
    def prt_sc(self):
        self.textBrowser.clear()
        self.textBrowser_2.clear()
        self.showMinimized()
        os.system("python3 ./use/ScreenShooter.py")  # 改
        # strt2=GetText()
        # self.textBrowser.setText(strt2)
        # sct.prc()
        self.showNormal()

    #调用翻译函数
    def translate(self):
        strt1 = self.textBrowser.toPlainText()
        strt2 = GetText()
        self.textBrowser.setText(strt2)  # 改
        strt3 = GetTranslation(strt2)
        self.textBrowser_2.setText(strt3)

    def search_single(self):
        self.textBrowser.clear()
        self.textBrowser_2.clear()
        str1 = self.lineEdit.text()
        self.textBrowser.setPlainText(str(str1))
        result = sr.analysis(sr.get_net(str(str1)))
        for i in result:
            self.textBrowser_2.append(i)

    #快捷键(全局热键)
    def hotkey_use(self):
        self.hotkey_sig.connect(self.MKey_pressEvent)
        self.hotkey_sig0.connect(self.open_min)
        m = self.s1.split('+')
        self.hk.register((m[0], m[1]), callback=lambda x: self.h_emit())
        n = self.s2.split('+')
        self.hk.register((n[0], n[1]), callback=lambda x: self.h0_emit())

    def hotkey_use_1(self, first):
        self.hotkey_sig1.connect(self.MKey_pressEvent)
        n = first.split('+')
        self.hk.unregister((n[0], n[1]))
        m = self.s1.split('+')
        self.hk.register((m[0], m[1]), callback=lambda x: self.h1_emit())

    def hotkey_use_2(self, second):
        self.hotkey_sig2.connect(self.open_min)
        n = second.split('+')
        self.hk.unregister((n[0], n[1]))
        m = self.s2.split('+')
        self.hk.register((m[0], m[1]), callback=lambda x: self.h2_emit())

    def MKey_pressEvent(self):
        # sct.prc()
        os.system("python3 ./use/ScreenShooter.py")  # 改

    def h_emit(self):
        self.hotkey_sig.emit()

    def h0_emit(self):
        self.hotkey_sig0.emit()

    def h1_emit(self):
        self.hotkey_sig1.emit()

    def h2_emit(self):
        self.hotkey_sig2.emit()

    #托盘
    def tray_min(self):
        self.tray = QSystemTrayIcon()
        self.icon = QIcon('image/ico.png')
        self.tray.activated.connect(self.iconActivated)
        self.tray.setIcon(self.icon)
        #气泡提示
        self.tray.setToolTip(u'翻译软件测试版')
        self.traymenu = QMenu()
        a1 = QAction(QIcon('image/re.png'), u'还原', self)  #添加一级菜单动作选项(还原窗口程序)
        a1.triggered.connect(self.show)
        a2 = QAction(QIcon('image/exit.png'), u'退出', self)  #添加一级菜单动作选项(退出程序)
        a2.triggered.connect(app.quit)
        self.traymenu.addAction(a2)
        self.traymenu.addAction(a1)
        self.tray.setContextMenu(self.traymenu)
        self.tray.show()
        self.tray.showMessage(u"", '应用已被最小化', icon=1)

    #点击托盘栏图标
    def iconActivated(self, ev):
        if ev == QSystemTrayIcon.Trigger:
            if self.isMinimized() or not self.isVisible():
                self.show()
            else:
                self.hide()

    #重写close事件
    def closeEvent(self, event):
        if (self.s4 == 'true'):
            event.ignore()
            self.hide()
            self.tray_min()
        else:
            pass
Beispiel #55
0
class MainWindow(QWidget):
    """ Main window of application. It is represented by icon in
    taskbar.

    Useful attributes
    -----------------
    addWindow : AddWindow object
    addActive : boolean
        If user wants to add new task, 'addWindow' becomes the QWidget for
        it, and while it is active 'addActive' remains True.
    editWindow : EditWindow object
    editActive : boolean
        If user wants to edit tasks, 'editWindow' becomes the QWidget for
        it, and while it is active 'editActive' remains True.
    tray : QSystemTrayIcon object
        Tray icon and all its attributes like context menu and
        activated action.
    timer : QTimer object
        Timer that fires every second. If one reminder's time is up,
        shows message.
    backupTimer : QTimer object
        Timer that fires every 5 minutes. Saves current state of
        tasks file to backup file.

    """
    def __init__(self):
        """ Init GUI and all required things. """
        super().__init__()

        iconAdd = Icon(byte=icons.add).convertToIcon().getIcon()
        self.tray = QSystemTrayIcon(iconAdd, self)

        menu = QMenu()
        menu.addAction(conf.lang.ADD_ACTION,
                       lambda: self.showWindow(QSystemTrayIcon.Trigger))
        menu.addAction(conf.lang.EDIT_ACTION,
                       lambda: self.showWindow('editAction'))
        menu.addSeparator()
        menu.addAction(conf.lang.OPT_ACTION,
                       lambda: self.showWindow('optAction'))

        menu.addAction(conf.lang.RESTORE, self.restore)
        menu.addAction(conf.lang.QUIT, self.quit)

        self.tray.setContextMenu(menu)
        self.tray.activated.connect(self.showWindow)

        self.tray.show()
        self.addWindow = None
        self.addActive = False
        self.editWindow = None
        self.editActive = False
        self.optWindow = None
        self.optActive = False

        self.timer = QTimer()
        self.timer.timeout.connect(self.timerTick)
        self.timer.start(1000)

        self.backup()
        self.backupTimer = QTimer()
        self.backupTimer.timeout.connect(self.backup)
        self.backupTimer.start(conf.backup * 1000)

    def timerTick(self):
        """ Checks tasks entry's time if it is up. Shows message with
        entry's text if its time's up.

        """
        global tasks
        global rewrite
        for entry in tasks:
            date = entry.getDateTime()
            today = dt.datetime.today()
            if (date - today).days < 0:
                tasks.remove(entry)
                class EvMessageBox(QMessageBox):
                    """ QMessageBox with timer.

                    Parameters
                    ----------
                    text : string
                        Text of message.
                    title : string
                        Title of message window.
                    wicon : QIcon object
                        Icon of message window.
                    icon : QMessageBox.Icon int
                        Icon of message body.
                    timeout : int
                        Time for message has being shown.

                    Useful attributes
                    -----------------
                    timer : QTimer object
                        Timer attached to message.

                    """
                    def __init__(self, text, title, wicon, icon, timeout):
                        super().__init__()
                        self.timeout = timeout
                        self.setText(text)
                        self.setWindowTitle(title)
                        self.setWindowIcon(wicon)
                        self.setIcon(icon)
                        self.addButton(QPushButton(conf.lang.REPEAT.format(
                                                   parseSeconds(conf.tdelta))),
                                       QMessageBox.YesRole)
                        self.addButton(QPushButton(conf.lang.CLOSE),
                                       QMessageBox.NoRole)
                        self.setWindowFlags(Qt.WindowStaysOnTopHint)
                        # self.setTextFormat(Qt.RichText)
                        self.timer = QTimer()
                        self.timer.timeout.connect(self.timerTick)

                    def showEvent(self, event):
                        """ Start timer on message showEvent. """
                        self.currentTime = 0
                        self.timer.start(1000)

                    def timerTick(self):
                        """ Done message on timeout. """
                        self.currentTime += 1
                        if self.currentTime >= self.timeout:
                            self.timer.stop()
                            self.done(-1)

                msgBox = EvMessageBox(
                    entry.text,
                    '{} {}'.format(conf.lang.TASK, date),
                    Icon(byte=icons.alert).convertToIcon().getIcon(),
                    QMessageBox.Information,
                    conf.mesout)
                reply = msgBox.exec_()
                msgBox.raise_()

                if reply != 1:
                    td = conf.tdelta if reply == 0 else 300 - conf.mesout
                    date = dateToStr(dt.datetime.now() + dt.timedelta(0, td))
                    date['date'] = date['date'].replace('/', '.')
                    date['time'] = date['time'].replace('.', ':')
                    tasks.append(Entry(date['date'], date['time'], entry.text))
                rewrite()
                if self.editActive:
                    self.editWindow.filterApply()

    def showWindow(self, event):
        """ Show child windows.
        If event is QSystemTrayIcon.Trigger then it checks if
        all windows are not open and show addWindow.

        If event is 'addAction' it means that user from editWindow
        want to edit reminder, so it opens addWindow.

        Then it checks if addAction or editAction is True, and alert
        appropriate window.

        Then if event is 'editAction' then it opens editWindow.

        """
        if event == 'editAction':
            if self.editActive:
                QApplication.alert(self.editWindow)
            else:
                self.editWindow = EditWindow(self)
                self.editWindow.show()
                self.editWindow.setFocus(True)
                self.editWindow.activateWindow()
            return self.editWindow
        elif event == 'addAction':
            self.addWindow = AddWindow(self)
            self.addWindow.show()
            self.addWindow.setFocus(True)
            self.addWindow.activateWindow()
            return self.addWindow
        elif event == QSystemTrayIcon.Trigger:
            if self.addActive:
                QApplication.alert(self.addWindow)
            else:
                self.addWindow = AddWindow(self)
                self.addWindow.show()
                self.addWindow.setFocus(True)
                self.addWindow.activateWindow()
            return self.addWindow
        elif event == 'optAction':
            if self.addActive:
                self.addWindow.hide()
            if self.editActive:
                self.editWindow.hide()
            self.optWindow = OptionsWindow(self)
            self.optWindow.show()
            self.optWindow.setFocus(True)
            self.optWindow.activateWindow()

    def backup(self):
        """ Copies content of tasks file to backup file. """
        with open(backup, 'w') as to_, open(filename, 'r') as from_:
            to_.write(from_.read())

    def restore(self):
        """ Restores content of tasks file from backup file after
        user confirmation.

        """
        global tasks
        shure = QMessageBox.question(self, conf.lang.RESTORE,
                                     conf.lang.RESTORE_TEXT,
                                     QMessageBox.No | QMessageBox.Yes,
                                     QMessageBox.No)

        if shure == QMessageBox.No:
            pass
        else:
            temp = open(backup).read() # don't forget to read backup
            self.backup()
            with open(filename, 'w') as to_:
                to_.write(temp)
            tasks = []
            readTasks()
            if self.editActive:
                self.editWindow.filterApply()

    def quit(self, really=True):
        """ Quits application. Hides tray icon firstly.
        If really is not True, do not quits the application.
        It is used to re-launch the application.

        """
        self.tray.hide()
        self.timer.stop()
        self.backupTimer.stop()
        if really:
            QCoreApplication.instance().quit()
Beispiel #56
0
class TriblerWindow(QMainWindow):
    resize_event = pyqtSignal()
    escape_pressed = pyqtSignal()
    tribler_crashed = pyqtSignal(str)
    received_search_completions = pyqtSignal(object)

    def __init__(self, core_args=None, core_env=None, api_port=None, api_key=None):
        QMainWindow.__init__(self)
        self._logger = logging.getLogger(self.__class__.__name__)

        QCoreApplication.setOrganizationDomain("nl")
        QCoreApplication.setOrganizationName("TUDelft")
        QCoreApplication.setApplicationName("Tribler")

        self.setWindowIcon(QIcon(QPixmap(get_image_path('tribler.png'))))

        self.gui_settings = QSettings('nl.tudelft.tribler')
        api_port = api_port or int(get_gui_setting(self.gui_settings, "api_port", DEFAULT_API_PORT))
        api_key = api_key or get_gui_setting(self.gui_settings, "api_key", hexlify(os.urandom(16)).encode('utf-8'))
        self.gui_settings.setValue("api_key", api_key)

        api_port = get_first_free_port(start=api_port, limit=100)
        request_manager.port, request_manager.key = api_port, api_key

        self.tribler_started = False
        self.tribler_settings = None
        # TODO: move version_id to tribler_common and get core version in the core crash message
        self.tribler_version = version_id
        self.debug_window = None

        self.error_handler = ErrorHandler(self)
        self.core_manager = CoreManager(api_port, api_key, self.error_handler)
        self.pending_requests = {}
        self.pending_uri_requests = []
        self.download_uri = None
        self.dialog = None
        self.create_dialog = None
        self.chosen_dir = None
        self.new_version_dialog = None
        self.start_download_dialog_active = False
        self.selected_torrent_files = []
        self.has_search_results = False
        self.last_search_query = None
        self.last_search_time = None
        self.start_time = time.time()
        self.token_refresh_timer = None
        self.shutdown_timer = None
        self.add_torrent_url_dialog_active = False

        sys.excepthook = self.error_handler.gui_error

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

        self.token_balance_widget.mouseReleaseEvent = self.on_token_balance_click

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

        connect(self.core_manager.core_state_update, 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)
        connect(self.debug_pane_shortcut.activated, self.clicked_menu_button_debug)
        self.import_torrent_shortcut = QShortcut(QKeySequence("Ctrl+o"), self)
        connect(self.import_torrent_shortcut.activated, self.on_add_torrent_browse_file)
        self.add_torrent_url_shortcut = QShortcut(QKeySequence("Ctrl+i"), self)
        connect(self.add_torrent_url_shortcut.activated, self.on_add_torrent_from_url)

        connect(self.top_search_bar.clicked, self.clicked_search_bar)

        # 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_downloads,
            self.left_menu_button_discovered,
            self.left_menu_button_trust_graph,
            self.left_menu_button_popular,
        ]
        hide_xxx = get_gui_setting(self.gui_settings, "family_filter", True, is_bool=True)
        self.search_results_page.initialize_content_page(hide_xxx=hide_xxx)
        self.search_results_page.channel_torrents_filter_input.setHidden(True)

        self.settings_page.initialize_settings_page()
        self.downloads_page.initialize_downloads_page()
        self.loading_page.initialize_loading_page()
        self.discovering_page.initialize_discovering_page()

        self.discovered_page.initialize_content_page(hide_xxx=hide_xxx)

        self.popular_page.initialize_content_page(hide_xxx=hide_xxx, controller_class=PopularContentTableViewController)

        self.trust_page.initialize_trust_page()
        self.trust_graph_page.initialize_trust_graph()

        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)
            connect(show_downloads_action.triggered, self.clicked_menu_button_downloads)
            token_balance_action = QAction('Show token balance', self)
            connect(token_balance_action.triggered, lambda _: self.on_token_balance_click(None))
            quit_action = QAction('Quit Tribler', self)
            connect(quit_action.triggered, 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.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)

        connect(self.core_manager.events_manager.torrent_finished, self.on_torrent_finished)
        connect(self.core_manager.events_manager.new_version_available, self.on_new_version_available)
        connect(self.core_manager.events_manager.tribler_started, self.on_tribler_started)
        connect(self.core_manager.events_manager.low_storage_signal, self.on_low_storage)
        connect(self.core_manager.events_manager.tribler_shutdown_signal, self.on_tribler_shutdown_state_update)
        connect(self.core_manager.events_manager.config_error_signal, self.on_config_error_signal)

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

        signal.signal(signal.SIGINT, sigint_handler)

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

        self.add_to_channel_dialog = AddToChannelDialog(self.window())

        self.add_torrent_menu = self.create_add_torrent_menu()
        self.add_torrent_button.setMenu(self.add_torrent_menu)

        self.channels_menu_list = self.findChild(ChannelsMenuListWidget, "channels_menu_list")

        connect(self.channels_menu_list.itemClicked, self.open_channel_contents_page)

        # The channels content page is only used to show subscribed channels, so we always show xxx
        # contents in it.
        connect(
            self.core_manager.events_manager.node_info_updated,
            lambda data: self.channels_menu_list.reload_if_necessary([data]),
        )
        connect(self.left_menu_button_new_channel.clicked, self.create_new_channel)

    def create_new_channel(self, checked):
        # TODO: DRY this with tablecontentmodel, possibly using QActions

        def create_channel_callback(channel_name):
            TriblerNetworkRequest(
                "channels/mychannel/0/channels",
                self.channels_menu_list.load_channels,
                method='POST',
                raw_data=json.dumps({"name": channel_name}) if channel_name else None,
            )

        NewChannelDialog(self, create_channel_callback)

    def open_channel_contents_page(self, channel_list_item):
        if not channel_list_item.flags() & Qt.ItemIsEnabled:
            return

        self.channel_contents_page.initialize_root_model_from_channel_info(channel_list_item.channel_info)
        self.stackedWidget.setCurrentIndex(PAGE_CHANNEL_CONTENTS)
        self.deselect_all_menu_buttons()

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

    def delete_tray_icon(self):
        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

    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:
        """

        def close_tribler_gui():
            self.close_tribler()
            # Since the core has already stopped at this point, it will not terminate the GUI.
            # So, we quit the GUI separately here.
            if not QApplication.closingDown():
                QApplication.quit()

        self.downloads_page.stop_loading_downloads()
        self.core_manager.stop(False)
        close_dialog = ConfirmationDialog(
            self.window(),
            tr("<b>CRITICAL ERROR</b>"),
            tr(
                "You are running low on disk space (<100MB). Please make sure to have "
                "sufficient free space available and restart Tribler again."
            ),
            [(tr("Close Tribler"), BUTTON_TYPE_NORMAL)],
        )
        connect(close_dialog.button_clicked, lambda _: close_tribler_gui())
        close_dialog.show()

    def on_torrent_finished(self, torrent_info):
        if "hidden" not in torrent_info or not torrent_info["hidden"]:
            self.tray_show_message(tr("Download finished"), tr("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 tray_set_tooltip(self, message):
        """
        Set a tooltip message for the tray icon, if possible.

        :param message: the message to display on hover
        """
        if self.tray_icon:
            try:
                self.tray_icon.setToolTip(message)
            except RuntimeError as e:
                logging.error("Failed to set tray tooltip: %s", str(e))

    def tray_show_message(self, title, message):
        """
        Show a message at the tray icon, if possible.

        :param title: the title of the message
        :param message: the message to display
        """
        if self.tray_icon:
            try:
                self.tray_icon.showMessage(title, message)
            except RuntimeError as e:
                logging.error("Failed to set tray message: %s", str(e))

    def on_tribler_started(self, version):
        if self.tribler_started:
            logging.warning("Received duplicate Tribler Core started event")
            return

        self.tribler_started = True
        self.tribler_version = version

        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)

        self.fetch_settings()

        self.downloads_page.start_loading_downloads()

        self.setAcceptDrops(True)
        self.setWindowTitle(f"Tribler {self.tribler_version}")

        autocommit_enabled = (
            get_gui_setting(self.gui_settings, "autocommit_enabled", True, is_bool=True) if self.gui_settings else True
        )
        self.channel_contents_page.initialize_content_page(autocommit_enabled=autocommit_enabled, hide_xxx=False)

        hide_xxx = get_gui_setting(self.gui_settings, "family_filter", True, is_bool=True)
        self.discovered_page.initialize_root_model(
            DiscoveredChannelsModel(
                channel_info={"name": tr("Discovered channels")}, endpoint_url="channels", hide_xxx=hide_xxx
            )
        )
        connect(self.core_manager.events_manager.discovered_channel, self.discovered_page.model.on_new_entry_received)

        self.popular_page.initialize_root_model(
            PopularTorrentsModel(channel_info={"name": "Popular torrents"}, hide_xxx=hide_xxx)
        )
        self.popular_page.explanation_text.setText(
            tr("This page show the list of popular torrents collected by Tribler during the last 24 hours.")
        )
        self.popular_page.explanation_container.setHidden(False)

        self.add_to_channel_dialog.load_channel(0)

        if not self.gui_settings.value("first_discover", False) and not self.core_manager.use_existing_core:
            connect(self.core_manager.events_manager.discovered_channel, self.stop_discovering)
            self.window().gui_settings.setValue("first_discover", True)
            self.discovering_page.is_discovering = True
            self.stackedWidget.setCurrentIndex(PAGE_DISCOVERING)
        else:
            self.clicked_menu_button_discovered()
            self.left_menu_button_discovered.setChecked(True)

        self.channels_menu_list.load_channels()

    def stop_discovering(self, response):
        if not self.discovering_page.is_discovering:
            return
        disconnect(self.core_manager.events_manager.discovered_channel, self.stop_discovering)
        self.discovering_page.is_discovering = False
        if self.stackedWidget.currentIndex() == PAGE_DISCOVERING:
            self.clicked_menu_button_discovered()
            self.left_menu_button_discovered.setChecked(True)

    def on_events_started(self, json_dict):
        self.setWindowTitle(f"Tribler {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 update_recent_download_locations(self, destination):
        # 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, str):
            destination = destination.encode('utf-8')
        encoded_destination = hexlify(destination)
        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 perform_start_download_request(
        self,
        uri,
        anon_download,
        safe_seeding,
        destination,
        selected_files,
        total_files=0,
        add_to_channel=False,
        callback=None,
    ):
        # Check if destination directory is writable
        is_writable, error = is_dir_writable(destination)
        if not is_writable:
            gui_error_message = tr(
                "Insufficient write permissions to <i>%s</i> directory. Please add proper "
                "write permissions on the directory and add the torrent again. %s"
            ) % (destination, error)
            ConfirmationDialog.show_message(
                self.window(), tr("Download error <i>%s</i>") % uri, gui_error_message, "OK"
            )
            return

        selected_files_list = []
        if len(selected_files) != total_files:  # Not all files included
            selected_files_list = [filename for filename in selected_files]

        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": uri,
            "anon_hops": anon_hops,
            "safe_seeding": safe_seeding,
            "destination": destination,
            "selected_files": selected_files_list,
        }
        TriblerNetworkRequest(
            "downloads", callback if callback else self.on_download_added, method='PUT', data=post_data
        )

        self.update_recent_download_locations(destination)

        if add_to_channel:

            def on_add_button_pressed(channel_id):
                post_data = {}
                if uri.startswith("file:"):
                    with open(uri[5:], "rb") as torrent_file:
                        post_data['torrent'] = b64encode(torrent_file.read()).decode('utf8')
                elif uri.startswith("magnet:"):
                    post_data['uri'] = uri

                if post_data:
                    TriblerNetworkRequest(
                        f"channels/mychannel/{channel_id}/torrents",
                        lambda _: self.tray_show_message(tr("Channel update"), tr("Torrent(s) added to your channel")),
                        method='PUT',
                        data=post_data,
                    )

            self.window().add_to_channel_dialog.show_dialog(on_add_button_pressed, confirm_button_text="Add torrent")

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

        # To prevent multiple dialogs on top of each other,
        # close any existing dialog first.
        if self.new_version_dialog:
            self.new_version_dialog.close_dialog()
            self.new_version_dialog = None

        self.new_version_dialog = ConfirmationDialog(
            self,
            tr("New version available"),
            tr("Version %s of Tribler is available.Do you want to visit the " "website to download the newest version?")
            % version,
            [(tr("IGNORE"), BUTTON_TYPE_NORMAL), (tr("LATER"), BUTTON_TYPE_NORMAL), (tr("OK"), BUTTON_TYPE_NORMAL)],
        )
        connect(self.new_version_dialog.button_clicked, 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")
        if self.new_version_dialog:
            self.new_version_dialog.close_dialog()
            self.new_version_dialog = None

    def on_search_text_change(self, text):
        # We do not want to bother the database on petty 1-character queries
        if len(text) < 2:
            return
        TriblerNetworkRequest(
            "search/completions", self.on_received_search_completions, url_params={'q': sanitize_for_fts(text)}
        )

    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):
        TriblerNetworkRequest("settings", self.received_settings, capture_core_errors=False)

    def received_settings(self, settings):
        if not settings:
            return
        # 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))

        # If there is any pending dialog (likely download dialog or error dialog of setting not available),
        # close the dialog
        if self.dialog:
            self.dialog.close_dialog()
            self.dialog = None

        self.tribler_settings = settings['settings']

        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()
        connect(self.token_refresh_timer.timeout, 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()
        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

        if not query:
            return

        self.has_search_results = True
        self.search_results_page.initialize_root_model(
            SearchResultsModel(
                channel_info={"name": (tr("Search results for %s") % query) if len(query) < 50 else f"{query[:50]}..."},
                endpoint_url="search",
                hide_xxx=get_gui_setting(self.gui_settings, "family_filter", True, is_bool=True),
                text_filter=to_fts_query(query),
                type_filter=[REGULAR_TORRENT, CHANNEL_TORRENT, COLLECTION_NODE],
            )
        )

        self.clicked_search_bar()

        # Trigger remote search
        self.search_results_page.preview_clicked()

        self.last_search_query = 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()

    def on_token_balance_click(self, _):
        self.raise_window()
        self.deselect_all_menu_buttons()
        self.stackedWidget.setCurrentIndex(PAGE_TRUST)
        self.load_token_balance()
        self.trust_page.load_history()

    def load_token_balance(self):
        TriblerNetworkRequest("bandwidth/statistics", self.received_bandwidth_statistics, capture_core_errors=False)

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

        self.trust_page.received_bandwidth_statistics(statistics)

        statistics = statistics["statistics"]
        balance = statistics["total_given"] - statistics["total_taken"]
        self.set_token_balance(balance)

        # If trust page is currently visible, then load the graph as well
        if self.stackedWidget.currentIndex() == PAGE_TRUST:
            self.trust_page.load_history()

    def set_token_balance(self, balance):
        if abs(balance) > 1024 ** 4:  # Balance is over a TB
            balance /= 1024.0 ** 4
            self.token_balance_label.setText(f"{balance:.1f} TB")
        elif abs(balance) > 1024 ** 3:  # Balance is over a GB
            balance /= 1024.0 ** 3
            self.token_balance_label.setText(f"{balance:.1f} GB")
        else:
            balance /= 1024.0 ** 2
            self.token_balance_label.setText("%d MB" % balance)

    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(tr("Import torrent from file"), self)
        browse_directory_action = QAction(tr("Import torrent(s) from directory"), self)
        add_url_action = QAction(tr("Import torrent from magnet/URL"), self)
        create_torrent_action = QAction(tr("Create torrent from file(s)"), self)

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

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

        return menu

    def on_create_torrent(self, checked):
        if self.create_dialog:
            self.create_dialog.close_dialog()

        self.create_dialog = CreateTorrentDialog(self)
        connect(self.create_dialog.create_torrent_notification, self.on_create_torrent_updates)
        self.create_dialog.show()

    def on_create_torrent_updates(self, update_dict):
        self.tray_show_message(tr("Torrent updates"), update_dict['msg'])

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

    def start_download_from_uri(self, uri):
        uri = uri.decode('utf-8') if isinstance(uri, bytes) else uri
        self.download_uri = uri

        if get_gui_setting(self.gui_settings, "ask_download_settings", True, is_bool=True):
            # FIXME: instead of using this workaround, make sure the settings are _available_ by this moment
            # If tribler settings is not available, fetch the settings and inform the user to try again.
            if not self.tribler_settings:
                self.fetch_settings()
                self.dialog = ConfirmationDialog.show_error(
                    self,
                    tr("Download Error"),
                    tr("Tribler settings is not available yet. Fetching it now. Please try again later."),
                )
                # By re-adding the download uri to the pending list, the request is re-processed
                # when the settings is received
                self.pending_uri_requests.append(uri)
                return
            # Clear any previous dialog if exists
            if self.dialog:
                self.dialog.close_dialog()
                self.dialog = None

            self.dialog = StartDownloadDialog(self, self.download_uri)
            connect(self.dialog.button_clicked, self.on_start_download_action)
            self.dialog.show()
            self.start_download_dialog_active = True
        else:
            # FIXME: instead of using this workaround, make sure the settings are _available_ by this moment
            # 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'],
                [],
            )
            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(),
                    add_to_channel=self.dialog.dialog_widget.add_to_channel_checkbox.isChecked(),
                )
            else:
                ConfirmationDialog.show_error(
                    self, tr("Tribler UI Error"), tr("Something went wrong. Please try again.")
                )
                logging.exception("Error while trying to download. Either dialog or dialog.dialog_widget is None")

        if self.dialog:
            self.dialog.close_dialog()
            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, checked):
        chosen_dir = QFileDialog.getExistingDirectory(
            self,
            tr("Please select the directory containing the .torrent files"),
            QDir.homePath(),
            QFileDialog.ShowDirsOnly,
        )
        self.chosen_dir = chosen_dir
        if len(chosen_dir) != 0:
            self.selected_torrent_files = [torrent_file for torrent_file in glob.glob(chosen_dir + "/*.torrent")]
            self.dialog = ConfirmationDialog(
                self,
                tr("Add torrents from directory"),
                tr("Add %s torrent files from the following directory to your Tribler channel: \n\n%s")
                % (len(self.selected_torrent_files), chosen_dir),
                [(tr("ADD"), BUTTON_TYPE_NORMAL), (tr("CANCEL"), BUTTON_TYPE_CONFIRM)],
                checkbox_text=tr("Add torrents to My Channel"),
            )
            connect(self.dialog.button_clicked, self.on_confirm_add_directory_dialog)
            self.dialog.show()

    def on_confirm_add_directory_dialog(self, action):
        if action == 0:
            if self.dialog.checkbox.isChecked():
                # TODO: add recursive directory scanning
                def on_add_button_pressed(channel_id):
                    TriblerNetworkRequest(
                        f"collections/mychannel/{channel_id}/torrents",
                        lambda _: self.tray_show_message(
                            tr("Channels update"), tr("%s added to your channel") % self.chosen_dir
                        ),
                        method='PUT',
                        data={"torrents_dir": self.chosen_dir},
                    )

                self.window().add_to_channel_dialog.show_dialog(
                    on_add_button_pressed, confirm_button_text=tr("Add torrent(s)")
                )

            for torrent_file in self.selected_torrent_files:
                self.perform_start_download_request(
                    f"file:{torrent_file}",
                    self.window().tribler_settings['download_defaults']['anonymity_enabled'],
                    self.window().tribler_settings['download_defaults']['safeseeding_enabled'],
                    self.tribler_settings['download_defaults']['saveas'],
                    [],
                )

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

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

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

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

            # If the URI is a 40-bytes hex-encoded infohash, convert it to a valid magnet link
            if len(uri) == 40:
                valid_ih_hex = True
                try:
                    int(uri, 16)
                except ValueError:
                    valid_ih_hex = False

                if valid_ih_hex:
                    uri = "magnet:?xt=urn:btih:" + uri

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

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

    def on_download_added(self, result):
        if not result:
            return
        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)
            button.setChecked(False)

    def clicked_search_bar(self, checked=False):
        query = self.top_search_bar.text()
        if query and self.has_search_results:
            self.deselect_all_menu_buttons()
            if self.stackedWidget.currentIndex() == PAGE_SEARCH_RESULTS:
                self.search_results_page.go_back_to_level(0)
            self.stackedWidget.setCurrentIndex(PAGE_SEARCH_RESULTS)

    def clicked_menu_button_discovered(self):
        self.deselect_all_menu_buttons()
        self.left_menu_button_discovered.setChecked(True)
        if self.stackedWidget.currentIndex() == PAGE_DISCOVERED:
            self.discovered_page.go_back_to_level(0)
            self.discovered_page.reset_view()
        self.stackedWidget.setCurrentIndex(PAGE_DISCOVERED)
        self.discovered_page.content_table.setFocus()

    def clicked_menu_button_popular(self):
        self.deselect_all_menu_buttons()
        self.left_menu_button_popular.setChecked(True)
        # We want to reset the view every time to show updates
        self.popular_page.go_back_to_level(0)
        self.popular_page.reset_view()
        self.stackedWidget.setCurrentIndex(PAGE_POPULAR)
        self.popular_page.content_table.setFocus()

    def clicked_menu_button_trust_graph(self):
        self.deselect_all_menu_buttons(self.left_menu_button_trust_graph)
        self.stackedWidget.setCurrentIndex(PAGE_TRUST_GRAPH_PAGE)

    def clicked_menu_button_downloads(self, checked):
        self.deselect_all_menu_buttons(self.left_menu_button_downloads)
        self.raise_window()
        self.left_menu_button_downloads.setChecked(True)
        self.stackedWidget.setCurrentIndex(PAGE_DOWNLOADS)

    def clicked_menu_button_debug(self, index=False):
        if not self.debug_window:
            self.debug_window = DebugWindow(self.tribler_settings, self.gui_settings, self.tribler_version)
        self.debug_window.show()

    def resizeEvent(self, _):
        # This thing here is necessary to send the resize event to dialogs, etc.
        self.resize_event.emit()

    def close_tribler(self, checked=False):
        if self.core_manager.shutting_down:
            return

        def show_force_shutdown():
            self.window().force_shutdown_btn.show()

        self.delete_tray_icon()
        self.show_loading_screen()
        self.hide_status_bar()
        self.loading_text_label.setText(tr("Shutting down..."))
        if self.debug_window:
            self.debug_window.setHidden(True)

        self.shutdown_timer = QTimer()
        connect(self.shutdown_timer.timeout, 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_manager.clear()

        # Stop the token balance timer
        if self.token_refresh_timer:
            self.token_refresh_timer.stop()

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

    def dragEnterEvent(self, e):
        file_urls = [_qurl_to_path(url) for url in e.mimeData().urls()] if e.mimeData().hasUrls() else []

        if any(os.path.isfile(filename) for filename in file_urls):
            e.accept()
        else:
            e.ignore()

    def dropEvent(self, e):
        file_urls = (
            [(_qurl_to_path(url), url.toString()) for url in e.mimeData().urls()] if e.mimeData().hasUrls() else []
        )

        for filename, fileurl in file_urls:
            if os.path.isfile(filename):
                self.start_download_from_uri(fileurl)

        e.accept()

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

    def clicked_skip_conversion(self):
        self.dialog = ConfirmationDialog(
            self,
            tr("Abort the conversion of Channels database"),
            tr(
                "The upgrade procedure is now <b>converting your personal channel</b> and channels "
                "collected by the previous installation of Tribler.<br>"
                "Are you sure you want to abort the conversion process?<br><br>"
                "<p style='color:red'><b> !!! WARNING !!! <br>"
                "You will lose your personal channel and subscribed channels if you ABORT now! </b> </p> <br>"
            ),
            [(tr("ABORT"), BUTTON_TYPE_CONFIRM), (tr("CONTINUE"), BUTTON_TYPE_NORMAL)],
        )
        connect(self.dialog.button_clicked, self.on_skip_conversion_dialog)
        self.dialog.show()

    def on_channel_subscribe(self, channel_info):
        patch_data = [{"public_key": channel_info['public_key'], "id": channel_info['id'], "subscribed": True}]
        TriblerNetworkRequest(
            "metadata",
            lambda data: self.core_manager.events_manager.node_info_updated.emit(data[0]),
            raw_data=json.dumps(patch_data),
            method='PATCH',
        )

    def on_channel_unsubscribe(self, channel_info):
        def _on_unsubscribe_action(action):
            if action == 0:
                patch_data = [{"public_key": channel_info['public_key'], "id": channel_info['id'], "subscribed": False}]
                TriblerNetworkRequest(
                    "metadata",
                    lambda data: self.core_manager.events_manager.node_info_updated.emit(data[0]),
                    raw_data=json.dumps(patch_data),
                    method='PATCH',
                )
            if self.dialog:
                self.dialog.close_dialog()
                self.dialog = None

        self.dialog = ConfirmationDialog(
            self,
            tr("Unsubscribe from channel"),
            tr("Are you sure you want to <b>unsubscribe</b> from channel<br/>")
            + '\"'
            + f"<b>{channel_info['name']}</b>"
            + '\"'
            + tr("<br/>and remove its contents?"),
            [(tr("UNSUBSCRIBE"), BUTTON_TYPE_NORMAL), (tr("CANCEL"), BUTTON_TYPE_CONFIRM)],
        )
        connect(self.dialog.button_clicked, _on_unsubscribe_action)
        self.dialog.show()

    def on_channel_delete(self, channel_info):
        def _on_delete_action(action):
            if action == 0:
                delete_data = [{"public_key": channel_info['public_key'], "id": channel_info['id']}]
                TriblerNetworkRequest(
                    "metadata",
                    lambda data: self.core_manager.events_manager.node_info_updated.emit(data[0]),
                    raw_data=json.dumps(delete_data),
                    method='DELETE',
                )
            if self.dialog:
                self.dialog.close_dialog()
                self.dialog = None

        self.dialog = ConfirmationDialog(
            self,
            tr("Delete channel"),
            tr("Are you sure you want to <b>delete</b> your personal channel<br/>")
            + '\"'
            + f"<b>{channel_info['name']}</b>"
            + '\"'
            + tr("<br/>and all its contents?"),
            [(tr("DELETE"), BUTTON_TYPE_NORMAL), (tr("CANCEL"), BUTTON_TYPE_CONFIRM)],
        )
        connect(self.dialog.button_clicked, _on_delete_action)
        self.dialog.show()

    def on_skip_conversion_dialog(self, action):
        if action == 0:
            TriblerNetworkRequest("upgrader", lambda _: None, data={"skip_db_upgrade": True}, method='POST')

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

    def on_tribler_shutdown_state_update(self, state):
        self.loading_text_label.setText(state)

    def on_config_error_signal(self, stacktrace):
        self._logger.error(f"Config error: {stacktrace}")
        user_message = tr(
            "Tribler recovered from a corrupted config. Please check your settings and update if necessary."
        )
        ConfirmationDialog.show_error(self, tr("Tribler config error"), user_message)
Beispiel #57
0
class SystemTrayIcon(QMainWindow):
    def __init__(self, parent=None):
        super(SystemTrayIcon, self).__init__(parent)
        self.setAttribute(Qt.WA_DeleteOnClose)
        self.settings = QSettings()
        self.language = self.settings.value('Language') or ''
        self.temp_decimal_bool = self.settings.value('Decimal') or False
        # initialize the tray icon type in case of first run: issue#42
        self.tray_type = self.settings.value('TrayType') or 'icon&temp'
        cond = conditions.WeatherConditions()
        self.temporary_city_status = False
        self.conditions = cond.trans
        self.clouds = cond.clouds
        self.wind = cond.wind
        self.wind_dir = cond.wind_direction
        self.wind_codes = cond.wind_codes
        self.inerror = False
        self.tentatives = 0
        self.baseurl = 'http://api.openweathermap.org/data/2.5/weather?id='
        self.accurate_url = 'http://api.openweathermap.org/data/2.5/find?q='
        self.forecast_url = ('http://api.openweathermap.org/data/2.5/forecast/'
                             'daily?id=')
        self.day_forecast_url = ('http://api.openweathermap.org/data/2.5/'
                                 'forecast?id=')
        self.wIconUrl = 'http://openweathermap.org/img/w/'
        apikey = self.settings.value('APPID') or ''
        self.appid = '&APPID=' + apikey
        self.forecast_icon_url = self.wIconUrl
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.refresh)
        self.menu = QMenu()
        self.citiesMenu = QMenu(self.tr('Cities'))
        self.tempCityAction = QAction(self.tr('&Temporary city'), self)
        self.refreshAction = QAction(self.tr('&Update'), self)
        self.settingsAction = QAction(self.tr('&Settings'), self)
        self.aboutAction = QAction(self.tr('&About'), self)
        self.exitAction = QAction(self.tr('Exit'), self)
        self.exitAction.setIcon(QIcon(':/exit'))
        self.aboutAction.setIcon(QIcon(':/info'))
        self.refreshAction.setIcon(QIcon(':/refresh'))
        self.settingsAction.setIcon(QIcon(':/configure'))
        self.tempCityAction.setIcon(QIcon(':/tempcity'))
        self.citiesMenu.setIcon(QIcon(':/bookmarks'))
        self.menu.addAction(self.settingsAction)
        self.menu.addAction(self.refreshAction)
        self.menu.addMenu(self.citiesMenu)
        self.menu.addAction(self.tempCityAction)
        self.menu.addAction(self.aboutAction)
        self.menu.addAction(self.exitAction)
        self.settingsAction.triggered.connect(self.config)
        self.exitAction.triggered.connect(qApp.quit)
        self.refreshAction.triggered.connect(self.manual_refresh)
        self.aboutAction.triggered.connect(self.about)
        self.tempCityAction.triggered.connect(self.tempcity)
        self.systray = QSystemTrayIcon()
        self.systray.setContextMenu(self.menu)
        self.systray.activated.connect(self.activate)
        self.systray.setIcon(QIcon(':/noicon'))
        self.systray.setToolTip(self.tr('Searching weather data...'))
        self.notification = ''
        self.notification_temp = 0
        self.notifications_id = ''
        self.systray.show()
        # The dictionnary has to be intialized here. If there is an error
        # the program couldn't become functionnal if the dictionnary is
        # reinitialized in the weatherdata method
        self.weatherDataDico = {}
        # The traycolor has to be initialized here for the case when we cannot
        # reach the tray method (case: set the color at first time usage)
        self.traycolor = ''
        self.refresh()

    def icon_loading(self):
        self.gif_loading = QMovie(":/loading")
        self.gif_loading.frameChanged.connect(self.update_gif)
        self.gif_loading.start()

    def update_gif(self):
        gif_frame = self.gif_loading.currentPixmap()
        self.systray.setIcon(QIcon(gif_frame))

    def manual_refresh(self):
        self.tentatives = 0
        self.refresh()

    def cities_menu(self):
        # Don't add the temporary city in the list
        if self.temporary_city_status:
            return
        self.citiesMenu.clear()
        cities = self.settings.value('CityList') or []
        if type(cities) is str:
            cities = eval(cities)
        try:
            current_city = (self.settings.value('City') + '_' +
                        self.settings.value('Country') + '_' +
                        self.settings.value('ID'))
        except:
            # firsttime run,if clic cancel in setings without any city configured
            pass
        # Prevent duplicate entries
        try:
            city_toadd = cities.pop(cities.index(current_city))
        except:
            city_toadd = current_city
        finally:
            cities.insert(0, city_toadd)
        # If we delete all cities it results to a '__'
        if (cities is not None and cities != '' and cities != '[]' and
                cities != ['__']):
            if type(cities) is not list:
                # FIXME sometimes the list of cities is read as a string (?)
                # eval to a list
                cities = eval(cities)
            # Create the cities list menu
            for city in cities:
                action = QAction(city, self)
                action.triggered.connect(partial(self.changecity, city))
                self.citiesMenu.addAction(action)
        else:
            self.empty_cities_list()

    @pyqtSlot(str)
    def changecity(self, city):
        cities_list = self.settings.value('CityList')
        logging.debug('Cities' + str(cities_list))
        if cities_list is None:
            self.empty_cities_list()
        if type(cities_list) is not list:
            # FIXME some times is read as string (?)
            cities_list = eval(cities_list)
        prev_city = (self.settings.value('City') + '_' +
                     self.settings.value('Country') + '_' +
                     self.settings.value('ID'))
        citytoset = ''
        # Set the chosen city as the default
        for town in cities_list:
            if town == city:
                ind = cities_list.index(town)
                citytoset = cities_list[ind]
                citytosetlist = citytoset.split('_')
                self.settings.setValue('City', citytosetlist[0])
                self.settings.setValue('Country', citytosetlist[1])
                self.settings.setValue('ID', citytosetlist[2])
                if prev_city not in cities_list:
                    cities_list.append(prev_city)
                self.settings.setValue('CityList', cities_list)
                logging.debug(cities_list)
        self.refresh()

    def empty_cities_list(self):
        self.citiesMenu.addAction(self.tr('Empty list'))

    def refresh(self):
        self.inerror = False
        self.window_visible = False
        self.systray.setIcon(QIcon(':/noicon'))
        if hasattr(self, 'overviewcity'):
            # if visible, it has to ...remain visible
            # (try reason) Prevent C++ wrapper error
            try:
                if not self.overviewcity.isVisible():
                    # kills the reference to overviewcity
                    # in order to be refreshed
                    self.overviewcity.close()
                    del self.overviewcity
                else:
                    self.overviewcity.close()
                    self.window_visible = True
            except:
                pass
        self.systray.setToolTip(self.tr('Fetching weather data ...'))
        self.city = self.settings.value('City') or ''
        self.id_ = self.settings.value('ID') or None
        if self.id_ is None:
            # Clear the menu, no cities configured
            self.citiesMenu.clear()
            self.empty_cities_list()
            # Sometimes self.overviewcity is in namespace but deleted
            try:
                self.overviewcity.close()
            except:
                e = sys.exc_info()[0]
                logging.error('Error closing overviewcity: ' + str(e))
                pass
            self.timer.singleShot(2000, self.firsttime)
            self.id_ = ''
            self.systray.setToolTip(self.tr('No city configured'))
            return
        # A city has been found, create the cities menu now
        self.cities_menu()
        self.country = self.settings.value('Country') or ''
        self.unit = self.settings.value('Unit') or 'metric'
        self.suffix = ('&mode=xml&units=' + self.unit + self.appid)
        self.interval = int(self.settings.value('Interval') or 30)*60*1000
        self.timer.start(self.interval)
        self.update()

    def firsttime(self):
        self.temp = ''
        self.wIcon = QPixmap(':/noicon')
        self.systray.showMessage(
            'meteo-qt:\n', self.tr('No city has been configured yet.') +
            '\n' + self.tr('Right click on the icon and click on Settings.'))

    def update(self):
        if hasattr(self, 'downloadThread'):
            if self.downloadThread.isRunning():
                logging.debug('remaining thread...')
                return
        logging.debug('Update...')
        self.icon_loading()
        self.wIcon = QPixmap(':/noicon')
        self.downloadThread = Download(
            self.wIconUrl, self.baseurl, self.forecast_url,
            self.day_forecast_url, self.id_, self.suffix)
        self.downloadThread.wimage['PyQt_PyObject'].connect(self.makeicon)
        self.downloadThread.finished.connect(self.tray)
        self.downloadThread.xmlpage['PyQt_PyObject'].connect(self.weatherdata)
        self.downloadThread.forecast_rawpage.connect(self.forecast)
        self.downloadThread.day_forecast_rawpage.connect(self.dayforecast)
        self.downloadThread.uv_signal.connect(self.uv)
        self.downloadThread.error.connect(self.error)
        self.downloadThread.done.connect(self.done, Qt.QueuedConnection)
        self.downloadThread.start()

    def uv(self, value):
        self.uv_coord = value

    def forecast(self, data):
        self.forecast_data = data

    def dayforecast(self, data):
        self.dayforecast_data = data

    def instance_overviewcity(self):
        try:
            self.inerror = False
            if hasattr(self, 'overviewcity'):
                logging.debug('Deleting overviewcity instance...')
                del self.overviewcity
            self.overviewcity = overview.OverviewCity(
                self.weatherDataDico, self.wIcon, self.forecast_data,
                self.dayforecast_data, self.unit, self.forecast_icon_url,
                self.uv_coord, self)
            self.overviewcity.closed_status_dialogue.connect(self.remove_object)
        except:
            self.inerror = True
            e = sys.exc_info()[0]
            logging.error('Error: ' + str(e))
            logging.debug('Try to create the city overview...\nAttempts: ' +
                          str(self.tentatives))
            return 'error'

    def remove_object(self):
        del self.overviewcity

    def done(self, done):
        if done == 0:
            self.inerror = False
        elif done == 1:
            self.inerror = True
            logging.debug('Trying to retrieve data ...')
            self.timer.singleShot(10000, self.try_again)
            return
        if hasattr(self, 'updateicon'):
            # Keep a reference of the image to update the icon in overview
            self.wIcon = self.updateicon
        if hasattr(self, 'forecast_data'):
            if hasattr(self, 'overviewcity'):
                # Update also the overview dialog if open
                if self.overviewcity.isVisible():
                    # delete dialog to prevent memory leak
                    self.overviewcity.close()
                    self.instance_overviewcity()
                    self.overview()
            elif self.window_visible is True:
                self.instance_overviewcity()
                self.overview()
            else:
                self.inerror = True
                self.try_create_overview()
        else:
            self.try_again()

    def try_create_overview(self):
        logging.debug('Tries to create overview :' + str(self.tentatives))
        instance = self.instance_overviewcity()
        if instance == 'error':
            self.inerror = True
            self.refresh()
        else:
            self.tentatives = 0
            self.inerror = False
            self.tooltip_weather()

    def try_again(self):
        self.nodata_message()
        logging.debug('Attempts: ' + str(self.tentatives))
        self.tentatives += 1
        self.timer.singleShot(5000, self.refresh)

    def nodata_message(self):
        nodata = QCoreApplication.translate(
            "Tray icon", "Searching for weather data...",
            "Tooltip (when mouse over the icon")
        self.systray.setToolTip(nodata)
        self.notification = nodata

    def error(self, error):
        logging.error('Error:\n' + str(error))
        self.nodata_message()
        self.timer.start(self.interval)
        self.inerror = True

    def makeicon(self, data):
        image = QImage()
        image.loadFromData(data)
        self.wIcon = QPixmap(image)
        # Keep a reference of the image to update the icon in overview
        self.updateicon = self.wIcon

    def weatherdata(self, tree):
        if self.inerror:
            return
        self.tempFloat = tree[1].get('value')
        self.temp = ' ' + str(round(float(self.tempFloat))) + '°'
        self.temp_decimal = '{0:.1f}'.format(float(self.tempFloat)) + '°'
        self.meteo = tree[8].get('value')
        meteo_condition = tree[8].get('number')
        try:
            self.meteo = self.conditions[meteo_condition]
        except:
            logging.debug('Cannot find localisation string for'
                          'meteo_condition:' + str(meteo_condition))
            pass
        clouds = tree[5].get('name')
        clouds_percent = tree[5].get('value') + '%'
        try:
            clouds = self.clouds[clouds]
            clouds = self.conditions[clouds]
        except:
            logging.debug('Cannot find localisation string for clouds:' +
                          str(clouds))
            pass
        wind = tree[4][0].get('name').lower()
        try:
            wind = self.wind[wind]
            wind = self.conditions[wind]
        except:
            logging.debug('Cannot find localisation string for wind:' +
                          str(wind))
            pass
        wind_codes = tree[4][2].get('code')
        try:
            wind_codes = self.wind_codes[wind_codes]
        except:
            logging.debug('Cannot find localisation string for wind_codes:' +
                          str(wind_codes))
            pass
        wind_dir = tree[4][2].get('name')
        try:
            wind_dir = self.wind_dir[tree[4][2].get('code')]
        except:
            logging.debug('Cannot find localisation string for wind_dir:' +
                          str(wind_dir))
            pass
        self.city_weather_info = (self.city + ' ' + self.country + ' ' +
                                  self.temp_decimal + ' ' + self.meteo)
        self.tooltip_weather()
        self.notification = self.city_weather_info
        self.weatherDataDico['City'] = self.city
        self.weatherDataDico['Country'] = self.country
        self.weatherDataDico['Temp'] = self.tempFloat + '°'
        self.weatherDataDico['Meteo'] = self.meteo
        self.weatherDataDico['Humidity'] = (tree[2].get('value'),
                                            tree[2].get('unit'))
        self.weatherDataDico['Wind'] = (
            tree[4][0].get('value'), wind, str(int(float(tree[4][2].get('value')))),
            wind_codes, wind_dir)
        self.weatherDataDico['Clouds'] = (clouds_percent + ' ' + clouds)
        self.weatherDataDico['Pressure'] = (tree[3].get('value'),
                                            tree[3].get('unit'))
        self.weatherDataDico['Humidity'] = (tree[2].get('value'),
                                            tree[2].get('unit'))
        self.weatherDataDico['Sunrise'] = tree[0][2].get('rise')
        self.weatherDataDico['Sunset'] = tree[0][2].get('set')
        rain_value = tree[7].get('value')
        if rain_value == None:
            rain_value = ''
        self.weatherDataDico['Precipitation'] = (tree[7].get('mode'), rain_value)

    def tooltip_weather(self):
        self.systray.setToolTip(self.city_weather_info)

    def tray(self):
        temp_decimal = eval(self.settings.value('Decimal') or 'False')
        try:
            if temp_decimal:
                temp_tray = self.temp_decimal
            else:
                temp_tray = self.temp
        except:
            # First time launch
            return
        if self.inerror or not hasattr(self, 'temp'):
            logging.critical('Cannot paint icon!')
            if hasattr(self, 'overviewcity'):
                try:
                    # delete dialog to prevent memory leak
                    self.overviewcity.close()
                except:
                    pass
            return
        try:
            self.gif_loading.stop()
        except:
            # In first time run the gif is not animated
            pass
        logging.debug('Paint tray icon...')
        # Place empty.png here to initialize the icon
        # don't paint the T° over the old value
        icon = QPixmap(':/empty')
        self.traycolor = self.settings.value('TrayColor') or ''
        self.fontsize = self.settings.value('FontSize') or '18'
        self.tray_type = self.settings.value('TrayType') or 'icon&temp'
        pt = QPainter()
        pt.begin(icon)
        if self.tray_type != 'temp':
            pt.drawPixmap(0, -12, 64, 64, self.wIcon)
        pt.setFont(QFont('sans-sertif', int(self.fontsize)))
        pt.setPen(QColor(self.traycolor))
        if self.tray_type == 'icon&temp':
            pt.drawText(icon.rect(), Qt.AlignBottom | Qt.AlignCenter,
                        str(temp_tray))
        if self.tray_type == 'temp':
            pt.drawText(icon.rect(), Qt.AlignCenter, str(temp_tray))
        pt.end()
        if self.tray_type == 'icon':
            self.systray.setIcon(QIcon(self.wIcon))
        else:
            self.systray.setIcon(QIcon(icon))
        try:
            if not self.overviewcity.isVisible():
                notifier = self.settings.value('Notifications') or 'True'
                notifier = eval(notifier)
                if notifier:
                    temp = int(re.search('\d+', self.temp_decimal).group())
                    if temp != self.notification_temp or self.id_ != self.notifications_id:
                        self.notifications_id = self.id_
                        self.notification_temp = temp
                        self.systray.showMessage('meteo-qt', self.notification)
        except:
            logging.debug('OverviewCity has been deleted' +
                          'Download weather information again...')
            self.try_again()
            return
        self.restore_city()
        self.tentatives = 0
        self.tooltip_weather()
        logging.info('Actual weather status for: ' + self.notification)

    def restore_city(self):
        if self.temporary_city_status:
            logging.debug('Restore the default settings (city)' +
                          'Forget the temporary city...')
            for e in ('ID', self.id_2), ('City', self.city2), ('Country', self.country2):
                self.citydata(e)
            self.temporary_city_status = False

    def activate(self, reason):
        if reason == 3:
            if self.inerror or self.id_ is None or self.id_ == '':
                return
            try:
                if hasattr(self, 'overviewcity') and self.overviewcity.isVisible():
                    self.overviewcity.hide()
                else:
                    self.overviewcity.hide()
                    # If dialog closed by the "X"
                    self.done(0)
                    self.overview()
            except:
                self.done(0)
                self.overview()
        elif reason == 1:
            self.menu.popup(QCursor.pos())

    def overview(self):
        if self.inerror or len(self.weatherDataDico) == 0:
            return
        self.overviewcity.show()

    def config_save(self):
        logging.debug('Config saving...')
        city = self.settings.value('City'),
        id_ = self.settings.value('ID')
        country = self.settings.value('Country')
        unit = self.settings.value('Unit')
        traycolor = self.settings.value('TrayColor')
        tray_type = self.settings.value('TrayType')
        fontsize = self.settings.value('FontSize')
        language = self.settings.value('Language')
        decimal = self.settings.value('Decimal')
        self.appid = '&APPID=' + self.settings.value('APPID') or ''
        if language != self.language and language is not None:
            self.systray.showMessage('meteo-qt:',QCoreApplication.translate(
                    "System tray notification",
                    "The application has to be restarted to apply the language setting", ''))
            self.language = language
        # Check if update is needed
        if traycolor is None:
            traycolor = ''
        if (self.traycolor != traycolor or self.tray_type != tray_type or
                self.fontsize != fontsize or decimal != self.temp_decimal):
            self.tray()
        if (city[0] == self.city and
           id_ == self.id_ and
           country == self.country and
           unit == self.unit):
            return
        else:
            logging.debug('Apply changes from settings...')
            self.refresh()

    def config(self):
        dialog = settings.MeteoSettings(self.accurate_url, self.appid, self)
        dialog.applied_signal.connect(self.config_save)
        if dialog.exec_() == 1:
            self.config_save()
            logging.debug('Update Cities menu...')
            self.cities_menu()

    def tempcity(self):
        # Prevent to register a temporary city
        # This happen when a temporary city is still loading
        self.restore_city()
        dialog = searchcity.SearchCity(self.accurate_url, self.appid, self)
        self.id_2, self.city2, self.country2 = (self.settings.value('ID'),
                                                self.settings.value('City'),
                                                self.settings.value('Country'))
        dialog.id_signal[tuple].connect(self.citydata)
        dialog.city_signal[tuple].connect(self.citydata)
        dialog.country_signal[tuple].connect(self.citydata)
        if dialog.exec_():
            self.temporary_city_status = True
            self.systray.setToolTip(self.tr('Fetching weather data...'))
            self.refresh()

    def citydata(self, what):
        self.settings.setValue(what[0], what[1])
        logging.debug('write ' + str(what[0]) + ' ' + str(what[1]))

    def about(self):
        title = self.tr("""<b>meteo-qt</b> v{0}
            <br/>License: GPLv3
            <br/>Python {1} - Qt {2} - PyQt {3} on {4}""").format(
                __version__, platform.python_version(),
                QT_VERSION_STR, PYQT_VERSION_STR, platform.system())
        image = ':/logo'
        text = self.tr("""<p>Author: Dimitrios Glentadakis <a href="mailto:[email protected]">[email protected]</a>
                        <p>A simple application showing the weather status
                        information on the system tray.
                        <p>Website: <a href="https://github.com/dglent/meteo-qt">
                        https://github.com/dglent/meteo-qt</a>
                        <br/>Data source: <a href="http://openweathermap.org/">
                        OpenWeatherMap</a>.
                        <br/>This software uses icons from the
                        <a href="http://www.kde.org/">Oxygen Project</a>.
                        <p>To translate meteo-qt in your language or contribute to
                        current translations, you can use the
                        <a href="https://www.transifex.com/projects/p/meteo-qt/">
                        Transifex</a> platform.
                        <p>If you want to report a dysfunction or a suggestion,
                        feel free to open an issue in <a href="https://github.com/dglent/meteo-qt/issues">
                        github</a>.""")

        contributors = QCoreApplication.translate("About dialog", """
            Pavel Fric<br/>
            [cs] Czech translation
            <p>Jürgen <a href="mailto:[email protected]">[email protected]</a><br/>
            [de] German translation
            <p>Peter Mattern <a href="mailto:[email protected]">[email protected]</a><br/>
            [de] German translation, Project
            <p>Dimitrios Glentadakis <a href="mailto:[email protected]">[email protected]</a><br/>
            [el] Greek translation
            <p>Ozkar L. Garcell <a href="mailto:[email protected]">[email protected]</a><br/>
            [es] Spanish translation
            <p>Laurene Albrand <a href="mailto:[email protected]">[email protected]</a><br/>
            [fr] French translation
            <p>Rémi Verschelde <a href="mailto:[email protected]">[email protected]</a><br/>
            [fr] French translation, Project
            <p>Daniel Napora <a href="mailto:[email protected]">[email protected]</a><br/>
            Tomasz Przybył <a href="mailto:[email protected]">[email protected]</a><br/>
            [pl] Polish translation
            <p>Artem Vorotnikov <a href="mailto:[email protected]">[email protected]</a><br/>
            [ru] Russian translation
            <p>Atilla Öntaş <a href="mailto:[email protected]">[email protected]</a><br/>
            [tr] Turkish translation
            <p>Yuri Chornoivan <a href="mailto:[email protected]">[email protected]</a><br/>
            [uk] Ukrainian translation
            <p>You-Cheng Hsieh <a href="mailto:[email protected]">[email protected]</a><br/>
            [zh_TW] Chinese (Taiwan) translation
            <p>pmav99<br/>
            Project""", "List of contributors")

        dialog = about_dlg.AboutDialog(title, text, image, contributors, self)
        dialog.exec_()
Beispiel #58
0
class TriblerWindow(QMainWindow):

    resize_event = pyqtSignal()
    escape_pressed = pyqtSignal()
    received_search_completions = pyqtSignal(object)

    def on_exception(self, *exc_info):
        # 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)

        if not self.feedback_dialog_is_open:
            dialog = FeedbackDialog(
                self, exception_text,
                self.core_manager.events_manager.tribler_version)
            self.feedback_dialog_is_open = True
            _ = dialog.exec_()

    def __init__(self):
        QMainWindow.__init__(self)

        self.navigation_stack = []
        self.feedback_dialog_is_open = False
        self.tribler_started = False
        self.tribler_settings = None
        self.debug_window = None
        self.core_manager = CoreManager()
        self.pending_requests = {}
        self.pending_uri_requests = []
        self.download_uri = None
        self.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

        sys.excepthook = self.on_exception

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

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

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

        self.read_settings()

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

        self.hide_left_menu_playlist()
        self.left_menu_button_debug.setHidden(True)
        self.top_menu_button.setHidden(True)
        self.left_menu.setHidden(True)
        self.trust_button.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))

        self.core_manager.start()

        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)

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

        self.show()

    def update_tray_icon(self, use_monochrome_icon):
        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_torrent_finished(self, torrent_info):
        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.trust_button.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.trust_button.setHidden(False)
        self.settings_button.setHidden(False)
        self.add_torrent_button.setHidden(False)
        self.top_search_bar.setHidden(False)

        # fetch the variables, needed for the video player port
        self.request_mgr = TriblerRequestManager()
        self.request_mgr.perform_request("variables", self.received_variables)

        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 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):
        selected_files_uri = ""
        if len(selected_files) != total_files:  # Not all files included
            selected_files_uri = u'&' + u''.join(
                u"selected_files[]=%s&" % file for file in selected_files)[:-1]

        anon_hops = int(self.tribler_settings['Tribler']
                        ['default_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" % (
            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()
        self.pending_requests[request_mgr.request_id] = request_mgr
        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 []
        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.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.dialog.button_clicked.connect(
            lambda action: self.on_new_version_dialog_done(version, action))
        self.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.dialog.setParent(None)
        self.dialog = None

    def read_settings(self):
        self.gui_settings = QSettings()
        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)

    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):
        self.received_search_completions.emit(completions)
        self.search_completion_model.setStringList(completions["completions"])

    def received_variables(self, variables):
        self.video_player_page.video_player_port = variables["variables"][
            "ports"]["video~port"]
        self.fetch_settings()

    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))
        else:
            self.tribler_settings = settings['settings']

        # 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']['enabled']:
            self.left_menu_button_video_player.setHidden(True)

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

    def on_top_search_button_click(self):
        self.left_menu_button_search.setChecked(True)
        self.has_search_results = True
        self.clicked_menu_button_search()
        self.search_results_page.perform_search(self.top_search_bar.text())
        self.search_request_mgr = TriblerRequestManager()
        self.search_request_mgr.perform_request(
            "search?q=%s" % self.top_search_bar.text(), None)

    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_trust_button_click(self):
        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 on_add_torrent_button_click(self, pos):
        menu = TriblerActionMenu(self)

        browse_files_action = QAction('Import torrent from file', self)
        browse_directory_action = QAction('Import torrents 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)

        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", "",
            "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):
            self.dialog = StartDownloadDialog(self.window().stackedWidget,
                                              self.download_uri)
            self.dialog.button_clicked.connect(self.on_start_download_action)
            self.dialog.show()
            self.start_download_dialog_active = True
        else:
            self.window().perform_start_download_request(
                self.download_uri,
                get_gui_setting(self.gui_settings,
                                "default_anonymity_enabled",
                                True,
                                is_bool=True),
                get_gui_setting(self.gui_settings,
                                "default_safeseeding_enabled",
                                True,
                                is_bool=True),
                self.tribler_settings['downloadconfig']['saveas'], [], 0)
            self.process_uri_request()

    def on_start_download_action(self, action):
        if action == 1:
            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())

        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",
            "", 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 = quote_plus(
                    (u"file:%s" % torrent_file).encode('utf-8'))
                self.perform_start_download_request(
                    escaped_uri,
                    get_gui_setting(self.gui_settings,
                                    "default_anonymity_enabled",
                                    True,
                                    is_bool=True),
                    get_gui_setting(self.gui_settings,
                                    "default_safeseeding_enabled",
                                    True,
                                    is_bool=True),
                    self.tribler_settings['downloadconfig']['saveas'], [], 0)

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

    def on_add_torrent_from_url(self):
        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.button_clicked.connect(
            self.on_torrent_from_url_dialog_done)
        self.dialog.show()

    def on_torrent_from_url_dialog_done(self, action):
        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.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):
        self.debug_window = DebugWindow(self.tribler_settings)
        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):
        prev_page = self.navigation_stack.pop()
        self.stackedWidget.setCurrentIndex(prev_page)

    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:
            self.show_loading_screen()

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

    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()
Beispiel #59
0
 def show(self):
     QSystemTrayIcon.show(self)
Beispiel #60
0
class SystemTray(QDialog):
    tray_alert_signal = pyqtSignal((str,))

    def __init__(self):
        super().__init__()
        self.create_actions()
        self.create_tray_icon()
        self.containing_folder = ""

    def create_actions(self):
        # menu items
        self.open_osf_folder_action = QAction("Open OSF Folder", self)
        self.launch_osf_action = QAction("Launch OSF", self)
        self.sync_now_action = QAction("Sync Now", self)
        self.currently_synching_action = QAction("Up to date", self)
        self.currently_synching_action.setDisabled(True)
        self.preferences_action = QAction("Settings", self)
        self.about_action = QAction("&About", self)
        self.quit_action = QAction("&Quit", self)

    def create_tray_icon(self):
        self.tray_icon_menu = QMenu(self)
        self.tray_icon_menu.addAction(self.open_osf_folder_action)
        self.tray_icon_menu.addAction(self.launch_osf_action)
        self.tray_icon_menu.addSeparator()
        self.tray_icon_menu.addAction(self.sync_now_action)
        self.tray_icon_menu.addAction(self.currently_synching_action)
        self.tray_icon_menu.addSeparator()
        self.tray_icon_menu.addAction(self.preferences_action)
        self.tray_icon_menu.addAction(self.about_action)
        self.tray_icon_menu.addSeparator()
        self.tray_icon_menu.addAction(self.quit_action)
        self.tray_icon = QSystemTrayIcon(self)
        self.tray_icon.setContextMenu(self.tray_icon_menu)

        # todo: do we have a better icon for use with desktop apps?
        icon = QIcon(":/cos_logo_backup.png")
        self.tray_icon.setIcon(icon)

    def start(self):
        self.tray_icon.show()

    def update_currently_synching(self, val):
        self.currently_synching_action.setText(str(val))
        self.tray_icon.show()

    def set_containing_folder(self, new_containing_folder):
        logging.debug("setting new containing folder is :{}".format(self.containing_folder))
        self.containing_folder = new_containing_folder

    def start_osf(self):
        url = "http://osf.io"
        webbrowser.open_new_tab(url)

    def open_osf_folder(self):
        # fixme: containing folder not being updated.
        import logging

        logging.debug("containing folder is :{}".format(self.containing_folder))
        if validate_containing_folder(self.containing_folder):
            if sys.platform == "win32":
                os.startfile(self.containing_folder)
            elif sys.platform == "darwin":
                subprocess.Popen(["open", self.containing_folder])
            else:
                try:
                    subprocess.Popen(["xdg-open", self.containing_folder])
                except OSError:
                    raise NotImplementedError
        else:
            AlertHandler.warn("osf folder is not set")