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()
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
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)
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))
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_()
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)
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()
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
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()
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()
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
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)
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_()
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)
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_()
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()
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')
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
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()))
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_()
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()
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()
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()
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)
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()
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()
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')
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)) #дозапись в файл
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)
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")
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)
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()
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)
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()
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()
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()
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()
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)
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()
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))
def showNotification(title, body): trayIcon = QSystemTrayIcon() trayIcon.setIcon(QIcon(getResource("speech.png"))) trayIcon.show() icon = QSystemTrayIcon.MessageIcon(QSystemTrayIcon.Information) trayIcon.showMessage(title, body, icon)
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()
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)
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()
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
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()
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
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()
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)
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_()
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()
def show(self): QSystemTrayIcon.show(self)
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")