def _initSystemTray(self, withMenu: bool = False): LOG.info("Initializing the system tray: available: %s, messages: %s", QSystemTrayIcon.isSystemTrayAvailable(), QSystemTrayIcon.supportsMessages()) if not QSystemTrayIcon.isSystemTrayAvailable(): return systemTrayIcon = QSystemTrayIcon(QIcon('assets/logo.png'), self._app) systemTrayIcon.setToolTip("The Deep Visualization Toolbox") systemTrayIcon.activated.connect(self.onSystemTrayActivated) if withMenu: contextMenu = QMenu() contextMenu.aboutToShow.connect(self.onSystemTrayMenuAboutToShow) actionTest = QAction('Test', contextMenu) actionTest.triggered.connect(self.onSystemTrayMenuActionTriggered) systemTrayIcon.setContextMenu(contextMenu) if QSystemTrayIcon.supportsMessages(): systemTrayIcon.\ messageClicked.connect(self.onSystemTrayMessageClicked) systemTrayIcon.show()
class TrayIcon(QThread): def __init__(self, parent=None): super().__init__(parent) self.main = parent self.trayIcon = QSystemTrayIcon(QIcon('image/icon.png')) self.trayIcon.setToolTip('Laptop Security Program v1.0 is Running') self.trayIcon.show() # noinspection PyUnresolvedReferences self.trayIcon.activated.connect(self.on_systray_activated) @staticmethod def on_systray_activated(): buttons = qApp.mouseButtons() if not buttons: msgBox = QMessageBox() msgBox.setWindowTitle('Exit') msgBox.setText('Are you sure to quit?') msgBox.setStandardButtons(QMessageBox.No | QMessageBox.Yes) msgBox.setIcon(QMessageBox.Information) msgBox.setDefaultButton(QMessageBox.No) msgBox.setWindowIcon(QIcon('image/icon.png')) msgBox.setWindowFlags(Qt.WindowStaysOnTopHint) msgBox.setWindowFlags(msgBox.windowFlags() & ~Qt.WindowCloseButtonHint & ~Qt.WindowMaximizeButtonHint & ~Qt.WindowMinimizeButtonHint) msgBox.activateWindow() msgBox.raise_() result = msgBox.exec_() if result == QMessageBox.Yes: WifiServer.EXIT = True quit(0) sys.exit(0)
class App: def __init__(self): # Create a Qt application self.app = QApplication(sys.argv) icon = QIcon("favicon.ico") menu = QMenu() settingAction = menu.addAction("setting") settingAction.triggered.connect(self.setting) exitAction = menu.addAction("exit") exitAction.triggered.connect(sys.exit) self.tray = QSystemTrayIcon() self.tray.setIcon(icon) self.tray.setContextMenu(menu) self.tray.show() self.tray.setToolTip("unko!") self.tray.showMessage("hoge", "moge") self.tray.showMessage("fuga", "moge") def run(self): # Enter Qt application main loop self.app.exec_() sys.exit() def setting(self): self.dialog = QDialog() self.dialog.setWindowTitle("Setting Dialog") self.dialog.show()
class AppUi(QtCore.QObject): reload_data = QtCore.pyqtSignal() def __init__(self, parent: QApplication, conf: Config, build_icons: BuildIcons): super(AppUi, self).__init__(parent) self.widget = QWidget() self.build_icons = build_icons self.tray = QSystemTrayIcon(self.build_icons.for_status(None), self.widget) self.tray.show() self.app_menu = AppMenu(self.widget, conf, self.build_icons) self.app_menu.reload_data.connect(self.reload_data) # type: ignore self.tray.setContextMenu(self.app_menu.menu) self.tray.activated.connect(self.show_menu) def show_menu(self, reason): if not sys.platform.startswith( 'darwin') and reason == QSystemTrayIcon.Trigger: self.app_menu.menu.popup(QCursor.pos()) def update_projects(self, integration_status: OverallIntegrationStatus): count = len(integration_status.get_failing_builds()) self.tray.setIcon( self.build_icons.for_aggregate_status( integration_status.get_build_status(), count)) self.app_menu.update(integration_status.get_projects()) self.tray.setToolTip("Last checked: " + strftime("%Y-%m-%d %H:%M:%S"))
class Ui_New(QWidget): def __init__(self): super().__init__() self.setupUi() def about(self): sys.exit() def see(self): path = os.path.abspath(filename) os.startfile(path) self.tp.setIcon(QIcon('./nothing_logo.jpg')) tpMenu = QMenu() a1 = QAction(QtGui.QIcon('exit.png'), u'退出', self) # 添加一级菜单动作选项(关于程序) a1.triggered.connect(self.about) tpMenu.addAction(a1) self.tp.setContextMenu(tpMenu) self.work() def work(self): global filemt while 1: time.sleep(10) if time.localtime(os.stat(filename).st_mtime) != filemt: filemt = time.localtime(os.stat(filename).st_mtime) print(filemt) self.tp.setIcon(QIcon('./Yes_logo.jpg')) tpMenu = QMenu() a1 = QAction(QtGui.QIcon('exit.png'), u'查看', self) # 添加一级菜单动作选项(关于程序) a1.triggered.connect(self.see) tpMenu.addAction(a1) self.tp.setContextMenu(tpMenu) break def setupUi(self): global filemt self.resize(250, 250) # move()方法移动了窗口到屏幕坐标x=300, y=300的位置. self.move(300, 300) # 在这里我们设置了窗口的标题.标题会被显示在标题栏上. self.setWindowTitle('Simple') # show()方法将窗口显示在屏幕上.一个窗口是先在内存中被创建,然后显示在屏幕上的. #self.show() print(1) self.tp = QSystemTrayIcon(self) self.tp.setIcon(QIcon('./nothing_logo.jpg')) self.tp.setToolTip(u'智乃酱真棒(*^▽^*)!') self.tp.showMessage('tp', 'tpContent') tpMenu = QMenu() a1 = QAction(QtGui.QIcon('exit.png'), u'退出', self) # 添加一级菜单动作选项(关于程序) a1.triggered.connect(self.about) tpMenu.addAction(a1) self.tp.setContextMenu(tpMenu) self.tp.show() filemt = time.localtime(os.stat(filename).st_mtime) print(filemt) #self.tp.messageClicked.connect(self.message) self.work()
class QTGui(QWidget): def __init__(self): super().__init__() self.showWindow() def changeEvent(self, QEvent): if QEvent.type() == QEvent.WindowStateChange: if self.isMinimized(): print("minimized") self.minimizetotray() super().changeEvent(QEvent) def showWindow(self): self.setGeometry(300, 300, 300, 63) self.setFixedSize(self.size()) self.setWindowIcon(QIcon("icon.png")) self.setWindowTitle("pyWall UI") global btn btn = QPushButton("Change", self) btn.resize(75, 23) btn.move(0, self.height() - btn.height()) btn.setToolTip("Change the wallpaper right now.") btn.clicked.connect(newWallpaperInNewThread) global txtinterval txtinterval = QTextEdit("100", self) txtinterval.setToolTip("Time interval in seconds between wallpaper changes.") txtinterval.resize(70, 23) txtinterval.move(0, btn.y() - txtinterval.height()) global chkbox chkbox = QCheckBox("Timer", self) chkbox.setToolTip("Use timer for auto wallpaper change.") chkbox.resize(49, 17) chkbox.move(0, txtinterval.y() - chkbox.height()) chkbox.stateChanged.connect(checkBoxStateChanged) global label label = QLabel("", self) label.setFont(QFont("Times", 8, QFont.Bold)) label.move(btn.width() + 5, 0) label.resize(self.width()-btn.width(),self.height()) label.setWordWrap(True) self.show() def minimizetotray(self): self.hide() self.tray = QSystemTrayIcon() self.tray.setIcon(QIcon("icon.png")) self.tray.setToolTip("pyWall Tray") self.tray.show() self.tray.showMessage("pyWall", "pyWall will run in background.", msecs=500) self.tray.activated.connect(self.trayiconactivated) def trayiconactivated(self, reason): if reason == QSystemTrayIcon.Trigger: self.tray.hide() self.show()
def tray_icon(app, win): tray = QSystemTrayIcon(QIcon("icon.png"), win) menu = QMenu() action = menu.addAction("Quit") action.triggered.connect(app.quit) menu.addAction(action) tray.setContextMenu(menu) tray.setVisible(True) tray.setToolTip("Cute Ninja") tray.show()
def setTrayIcon(self): tray_icon = QSystemTrayIcon(QIcon(real_path("img/logo.svg")), self) tray_icon.show() menu = QMenu() menu.addAction("Burst!").triggered.connect(self.clickBurst) menu.addAction("Sync").triggered.connect(self.clickSync) menu.addAction("Setting").triggered.connect(self.clickSetting) menu.addAction("Exit").triggered.connect(self.clickExit) tray_icon.setContextMenu(menu) tray_icon.activated.connect(self.clickTrayIcon) tray_icon.setToolTip("DWords")
class Tray: # create tray icon menu def __init__(self, ): # create systemtray UI self.icon = QIcon(ico) self.tray = QSystemTrayIcon() self.tray.setIcon(self.icon) self.tray.setToolTip("Now Playing ▶") self.tray.setVisible(True) self.menu = QMenu() # create systemtray options and actions self.actTitle = QAction("Now Playing v1.4") self.menu.addAction(self.actTitle) self.actTitle.setEnabled(False) self.actConfig = QAction("Settings") self.actConfig.triggered.connect(win.show) self.menu.addAction(self.actConfig) self.menu.addSeparator() self.actPause = QAction() self.actPause.triggered.connect(self.pause) self.menu.addAction(self.actPause) self.actPause.setEnabled(False) self.actExit = QAction("Exit") self.actExit.triggered.connect(self.cleanquit) self.menu.addAction(self.actExit) # add menu to the systemtray UI self.tray.setContextMenu(self.menu) def unpause(self): # unpause polling global paused paused = 0 self.actPause.setText('Pause') self.actPause.triggered.connect(self.pause) def pause(self): # pause polling global paused paused = 1 self.actPause.setText('Resume') self.actPause.triggered.connect(self.unpause) def cleanquit(self): # quit app and cleanup self.tray.setVisible(False) file = ConfigFile(config, config_file).file if file: writetrack(file) sys.exit()
class tray(): def __init__(self): super().__init__() self.initUI() def turn_off(self,device): is_list = isinstance(device,list) if is_list == False: return device.turn_off() else: return [i.turn_off() for i in device] def turn_on(self,device): is_list = isinstance(device,list) if is_list == False: return device.turn_on() else: return[ i.turn_on() for i in device] def initUI(self): self.app = QApplication(sys.argv) api.init('LOGIN','PASSWORD',"44","tuya") self.device_ids = api.get_all_devices() self.devices = dict((i.name(),i) for i in self.device_ids if i.obj_type == 'light' or i.obj_type == 'switch') self.lights = dict((i.name(),i) for i in self.device_ids if i.obj_type == 'light') self.devices['All Lights'] = list(self.lights.values()) self.tray_icon = QSystemTrayIcon(QIcon('lightbulb.png'),parent=self.app) self.tray_icon.setToolTip('Tuya - Lights') self.menu = QMenu() self.menus = dict() self.buttons = dict() for j in self.devices.keys(): self.menus[f"{j}_Action"] = self.menu.addMenu(j) self.menus[f"{j}_Action"].addAction('On') self.menus[f"{j}_Action"].addAction('Off') self.buttons = self.menus[f"{j}_Action"].actions() for i in self.buttons: if i.iconText() == 'On': i.triggered.connect(partial(self.turn_on,self.devices[j])) elif i.iconText() == 'Off': i.triggered.connect(partial(self.turn_off,self.devices[j])) self.exitaction = self.menu.addAction('Exit') self.exitaction.triggered.connect(self.app.quit) self.tray_icon.setContextMenu(self.menu) self.tray_icon.show() self.app.exec_() sys.exit(self.app.exec_())
class TrayWindow(QDialog): def __init__(self, main_window, *args, **kwargs): super(TrayWindow, self).__init__(*args, **kwargs) self.createActions() self.createTrayIcon() self.trayIcon.show() self.settingsWindow = main_window def createActions(self): self.settingsAction = QAction("Settings", self, triggered=self.showSettings) self.quitAction = QAction("Quit", self, triggered=QApplication.instance().quit) def createTrayIcon(self): self.trayIconMenu = QMenu(self) self.trayIconMenu.addAction(self.settingsAction) self.trayIconMenu.addSeparator() self.trayIconMenu.addAction(self.quitAction) self.trayIcon = QSystemTrayIcon(self) self.trayIcon.setContextMenu(self.trayIconMenu) self.trayIcon.activated.connect(self.iconActivated) icon = QIcon(getResource("speechOn.png")) self.trayIcon.setIcon(icon) self.setWindowIcon(icon) self.trayIcon.setToolTip("MicroCommander") def iconActivated(self, reason): self.showNotification("Micro commander", "Status: running") def showSettings(self): self.settingsWindow.show() @staticmethod def showNotification(title, body): trayIcon = QSystemTrayIcon() trayIcon.setIcon(QIcon(getResource("speech.png"))) trayIcon.show() icon = QSystemTrayIcon.MessageIcon(QSystemTrayIcon.Information) trayIcon.showMessage(title, body, icon) @staticmethod def show_message(tray, title, body): icon = QSystemTrayIcon.MessageIcon(QSystemTrayIcon.Information) tray.showMessage(title, body, icon)
class systemTray(QObject): cs = check_service def start_tray(self, ns, ip): # Initialize QApplication self.app = QApplication([]) self.app.setQuitOnLastWindowClosed(False) # Check service status ipc, state = self.cs(ns, ip) # Set icon icon = QIcon(srcdir + ipc) self.tray = QSystemTrayIcon() self.tray.setIcon(icon) self.tray.setVisible(True) self.tray.setToolTip(ns + " is " + state) # Set menu self.menu = QMenu() self.quit = QAction("Exit") self.quit.triggered.connect(self.app.quit) self.menu.addAction(self.quit) # Add menu to tray self.tray.setContextMenu(self.menu) # Create thread self.thread = QThread() # Create object which will be moved from main thread to worker thread self.worker = worker(ns, ip) # Move object to worker thread self.worker.moveToThread(self.thread) # Connect object to signal self.worker.newIcon.connect(self.updateIcon) self.worker.newToolTip.connect(self.updateToolTip) # Connect started signal to run method of object in worker thread self.thread.started.connect(self.worker.run) # Start thread self.thread.start() # Run tray self.app.exec_() def updateIcon(self, icon): self.tray.setIcon(icon) def updateToolTip(self, tooltip): self.tray.setToolTip(tooltip)
def set_systray(): systray = QSystemTrayIcon() systray.setIcon( QIcon( "E:\MyWorkPlace\pythonwork\devices\windows_system_tray\systray.png" )) # systray.setToolTip("Warframe") while True: systray.setToolTip(""" 本地时间:{} 地球平原时间:{},倒计时:{} 金星平原天气:{},倒计时:{} """.format(1, 2, 3, 4, 5)) systray.show() time.sleep(1)
class TrayIcon(QWidget): #child class name def __init__(self): #class constructor super().__init__(); #inheritance from parent attributes self.tray=QSystemTrayIcon(QtGui.QIcon('chidalu.jpg')); #create a systemtrayicon self.tray.setToolTip('Click Me'); #set tooltip of trayicon self.tray.show(); #show trayicon menu=QMenu(self); #create a menu object exitAction=QAction('Exit'); #create an exit action exitAction.triggered.connect(app.quit); #connect a signal to a slot menu.addAction(exitAction); #add action to menu self.tray.setContextMenu(menu); #set context menu of trayicon
def init_tray_icon(app_window): if not QSystemTrayIcon.isSystemTrayAvailable(): QMessageBox.critical( None, APP_NAME, 'System tray is not supported, app will exit' ) sys.exit(1) icon = QIcon(':/images/app.png') icon.setIsMask(True) tray_icon = QSystemTrayIcon(icon, app_window) tray_icon.activated.connect( lambda reason: tray_icon_activated(reason, app_window, tray_icon) ) tray_icon.setToolTip(APP_NAME) return tray_icon
def set_tray(self): tray_icon = QSystemTrayIcon(self) icon = QIcon("icon.png") tray_icon.setIcon(icon) 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(self.close) tray_menu = QMenu() tray_menu.addAction(show_action) tray_menu.addAction(hide_action) tray_menu.addAction(quit_action) tray_icon.setContextMenu(tray_menu) tray_icon.show() tray_icon.setToolTip('Solar combine')
def apiTray(): app = QtWidgets.QApplication(sys.argv) scriptDir = os.path.dirname(os.path.realpath(__file__)) trayIcon = QSystemTrayIcon(QtGui.QIcon(scriptDir + "/resources/logo.png")) trayIcon.setToolTip("AmazonWatch") trayIcon.show() menu = QMenu() openAction = menu.addAction("Open GUI") openAction.triggered.connect(startGuiProcess) menu.addSeparator() actionGithub = menu.addAction("Visit Github") actionGithub.triggered.connect(openBrowser) menu.addSeparator() exitAction = menu.addAction("Exit") exitAction.triggered.connect(app.quit) trayIcon.setContextMenu(menu) sys.exit(app.exec_())
def set_tray(self): tray_icon = QSystemTrayIcon(self) icon = QIcon("icon.png") tray_icon.setIcon(icon) 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(self.close) tray_menu = QMenu() tray_menu.addAction(show_action) tray_menu.addAction(hide_action) tray_menu.addAction(quit_action) tray_icon.setContextMenu(tray_menu) tray_icon.show() tray_icon.setToolTip('Address book')
def _create_systemtray(self): ''' Create & add systemtray icon with controls. ''' if self._systemtray: return systemtray = QSystemTrayIcon(self) systemtray.setToolTip(app_data.app_name()) systemtray.setIcon(QIcon(app_data.app_logo_path())) systemtray.setVisible(True) menu = QMenu(self) about_action = menu.addAction(QIcon(app_data.about_logo_path()), "About") about_action.triggered.connect(self._about_clicked) exclusions_action = menu.addAction(QIcon(app_data.exclusions_logo_path()), "Exclusions") exclusions_action.triggered.connect(self._exclusions_clicked) quit_action = menu.addAction(QIcon(app_data.quit_logo_path()), "Quit") quit_action.triggered.connect(self._quit_clicked) systemtray.setContextMenu(menu) self._systemtray = systemtray
class AppUi(QtCore.QObject): reload_data = QtCore.pyqtSignal() def __init__(self, parent, conf, build_icons): super(AppUi, self).__init__(parent) self.widget = QWidget() self.build_icons = build_icons self.tray = QSystemTrayIcon(self.build_icons.for_status(None), self.widget) self.tray.show() self.app_menu = AppMenu(self.widget, conf, self.build_icons) self.app_menu.reload_data.connect(self.reload_data) self.tray.setContextMenu(self.app_menu.menu) def update_projects(self, integration_status): count = len(integration_status.get_failing_builds()) self.tray.setIcon(self.build_icons.for_aggregate_status(integration_status.get_build_status(), count)) self.app_menu.update(integration_status.get_projects()) self.tray.setToolTip("Last checked: " + strftime("%Y-%m-%d %H:%M:%S"))
class QTrayIcon(object): __options = [] def __init__(self, title=None, icon=None, parent=None): self.__trayIcon = QSystemTrayIcon(parent=parent) self.__context_menu = QMenu() self.__trayIcon.setContextMenu(self.__context_menu) self.set_icon(icon) self.set_title(title) def __add_option(self, text, function): option = QAction(text) option.triggered.connect(function) self.__context_menu.addAction(option) self.__options.append(option) def add_options(self, options): for option, function in options.items(): self.__add_option(option, function) def disable_option(self, index): self.__options[index].setEnabled(False) def enable_option(self, index): self.__options[index].setEnabled(True) def set_icon(self, icon): self.__icon = QIcon(icon) self.__trayIcon.setIcon(self.__icon) self.__trayIcon.setVisible(True) def set_title(self, title): self.__trayIcon.setToolTip(title) def show_message(self, title, message, duration=5000, icon=None): icon = QIcon(icon) if icon else self.__icon self.__trayIcon.showMessage(title, message, self.__icon, duration)
def runcatCPU(): app = QApplication(sys.argv) # 最后一个可视的窗口退出时程序不退出 app.setQuitOnLastWindowClosed(False) icon = QSystemTrayIcon() icon.setIcon(QIcon('icons/0.png')) icon.setVisible(True) cpu_percent = psutil.cpu_percent(interval=1) / 100 cpu_percent_update_fps = 20 fps_count = 0 while True: fps_count += 1 if fps_count > cpu_percent_update_fps: cpu_percent = psutil.cpu_percent(interval=1) / 100 fps_count = 0 # 开口向上的抛物线, 左边递减 time_interval = (cpu_percent * cpu_percent - 2 * cpu_percent + 2) / 20 for i in range(5): icon.setIcon(QIcon('icons/%d.png' % i)) icon.setToolTip('cpu: %.2f' % cpu_percent) time.sleep(time_interval) app.exec_()
def tray_icon_handler(): app = QApplication(sys.argv) tray_icon = QSystemTrayIcon(QIcon('stocks.ico'), parent=app) tray_icon.setToolTip('Price Alert Running!') tray_icon.show() menu = QMenu() global last_update last_update = menu.addAction('Last update: - - - ') open_excel = menu.addAction('Open Price Alert Excel Sheet') open_excel.triggered.connect( lambda: os.startfile(os.getcwd() + '\\Price_Alert.xlsm')) exit_action = menu.addAction('Exit') exit_action.triggered.connect(lambda: os._exit(0)) tray_icon.setContextMenu(menu) sys.exit(app.exec_())
class EmojiBoard(QMainWindow): def __init__(self): super(EmojiBoard, self).__init__(None, Qt.WindowStaysOnTopHint) self.icon = QIcon(ICON_PLACE) self.setGeometry(X_POS, Y_POS, WIDTH, HEIGHT) self.setWindowTitle(NAME) self.setWindowIcon(self.icon) self.setWindowFlag(Qt.FramelessWindowHint) if sys.platform == "win32": set_taskbar_icon() self.populate() self.tray_icon = None self.set_tray() self.ready() def populate(self): TitleBar(self) EmojiGrid(self) def set_tray(self): self.tray_icon = QSystemTrayIcon(self.icon) self.tray_icon.setToolTip(NAME) menu = QMenu() exit_action = menu.addAction("Exit") exit_action.triggered.connect(app.quit) self.tray_icon.setContextMenu(menu) def ready(self): self.tray_icon.show() self.show()
def runcatMemory(): app = QApplication(sys.argv) # 最后一个可视的窗口退出时程序不退出 app.setQuitOnLastWindowClosed(False) icon = QSystemTrayIcon() icon.setIcon(QIcon('icons/0.png')) icon.setVisible(True) memory_percent = psutil.virtual_memory().percent / 100 memory_percent_update_fps = 20 fps_count = 0 while True: fps_count += 1 if fps_count > memory_percent_update_fps: memory_percent = psutil.virtual_memory().percent / 100 fps_count = 0 # 开口向上的抛物线, 左边递减 time_interval = (memory_percent * memory_percent - 2 * memory_percent + 2) / 20 for i in range(5): icon.setIcon(QIcon('icons/%d.png' % i)) icon.setToolTip('memory: %.2f' % memory_percent) time.sleep(time_interval) app.exec_()
class TaryWiondw(QWidget): def __init__(self): super().__init__() # 创建托盘对象 self.tray = QSystemTrayIcon() # 创建QIcon对象,用于设置图标(图片过大会出错) self.trayIconPix = QPixmap(16, 16) self.trayIconPix.fill(QColor(100, 100, 100)) self.Icon = QIcon(self.trayIconPix) # 设置托盘图标(QIcon图标过大或者出错会导致托盘显示不出来) self.tray.setIcon(self.Icon) # 创建QAction showAction = QAction("&Show", self, triggered=self.Show) quitAction = QAction("&Quit", self, triggered=self.Exit) # 创建菜单对象 self.trayMenu = QMenu(self) # 将动作对象添加到菜单 self.trayMenu.addAction(showAction) # 增加分割线 self.trayMenu.addSeparator() self.trayMenu.addAction(quitAction) # 将菜单栏加入到右键按钮中 self.tray.setContextMenu(self.trayMenu) def Exit(self): # 点击关闭按钮或者点击退出事件会出现图标无法消失的bug,需要手动将图标内存清除 self.tray = None sys.exit(app.exec_()) def Show(self): self.show() print(self.tray.showMessage("test", "test", icon=0, msecs=100)) self.tray.show self.tray.setToolTip("这是一个气泡提示信息!")
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 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 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 XNova_MainWindow(QWidget): STATE_NOT_AUTHED = 0 STATE_AUTHED = 1 def __init__(self, parent=None): super(XNova_MainWindow, self).__init__(parent, Qt.Window) # state vars self.config_store_dir = './cache' self.cfg = configparser.ConfigParser() self.cfg.read('config/net.ini', encoding='utf-8') self.state = self.STATE_NOT_AUTHED self.login_email = '' self.cookies_dict = {} self._hidden_to_tray = False # # init UI self.setWindowIcon(QIcon(':/i/xnova_logo_64.png')) self.setWindowTitle('XNova Commander') # main layouts self._layout = QVBoxLayout() self._layout.setContentsMargins(0, 2, 0, 0) self._layout.setSpacing(3) self.setLayout(self._layout) self._horizontal_layout = QHBoxLayout() self._horizontal_layout.setContentsMargins(0, 0, 0, 0) self._horizontal_layout.setSpacing(6) # flights frame self._fr_flights = QFrame(self) self._fr_flights.setMinimumHeight(22) self._fr_flights.setFrameShape(QFrame.NoFrame) self._fr_flights.setFrameShadow(QFrame.Plain) # planets bar scrollarea self._sa_planets = QScrollArea(self) self._sa_planets.setMinimumWidth(125) self._sa_planets.setMaximumWidth(125) self._sa_planets.setFrameShape(QFrame.NoFrame) self._sa_planets.setFrameShadow(QFrame.Plain) self._sa_planets.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) self._sa_planets.setWidgetResizable(True) self._panel_planets = QWidget(self._sa_planets) self._layout_pp = QVBoxLayout() self._panel_planets.setLayout(self._layout_pp) self._lbl_planets = QLabel(self.tr('Planets:'), self._panel_planets) self._lbl_planets.setMaximumHeight(32) self._layout_pp.addWidget(self._lbl_planets) self._layout_pp.addStretch() self._sa_planets.setWidget(self._panel_planets) # # tab widget self._tabwidget = XTabWidget(self) self._tabwidget.enableButtonAdd(False) self._tabwidget.tabCloseRequested.connect(self.on_tab_close_requested) self._tabwidget.addClicked.connect(self.on_tab_add_clicked) # # create status bar self._statusbar = XNCStatusBar(self) self.set_status_message(self.tr('Not connected: Log in!')) # # tab widget pages self.login_widget = None self.flights_widget = None self.overview_widget = None self.imperium_widget = None # # settings widget self.settings_widget = SettingsWidget(self) self.settings_widget.settings_changed.connect(self.on_settings_changed) self.settings_widget.hide() # # finalize layouts self._horizontal_layout.addWidget(self._sa_planets) self._horizontal_layout.addWidget(self._tabwidget) self._layout.addWidget(self._fr_flights) self._layout.addLayout(self._horizontal_layout) self._layout.addWidget(self._statusbar) # # system tray icon self.tray_icon = None show_tray_icon = False if 'tray' in self.cfg: if (self.cfg['tray']['icon_usage'] == 'show') or \ (self.cfg['tray']['icon_usage'] == 'show_min'): self.create_tray_icon() # # try to restore last window size ssz = self.load_cfg_val('main_size') if ssz is not None: self.resize(ssz[0], ssz[1]) # # world initialization self.world = XNovaWorld_instance() self.world_timer = QTimer(self) self.world_timer.timeout.connect(self.on_world_timer) # overrides QWidget.closeEvent # cleanup just before the window close def closeEvent(self, close_event: QCloseEvent): logger.debug('closing') if self.tray_icon is not None: self.tray_icon.hide() self.tray_icon = None if self.world_timer.isActive(): self.world_timer.stop() self.world.script_command = 'stop' # also stop possible running scripts if self.world.isRunning(): self.world.quit() logger.debug('waiting for world thread to stop (5 sec)...') wait_res = self.world.wait(5000) if not wait_res: logger.warn('wait failed, last chance, terminating!') self.world.terminate() # store window size ssz = (self.width(), self.height()) self.store_cfg_val('main_size', ssz) # accept the event close_event.accept() def showEvent(self, evt: QShowEvent): super(XNova_MainWindow, self).showEvent(evt) self._hidden_to_tray = False def changeEvent(self, evt: QEvent): super(XNova_MainWindow, self).changeEvent(evt) if evt.type() == QEvent.WindowStateChange: if not isinstance(evt, QWindowStateChangeEvent): return # make sure we only do this for minimize events if (evt.oldState() != Qt.WindowMinimized) and self.isMinimized(): # we were minimized! explicitly hide settings widget # if it is open, otherwise it will be lost forever :( if self.settings_widget is not None: if self.settings_widget.isVisible(): self.settings_widget.hide() # should we minimize to tray? if self.cfg['tray']['icon_usage'] == 'show_min': if not self._hidden_to_tray: self._hidden_to_tray = True self.hide() def create_tray_icon(self): if QSystemTrayIcon.isSystemTrayAvailable(): logger.debug('System tray icon is available, showing') self.tray_icon = QSystemTrayIcon(QIcon(':/i/xnova_logo_32.png'), self) self.tray_icon.setToolTip(self.tr('XNova Commander')) self.tray_icon.activated.connect(self.on_tray_icon_activated) self.tray_icon.show() else: self.tray_icon = None def hide_tray_icon(self): if self.tray_icon is not None: self.tray_icon.hide() self.tray_icon.deleteLater() self.tray_icon = None def set_tray_tooltip(self, tip: str): if self.tray_icon is not None: self.tray_icon.setToolTip(tip) def set_status_message(self, msg: str): self._statusbar.set_status(msg) def store_cfg_val(self, category: str, value): pickle_filename = '{0}/{1}.dat'.format(self.config_store_dir, category) try: cache_dir = pathlib.Path(self.config_store_dir) if not cache_dir.exists(): cache_dir.mkdir() with open(pickle_filename, 'wb') as f: pickle.dump(value, f) except pickle.PickleError as pe: pass except IOError as ioe: pass def load_cfg_val(self, category: str, default_value=None): value = None pickle_filename = '{0}/{1}.dat'.format(self.config_store_dir, category) try: with open(pickle_filename, 'rb') as f: value = pickle.load(f) if value is None: value = default_value except pickle.PickleError as pe: pass except IOError as ioe: pass return value @pyqtSlot() def on_settings_changed(self): self.cfg.read('config/net.ini', encoding='utf-8') # maybe show/hide tray icon now? show_tray_icon = False if 'tray' in self.cfg: icon_usage = self.cfg['tray']['icon_usage'] if (icon_usage == 'show') or (icon_usage == 'show_min'): show_tray_icon = True # show if needs show and hidden, or hide if shown and needs to hide if show_tray_icon and (self.tray_icon is None): logger.debug('settings changed, showing tray icon') self.create_tray_icon() elif (not show_tray_icon) and (self.tray_icon is not None): logger.debug('settings changed, hiding tray icon') self.hide_tray_icon() # also notify world about changed config! self.world.reload_config() def add_tab(self, widget: QWidget, title: str, closeable: bool = True) -> int: tab_index = self._tabwidget.addTab(widget, title, closeable) return tab_index def remove_tab(self, index: int): self._tabwidget.removeTab(index) # called by main application object just after main window creation # to show login widget and begin login process def begin_login(self): # create flights widget self.flights_widget = FlightsWidget(self._fr_flights) self.flights_widget.load_ui() install_layout_for_widget(self._fr_flights, Qt.Vertical, margins=(1, 1, 1, 1), spacing=1) self._fr_flights.layout().addWidget(self.flights_widget) self.flights_widget.set_online_state(False) self.flights_widget.requestShowSettings.connect(self.on_show_settings) # create and show login widget as first tab self.login_widget = LoginWidget(self._tabwidget) self.login_widget.load_ui() self.login_widget.loginError.connect(self.on_login_error) self.login_widget.loginOk.connect(self.on_login_ok) self.login_widget.show() self.add_tab(self.login_widget, self.tr('Login'), closeable=False) # self.test_setup_planets_panel() # self.test_planet_tab() def setup_planets_panel(self, planets: list): layout = self._panel_planets.layout() layout.setSpacing(0) remove_trailing_spacer_from_layout(layout) # remove all previous planet widgets from planets panel if layout.count() > 0: for i in range(layout.count()-1, -1, -1): li = layout.itemAt(i) if li is not None: wi = li.widget() if wi is not None: if isinstance(wi, PlanetSidebarWidget): layout.removeWidget(wi) wi.close() wi.deleteLater() # fix possible mem leak del wi for pl in planets: pw = PlanetSidebarWidget(self._panel_planets) pw.setPlanet(pl) layout.addWidget(pw) pw.show() # connections from each planet bar widget pw.requestOpenGalaxy.connect(self.on_request_open_galaxy_tab) pw.requestOpenPlanet.connect(self.on_request_open_planet_tab) append_trailing_spacer_to_layout(layout) def update_planets_panel(self): """ Calls QWidget.update() on every PlanetBarWidget embedded in ui.panel_planets, causing repaint """ layout = self._panel_planets.layout() if layout.count() > 0: for i in range(layout.count()): li = layout.itemAt(i) if li is not None: wi = li.widget() if wi is not None: if isinstance(wi, PlanetSidebarWidget): wi.update() def add_tab_for_planet(self, planet: XNPlanet): # construct planet widget and setup signals/slots plw = PlanetWidget(self._tabwidget) plw.requestOpenGalaxy.connect(self.on_request_open_galaxy_tab) plw.setPlanet(planet) # construct tab title tab_title = '{0} {1}'.format(planet.name, planet.coords.coords_str()) # add tab and make it current tab_index = self.add_tab(plw, tab_title, closeable=True) self._tabwidget.setCurrentIndex(tab_index) self._tabwidget.tabBar().setTabIcon(tab_index, QIcon(':/i/planet_32.png')) return tab_index def add_tab_for_galaxy(self, coords: XNCoords = None): gw = GalaxyWidget(self._tabwidget) tab_title = '{0}'.format(self.tr('Galaxy')) if coords is not None: tab_title = '{0} {1}'.format(self.tr('Galaxy'), coords.coords_str()) gw.setCoords(coords.galaxy, coords.system) idx = self.add_tab(gw, tab_title, closeable=True) self._tabwidget.setCurrentIndex(idx) self._tabwidget.tabBar().setTabIcon(idx, QIcon(':/i/galaxy_32.png')) @pyqtSlot(int) def on_tab_close_requested(self, idx: int): # logger.debug('tab close requested: {0}'.format(idx)) if idx <= 1: # cannot close overview or imperium tabs return self.remove_tab(idx) @pyqtSlot() def on_tab_add_clicked(self): pos = QCursor.pos() planets = self.world.get_planets() # logger.debug('tab bar add clicked, cursor pos = ({0}, {1})'.format(pos.x(), pos.y())) menu = QMenu(self) # galaxy view galaxy_action = QAction(menu) galaxy_action.setText(self.tr('Add galaxy view')) galaxy_action.setData(QVariant('galaxy')) menu.addAction(galaxy_action) # planets menu.addSection(self.tr('-- Planet tabs: --')) for planet in planets: action = QAction(menu) action.setText('{0} {1}'.format(planet.name, planet.coords.coords_str())) action.setData(QVariant(planet.planet_id)) menu.addAction(action) action_ret = menu.exec(pos) if action_ret is not None: # logger.debug('selected action data = {0}'.format(str(action_ret.data()))) if action_ret == galaxy_action: logger.debug('action_ret == galaxy_action') self.add_tab_for_galaxy() return # else consider this is planet widget planet_id = int(action_ret.data()) self.on_request_open_planet_tab(planet_id) @pyqtSlot(str) def on_login_error(self, errstr): logger.error('Login error: {0}'.format(errstr)) self.state = self.STATE_NOT_AUTHED self.set_status_message(self.tr('Login error: {0}').format(errstr)) QMessageBox.critical(self, self.tr('Login error:'), errstr) @pyqtSlot(str, dict) def on_login_ok(self, login_email, cookies_dict): # logger.debug('Login OK, login: {0}, cookies: {1}'.format(login_email, str(cookies_dict))) # save login data: email, cookies self.state = self.STATE_AUTHED self.set_status_message(self.tr('Login OK, loading world')) self.login_email = login_email self.cookies_dict = cookies_dict # # destroy login widget and remove its tab self.remove_tab(0) self.login_widget.close() self.login_widget.deleteLater() self.login_widget = None # # create overview widget and add it as first tab self.overview_widget = OverviewWidget(self._tabwidget) self.overview_widget.load_ui() self.add_tab(self.overview_widget, self.tr('Overview'), closeable=False) self.overview_widget.show() self.overview_widget.setEnabled(False) # # create 2nd tab - Imperium self.imperium_widget = ImperiumWidget(self._tabwidget) self.add_tab(self.imperium_widget, self.tr('Imperium'), closeable=False) self.imperium_widget.setEnabled(False) # # initialize XNova world updater self.world.initialize(cookies_dict) self.world.set_login_email(self.login_email) # connect signals from world self.world.world_load_progress.connect(self.on_world_load_progress) self.world.world_load_complete.connect(self.on_world_load_complete) self.world.net_request_started.connect(self.on_net_request_started) self.world.net_request_finished.connect(self.on_net_request_finished) self.world.flight_arrived.connect(self.on_flight_arrived) self.world.build_complete.connect(self.on_building_complete) self.world.loaded_overview.connect(self.on_loaded_overview) self.world.loaded_imperium.connect(self.on_loaded_imperium) self.world.loaded_planet.connect(self.on_loaded_planet) self.world.start() @pyqtSlot(str, int) def on_world_load_progress(self, comment: str, progress: int): self._statusbar.set_world_load_progress(comment, progress) @pyqtSlot() def on_world_load_complete(self): logger.debug('main: on_world_load_complete()') # enable adding new tabs self._tabwidget.enableButtonAdd(True) # update statusbar self._statusbar.set_world_load_progress('', -1) # turn off progress display self.set_status_message(self.tr('World loaded.')) # update account info if self.overview_widget is not None: self.overview_widget.setEnabled(True) self.overview_widget.update_account_info() self.overview_widget.update_builds() # update flying fleets self.flights_widget.set_online_state(True) self.flights_widget.update_flights() # update planets planets = self.world.get_planets() self.setup_planets_panel(planets) if self.imperium_widget is not None: self.imperium_widget.setEnabled(True) self.imperium_widget.update_planets() # update statusbar self._statusbar.update_online_players_count() # update tray tooltip, add account name self.set_tray_tooltip(self.tr('XNova Commander') + ' - ' + self.world.get_account_info().login) # set timer to do every-second world recalculation self.world_timer.setInterval(1000) self.world_timer.setSingleShot(False) self.world_timer.start() @pyqtSlot() def on_loaded_overview(self): logger.debug('on_loaded_overview') # A lot of things are updated when overview is loaded # * Account information and stats if self.overview_widget is not None: self.overview_widget.update_account_info() # * flights will be updated every second anyway in on_world_timer(), so no need to call # self.flights_widget.update_flights() # * messages count also, is updated with flights # * current planet may have changed self.update_planets_panel() # * server time is updated also self._statusbar.update_online_players_count() @pyqtSlot() def on_loaded_imperium(self): logger.debug('on_loaded_imperium') # need to update imperium widget if self.imperium_widget is not None: self.imperium_widget.update_planets() # The important note here is that imperium update is the only place where # the planets list is read, so number of planets, their names, etc may change here # Also, imperium update OVERWRITES full planets array, so, all prev # references to planets in all GUI elements must be invalidated, because # they will point to unused, outdated planets planets = self.world.get_planets() # re-create planets sidebar self.setup_planets_panel(planets) # update all builds in overview widget if self.overview_widget: self.overview_widget.update_builds() # update all planet tabs with new planet references cnt = self._tabwidget.count() if cnt > 2: for index in range(2, cnt): tab_page = self._tabwidget.tabWidget(index) if tab_page is not None: try: tab_type = tab_page.get_tab_type() if tab_type == 'planet': tab_planet = tab_page.planet() new_planet = self.world.get_planet(tab_planet.planet_id) tab_page.setPlanet(new_planet) except AttributeError: # not all pages may have method get_tab_type() pass @pyqtSlot(int) def on_loaded_planet(self, planet_id: int): logger.debug('Got signal on_loaded_planet({0}), updating overview ' 'widget and planets panel'.format(planet_id)) if self.overview_widget: self.overview_widget.update_builds() self.update_planets_panel() # update also planet tab, if any planet = self.world.get_planet(planet_id) if planet is not None: tab_idx = self.find_tab_for_planet(planet_id) if tab_idx != -1: tab_widget = self._tabwidget.tabWidget(tab_idx) if isinstance(tab_widget, PlanetWidget): logger.debug('Updating planet tab #{}'.format(tab_idx)) tab_widget.setPlanet(planet) @pyqtSlot() def on_world_timer(self): if self.world: self.world.world_tick() self.update_planets_panel() if self.flights_widget: self.flights_widget.update_flights() if self.overview_widget: self.overview_widget.update_builds() if self.imperium_widget: self.imperium_widget.update_planet_resources() @pyqtSlot() def on_net_request_started(self): self._statusbar.set_loading_status(True) @pyqtSlot() def on_net_request_finished(self): self._statusbar.set_loading_status(False) @pyqtSlot(int) def on_tray_icon_activated(self, reason): # QSystemTrayIcon::Unknown 0 Unknown reason # QSystemTrayIcon::Context 1 The context menu for the system tray entry was requested # QSystemTrayIcon::DoubleClick 2 The system tray entry was double clicked # QSystemTrayIcon::Trigger 3 The system tray entry was clicked # QSystemTrayIcon::MiddleClick 4 The system tray entry was clicked with the middle mouse button if reason == QSystemTrayIcon.Trigger: # left-click self.setWindowState((self.windowState() & ~Qt.WindowMinimized) | Qt.WindowActive) self.show() return def show_tray_message(self, title, message, icon_type=None, timeout_ms=None): """ Shows message from system tray icon, if system supports it. If no support, this is just a no-op :param title: message title :param message: message text :param icon_type: one of: QSystemTrayIcon.NoIcon 0 No icon is shown. QSystemTrayIcon.Information 1 An information icon is shown. QSystemTrayIcon.Warning 2 A standard warning icon is shown. QSystemTrayIcon.Critical 3 A critical warning icon is shown """ if self.tray_icon is None: return if self.tray_icon.supportsMessages(): if icon_type is None: icon_type = QSystemTrayIcon.Information if timeout_ms is None: timeout_ms = 10000 self.tray_icon.showMessage(title, message, icon_type, timeout_ms) else: logger.info('This system does not support tray icon messages.') @pyqtSlot() def on_show_settings(self): if self.settings_widget is not None: self.settings_widget.show() self.settings_widget.showNormal() @pyqtSlot(XNFlight) def on_flight_arrived(self, fl: XNFlight): logger.debug('main: flight arrival: {0}'.format(fl)) mis_str = flight_mission_for_humans(fl.mission) if fl.direction == 'return': mis_str += ' ' + self.tr('return') short_fleet_info = self.tr('{0} {1} => {2}, {3} ship(s)').format( mis_str, fl.src, fl.dst, len(fl.ships)) self.show_tray_message(self.tr('XNova: Fleet arrived'), short_fleet_info) @pyqtSlot(XNPlanet, XNPlanetBuildingItem) def on_building_complete(self, planet: XNPlanet, bitem: XNPlanetBuildingItem): logger.debug('main: build complete: on planet {0}: {1}'.format( planet.name, str(bitem))) # update also planet tab, if any if isinstance(planet, XNPlanet): tab_idx = self.find_tab_for_planet(planet.planet_id) if tab_idx != -1: tab_widget = self._tabwidget.tabWidget(tab_idx) if isinstance(tab_widget, PlanetWidget): logger.debug('Updating planet tab #{}'.format(tab_idx)) tab_widget.setPlanet(planet) # construct message to show in tray if bitem.is_shipyard_item: binfo_str = '{0} x {1}'.format(bitem.quantity, bitem.name) else: binfo_str = self.tr('{0} lv.{1}').format(bitem.name, bitem.level) msg = self.tr('{0} has built {1}').format(planet.name, binfo_str) self.show_tray_message(self.tr('XNova: Building complete'), msg) @pyqtSlot(XNCoords) def on_request_open_galaxy_tab(self, coords: XNCoords): tab_index = self.find_tab_for_galaxy(coords.galaxy, coords.system) if tab_index == -1: # create new tab for these coords self.add_tab_for_galaxy(coords) return # else switch to that tab self._tabwidget.setCurrentIndex(tab_index) @pyqtSlot(int) def on_request_open_planet_tab(self, planet_id: int): tab_index = self.find_tab_for_planet(planet_id) if tab_index == -1: # create new tab for planet planet = self.world.get_planet(planet_id) if planet is not None: self.add_tab_for_planet(planet) return # else switch to that tab self._tabwidget.setCurrentIndex(tab_index) def find_tab_for_planet(self, planet_id: int) -> int: """ Finds tab index where specified planet is already opened :param planet_id: planet id to search for :return: tab index, or -1 if not found """ cnt = self._tabwidget.count() if cnt < 3: return -1 # only overview and imperium tabs are present for index in range(2, cnt): tab_page = self._tabwidget.tabWidget(index) if tab_page is not None: try: tab_type = tab_page.get_tab_type() if tab_type == 'planet': tab_planet = tab_page.planet() if tab_planet.planet_id == planet_id: # we have found tab index where this planet is already opened return index except AttributeError: # not all pages may have method get_tab_type() pass return -1 def find_tab_for_galaxy(self, galaxy: int, system: int) -> int: """ Finds tab index where specified galaxy view is already opened :param galaxy: galaxy target coordinate :param system: system target coordinate :return: tab index, or -1 if not found """ cnt = self._tabwidget.count() if cnt < 3: return -1 # only overview and imperium tabs are present for index in range(2, cnt): tab_page = self._tabwidget.tabWidget(index) if tab_page is not None: try: tab_type = tab_page.get_tab_type() if tab_type == 'galaxy': coords = tab_page.coords() if (coords[0] == galaxy) and (coords[1] == system): # we have found galaxy tab index where this place is already opened return index except AttributeError: # not all pages may have method get_tab_type() pass return -1 def test_setup_planets_panel(self): """ Testing only - add 'fictive' planets to test planets panel without loading data :return: None """ pl1 = XNPlanet('Arnon', XNCoords(1, 7, 6)) pl1.pic_url = 'skins/default/planeten/small/s_normaltempplanet08.jpg' pl1.fields_busy = 90 pl1.fields_total = 167 pl1.is_current = True pl2 = XNPlanet('Safizon', XNCoords(1, 232, 7)) pl2.pic_url = 'skins/default/planeten/small/s_dschjungelplanet05.jpg' pl2.fields_busy = 84 pl2.fields_total = 207 pl2.is_current = False test_planets = [pl1, pl2] self.setup_planets_panel(test_planets) def test_planet_tab(self): """ Testing only - add 'fictive' planet tab to test UI without loading world :return: """ # construct planet pl1 = XNPlanet('Arnon', coords=XNCoords(1, 7, 6), planet_id=12345) pl1.pic_url = 'skins/default/planeten/small/s_normaltempplanet08.jpg' pl1.fields_busy = 90 pl1.fields_total = 167 pl1.is_current = True pl1.res_current.met = 10000000 pl1.res_current.cry = 50000 pl1.res_current.deit = 250000000 # 250 mil pl1.res_per_hour.met = 60000 pl1.res_per_hour.cry = 30000 pl1.res_per_hour.deit = 15000 pl1.res_max_silos.met = 6000000 pl1.res_max_silos.cry = 3000000 pl1.res_max_silos.deit = 1000000 pl1.energy.energy_left = 10 pl1.energy.energy_total = 1962 pl1.energy.charge_percent = 92 # planet building item bitem = XNPlanetBuildingItem() bitem.gid = 1 bitem.name = 'Рудник металла' bitem.level = 29 bitem.remove_link = '' bitem.build_link = '?set=buildings&cmd=insert&building={0}'.format(bitem.gid) bitem.seconds_total = 23746 bitem.cost_met = 7670042 bitem.cost_cry = 1917510 bitem.is_building_item = True # second bitem bitem2 = XNPlanetBuildingItem() bitem2.gid = 2 bitem2.name = 'Рудник кристалла' bitem2.level = 26 bitem2.remove_link = '' bitem2.build_link = '?set=buildings&cmd=insert&building={0}'.format(bitem2.gid) bitem2.seconds_total = 13746 bitem2.cost_met = 9735556 bitem2.cost_cry = 4667778 bitem2.is_building_item = True bitem2.is_downgrade = True bitem2.seconds_left = bitem2.seconds_total // 2 bitem2.calc_end_time() # add bitems pl1.buildings_items = [bitem, bitem2] # add self.add_tab_for_planet(pl1)
class MainWindow(QMainWindow): """ A window that shows the current status of the encrypted container and a button to unlock/close it. Open containers leave an icon in the systray as a reminder to close them eventually. """ def __init__(self, device_name=None, container_path=None, key_file=None, mount_point=None): """ Command line arguments checks are done here to be able to display a graphical dialog with error messages . If no arguments were supplied on the command line a setup dialog will be shown. All commands will be executed from a separate worker process with administrator privileges that gets initialized here. :param device_name: The device mapper name :type device_name: str/unicode or None :param container_path: The path of the container file :type container_path: str/unicode or None :param key_file: The path of an optional key file :type key_file: str/unicode or None :param mount_point: The path of an optional mount point :type mount_point: str/unicode or None """ super(MainWindow, self).__init__() self.luks_device_name = device_name self.encrypted_container = container_path self.key_file = key_file self.mount_point = mount_point self.worker = None self.is_waiting_for_worker = False self.is_unlocked = False self.is_initialized = False self.has_tray = QSystemTrayIcon.isSystemTrayAvailable() # L10n: program name - translatable for startmenu titlebar etc self.setWindowTitle(_('luckyLUKS')) self.setWindowIcon(QIcon.fromTheme('dialog-password', QApplication.style().standardIcon(QStyle.SP_DriveHDIcon))) # check if cryptsetup and sudo are installed not_installed_msg = _('{program_name} executable not found!\nPlease install, eg for Debian/Ubuntu\n`apt-get install {program_name}`') if not utils.is_installed('cryptsetup'): show_alert(self, not_installed_msg.format(program_name='cryptsetup'), critical=True) if not utils.is_installed('sudo'): show_alert(self, not_installed_msg.format(program_name='sudo'), critical=True) # quick sanity checks before asking for passwd if os.getuid() == 0: show_alert(self, _('Graphical programs should not be run as root!\nPlease call as normal user.'), critical=True) if self.encrypted_container and not os.path.exists(self.encrypted_container): show_alert(self, _('Container file not accessible\nor path does not exist:\n\n{file_path}').format(file_path=self.encrypted_container), critical=True) # only either encrypted_container or luks_device_name supplied if bool(self.encrypted_container) != bool(self.luks_device_name): show_alert(self, _('Invalid arguments:\n' 'Please call without any arguments\n' 'or supply both container and name.\n\n' '<b>{executable} -c CONTAINER -n NAME [-m MOUNTPOINT]</b>\n\n' 'CONTAINER = Path of the encrypted container file\n' 'NAME = A (unique) name to identify the unlocked container\n' 'Optional: MOUNTPOINT = where to mount the encrypted filesystem\n\n' 'If automatic mounting is configured on your system,\n' 'explicitly setting a mountpoint is not required\n\n' 'For more information, visit\n' '<a href="{project_url}">{project_url}</a>' ).format(executable=os.path.basename(sys.argv[0]), project_url=PROJECT_URL), critical=True) # spawn worker process with root privileges try: self.worker = utils.WorkerMonitor(self) # start communication thread self.worker.start() except utils.SudoException as se: show_alert(self, format_exception(se), critical=True) return # if no arguments supplied, display dialog to gather this information if self.encrypted_container is None and self.luks_device_name is None: from luckyLUKS.setupUI import SetupDialog sd = SetupDialog(self) if sd.exec_() == QDialog.Accepted: self.luks_device_name = sd.get_luks_device_name() self.encrypted_container = sd.get_encrypted_container() self.mount_point = sd.get_mount_point() self.key_file = sd.get_keyfile() self.is_unlocked = True # all checks in setup dialog -> skip initializing state else: # user closed dialog -> quit program # and check if a keyfile create thread has to be stopped # the worker process terminates itself when its parent dies if hasattr(sd, 'create_thread') and sd.create_thread.isRunning(): sd.create_thread.terminate() QApplication.instance().quit() return # center window on desktop qr = self.frameGeometry() cp = QDesktopWidget().availableGeometry().center() qr.moveCenter(cp) self.move(qr.topLeft()) # widget content main_grid = QGridLayout() main_grid.setSpacing(10) icon = QLabel() icon.setPixmap(QIcon.fromTheme('dialog-password', QApplication.style().standardIcon(QStyle.SP_DriveHDIcon)).pixmap(32)) main_grid.addWidget(icon, 0, 0) main_grid.addWidget(QLabel('<b>' + _('Handle encrypted container') + '</b>\n'), 0, 1, alignment=Qt.AlignCenter) main_grid.addWidget(QLabel(_('Name:')), 1, 0) main_grid.addWidget(QLabel('<b>{dev_name}</b>'.format(dev_name=self.luks_device_name)), 1, 1, alignment=Qt.AlignCenter) main_grid.addWidget(QLabel(_('File:')), 2, 0) main_grid.addWidget(QLabel(self.encrypted_container), 2, 1, alignment=Qt.AlignCenter) if self.key_file is not None: main_grid.addWidget(QLabel(_('Key:')), 3, 0) main_grid.addWidget(QLabel(self.key_file), 3, 1, alignment=Qt.AlignCenter) if self.mount_point is not None: main_grid.addWidget(QLabel(_('Mount:')), 4, 0) main_grid.addWidget(QLabel(self.mount_point), 4, 1, alignment=Qt.AlignCenter) main_grid.addWidget(QLabel(_('Status:')), 5, 0) self.label_status = QLabel('') main_grid.addWidget(self.label_status, 5, 1, alignment=Qt.AlignCenter) self.button_toggle_status = QPushButton('') self.button_toggle_status.setMinimumHeight(34) self.button_toggle_status.clicked.connect(self.toggle_container_status) main_grid.setRowMinimumHeight(6, 10) main_grid.addWidget(self.button_toggle_status, 7, 1) widget = QWidget() widget.setLayout(main_grid) widget.setContentsMargins(10, 10, 10, 10) self.setCentralWidget(widget) # tray popup menu if self.has_tray: tray_popup = QMenu(self) tray_popup.addAction(QIcon.fromTheme('dialog-password', QApplication.style().standardIcon(QStyle.SP_DriveHDIcon)), self.luks_device_name).setEnabled(False) tray_popup.addSeparator() self.tray_toggle_action = QAction(QApplication.style().standardIcon(QStyle. SP_DesktopIcon), _('Hide'), self) self.tray_toggle_action.triggered.connect(self.toggle_main_window) tray_popup.addAction(self.tray_toggle_action) quit_action = QAction(QApplication.style().standardIcon(QStyle.SP_MessageBoxCritical), _('Quit'), self) quit_action.triggered.connect(self.tray_quit) tray_popup.addAction(quit_action) # systray self.tray = QSystemTrayIcon(self) self.tray.setIcon(QIcon.fromTheme('dialog-password', QApplication.style().standardIcon(QStyle.SP_DriveHDIcon))) self.tray.setContextMenu(tray_popup) self.tray.activated.connect(self.toggle_main_window) self.tray.show() self.init_status() def refresh(self): """ Update widgets to reflect current container status. Adds systray icon if needed """ if self.is_unlocked: self.label_status.setText(_('Container is {unlocked_green_bold}').format( unlocked_green_bold='<font color="#006400"><b>' + _('unlocked') + '</b></font>')) self.button_toggle_status.setText(_('Close Container')) if self.has_tray: self.tray.setToolTip(_('{device_name} is unlocked').format(device_name=self.luks_device_name)) else: self.label_status.setText(_('Container is {closed_red_bold}').format( closed_red_bold='<font color="#b22222"><b>' + _('closed') + '</b></font>')) self.button_toggle_status.setText(_('Unlock Container')) if self.has_tray: self.tray.setToolTip(_('{device_name} is closed').format(device_name=self.luks_device_name)) self.show() self.setFixedSize(self.sizeHint()) def tray_quit(self): """ Triggered by clicking on `quit` in the systray popup: asks to close an unlocked container """ if not self.is_unlocked: QApplication.instance().quit() elif not self.is_waiting_for_worker: self.show() self.confirm_close() def toggle_main_window(self, tray_icon_clicked): """ Triggered by clicking on the systray icon: show/hide main window """ if not tray_icon_clicked or tray_icon_clicked == QSystemTrayIcon.Trigger: # don't activate on rightclick/contextmenu if self.isVisible(): self.hide() self.tray_toggle_action.setText(_('Show')) else: self.show() self.tray_toggle_action.setText(_('Hide')) def closeEvent(self, event): """ Triggered by closing the window: If the container is unlocked, the program won't quit but remain in the systray. """ if not self.is_waiting_for_worker: if self.is_unlocked: if self.has_tray: self.hide() self.tray_toggle_action.setText(_('Show')) else: self.confirm_close() event.ignore() else: event.accept() def confirm_close(self): """ Inform about opened container and ask for confirmation to close & quit """ message = _('<b>{device_name}</b> >> {container_path}\n' 'is currently <b>unlocked</b>,\n' 'Close Container now and quit?').format(device_name=self.luks_device_name, container_path=self.encrypted_container) mb = QMessageBox(QMessageBox.Question, '', message, QMessageBox.Ok | QMessageBox.Cancel, self) mb.button(QMessageBox.Ok).setText(_('Quit')) if mb.exec_() == QMessageBox.Ok: self.do_close_container(shutdown=True) def customEvent(self, event): """ Receives response from worker and calls supplied callback function """ event.callback(event.response) def toggle_container_status(self): """ Unlock or close container """ if self.is_unlocked: self.do_close_container() else: try: UnlockContainerDialog(self, self.worker, self.luks_device_name, self.encrypted_container, self.key_file, self.mount_point).communicate() self.is_unlocked = True except UserInputError as uie: show_alert(self, format_exception(uie)) self.is_unlocked = False self.refresh() def do_close_container(self, shutdown=False): """ Send close command to worker and supply callbacks :param shutdown: Quit application after container successfully closed? (default=False) :type shutdown: bool """ self.disable_ui(_('Closing Container ..')) self.worker.execute(command={'type': 'request', 'msg': 'close', 'device_name': self.luks_device_name, 'container_path': self.encrypted_container }, success_callback=lambda msg: self.on_container_closed(msg, error=False, shutdown=shutdown), error_callback=lambda msg: self.on_container_closed(msg, error=True, shutdown=shutdown)) def on_container_closed(self, message, error, shutdown): """ Callback after worker closed container :param message: Contains an error description if error=True, otherwise the current state of the container (unlocked/closed) :type message: str :param error: Error during closing of container :type error: bool :param shutdown: Quit application after container successfully closed? :type shutdown: bool """ if error: show_alert(self, message) else: self.is_unlocked = False if not error and shutdown: # automatic shutdown only if container successfully closed QApplication.instance().quit() else: self.enable_ui() def init_status(self): """ Request current status of container from worker if needed """ if not self.is_unlocked: self.disable_ui(_('Initializing ..')) self.worker.execute(command={'type': 'request', 'msg': 'status', 'device_name': self.luks_device_name, 'container_path': self.encrypted_container, 'key_file': self.key_file, 'mount_point': self.mount_point }, success_callback=self.on_initialized, error_callback=lambda msg: self.on_initialized(msg, error=True)) else: # unlocked by setup-dialog -> just refresh UI self.enable_ui() self.is_initialized = True # qt event loop can start now def on_initialized(self, message, error=False): """ Callback after worker send current state of container :param message: Contains an error description if error=True, otherwise the current state of the container (unlocked/closed) :type message: str :param critical: Error during initialization (default=False) :type critical: bool """ if error: show_alert(self, message, critical=True) else: self.is_unlocked = (True if message == 'unlocked' else False) self.enable_ui() def enable_ui(self): """ Enable buttons and refresh state """ self.refresh() self.is_waiting_for_worker = False self.button_toggle_status.setEnabled(True) def disable_ui(self, reason): """ Disable buttons and display waiting message :param reason: A waiting message that gets displayed :type reason: str/unicode """ self.is_waiting_for_worker = True self.button_toggle_status.setText(reason) self.button_toggle_status.setEnabled(False)
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 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 Dialog(QDialog): def __init__(self): super().__init__() # Создание парсера self._dotaParser = parser.DotaParser() # Инициализация GUI self.initUi() self.initTray() self.initTimer() # Первоначальный парсинг self.startParser() self.fillDialog() def initUi(self): self._ongoingTitle = QLabel("<h3>Ongoing matches</h3>") self._ongoingLabel = QLabel("") self._upcomingTitle = QLabel("<h3>Upcoming matches</h3>") self._upcomingLabel = QLabel("") self._quitButton = QPushButton("Quit") self._layout = QVBoxLayout() self._layout.addWidget(self._ongoingTitle) self._layout.addWidget(self._ongoingLabel) self._layout.addWidget(self._upcomingTitle) self._layout.addWidget(self._upcomingLabel) self._layout.addWidget(self._quitButton) self.setLayout(self._layout) self._quitButton.clicked.connect(qApp.quit) self.setWindowTitle("Dota-2-Widget") def initTray(self): self._tray = QSystemTrayIcon() self._tray.setToolTip("dota-2-widget") self._tray.setIcon(QIcon("./dota2.gif")) self._tray.activated.connect(self.showOrHideDialog) self._tray.show() def initTimer(self): self._timer = QTimer() self._timer.timeout.connect(self.startParser) self._timer.timeout.connect(self.fillDialog) self._timer.start(5000) def startParser(self): self._dotaParser.startParser() #print("startParser") def fillDialog(self): # Получение результатов ongoingMatches = self._dotaParser.getOngoingMatches() upcomingMatches = self._dotaParser.getUpcomingMatches() # Запись результатов resultString = "" for i in range(0, len(ongoingMatches)): resultString += ongoingMatches[i] + '\n' self._ongoingLabel.setText(resultString) resultString = "" for i in range(0, len(upcomingMatches)): resultString += upcomingMatches[i] + '\n' if (i > 10): break self._upcomingLabel.setText(resultString) #print("fillDialog") def closeEvent(self, ce): if (self._tray.isVisible()): self.hide() def showOrHideDialog(self, result): if (result == QSystemTrayIcon.Trigger): if (not self.isVisible()): self.show() else: self.hide()
class ElectrumGui(Logger): network_dialog: Optional['NetworkDialog'] @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-dash.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-dash.png")) self._cleaned_up = False # timer self.timer = QTimer(self.app) self.timer.setSingleShot(False) self.timer.setInterval(500) # msec self.network_dialog = None self.dash_net_dialog = None self.network_updated_signal_obj = QNetworkUpdatedSignalObject() self.dash_net_sobj = QDashNetSignalsObject() self._num_wizards_in_progress = 0 self._num_wizards_lock = threading.Lock() self.dark_icon = self.config.get("dark_icon", False) self.tray = None self._init_tray() self.app.new_window_signal.connect(self.start_new_window) self.set_dark_theme_if_needed() run_hook('init_qt', self) def _init_tray(self): self.tray = QSystemTrayIcon(self.tray_icon(), None) self.tray.setToolTip('Dash Electrum') self.tray.activated.connect(self.tray_activated) self.build_tray_menu() self.tray.show() def set_dark_theme_if_needed(self): use_dark_theme = self.config.get('qt_gui_color_theme', 'default') == 'dark' self.app.setStyle('Fusion') if use_dark_theme: from .dark_dash_style import dash_stylesheet self.app.setStyleSheet(dash_stylesheet) else: from .dash_style import dash_stylesheet self.app.setStyleSheet(dash_stylesheet) # Apply any necessary stylesheet patches patch_qt_stylesheet(use_dark_theme=use_dark_theme) # Even if we ourselves don't set the dark theme, # the OS/window manager/etc might set *a dark theme*. # Hence, try to choose colors accordingly: ColorScheme.update_from_widget(QWidget(), force_dark=use_dark_theme) def build_tray_menu(self): if not self.tray: return # Avoid immediate GC of old menu when window closed via its action if self.tray.contextMenu() is None: m = QMenu() self.tray.setContextMenu(m) else: m = self.tray.contextMenu() m.clear() m.addAction(_("Network"), self.show_network_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 Dash Electrum"), self.app.quit) def tray_icon(self): if self.dark_icon: return read_QIcon('electrum_dark_icon.png') else: return read_QIcon('electrum_light_icon.png') def toggle_tray_icon(self): if not self.tray: return self.dark_icon = not self.dark_icon self.config.set_key("dark_icon", self.dark_icon, True) self.tray.setIcon(self.tray_icon()) def tray_activated(self, reason): if reason == QSystemTrayIcon.DoubleClick: if all([w.is_hidden() for w in self.windows]): for w in self.windows: w.bring_to_top() else: for w in self.windows: w.hide() def _cleanup_before_exit(self): if self._cleaned_up: return self._cleaned_up = True self.app.new_window_signal.disconnect() self.efilter = None # If there are still some open windows, try to clean them up. for window in list(self.windows): window.close() window.clean_up() if self.network_dialog: self.network_dialog.close() self.network_dialog.clean_up() self.network_dialog = None self.network_updated_signal_obj = None if self.dash_net_dialog: self.dash_net_dialog.close() self.dash_net_dialog.clean_up() self.dash_net_dialog = None self.dash_net_sobj = None # Shut down the timer cleanly self.timer.stop() self.timer = None # clipboard persistence. see http://www.mail-archive.com/[email protected]/msg17328.html event = QtCore.QEvent(QtCore.QEvent.Clipboard) self.app.sendEvent(self.app.clipboard(), event) if self.tray: self.tray.hide() self.tray.deleteLater() self.tray = None def _maybe_quit_if_no_windows_open(self) -> None: """Check if there are any open windows and decide whether we should quit.""" # keep daemon running after close if self.config.get('daemon'): return # check if a wizard is in progress with self._num_wizards_lock: if self._num_wizards_in_progress > 0 or len(self.windows) > 0: return self.app.quit() def new_window(self, path, uri=None): # Use a signal as can be called from daemon thread self.app.new_window_signal.emit(path, uri) def show_network_dialog(self): if self.network_dialog: self.network_dialog.on_update() self.network_dialog.show() self.network_dialog.raise_() return self.network_dialog = NetworkDialog( network=self.daemon.network, config=self.config, network_updated_signal_obj=self.network_updated_signal_obj) self.network_dialog.show() def show_dash_net_dialog(self): if self.dash_net_dialog: self.dash_net_dialog.on_updated() self.dash_net_dialog.show() self.dash_net_dialog.raise_() return self.dash_net_dialog = DashNetDialog(network=self.daemon.network, config=self.config, dash_net_sobj=self.dash_net_sobj) self.dash_net_dialog.show() def _create_window_for_wallet(self, wallet): w = ElectrumWindow(self, wallet) self.windows.append(w) self.build_tray_menu() w.warn_if_testnet() w.warn_if_watching_only() return w def count_wizards_in_progress(func): def wrapper(self: 'ElectrumGui', *args, **kwargs): with self._num_wizards_lock: self._num_wizards_in_progress += 1 try: return func(self, *args, **kwargs) finally: with self._num_wizards_lock: self._num_wizards_in_progress -= 1 self._maybe_quit_if_no_windows_open() return wrapper @count_wizards_in_progress def start_new_window(self, path, uri, *, app_is_starting=False) -> Optional[ElectrumWindow]: '''Raises the window for the wallet if it is open. Otherwise opens the wallet and creates a new window for it''' wallet = None if self.config.get('tor_auto_on', True): network = self.daemon.network if network: proxy_modifiable = self.config.is_modifiable('proxy') if not proxy_modifiable or not network.detect_tor_proxy(): warn_d = TorWarnDialog(self, path) warn_d.exec_() if warn_d.result() < 0: return try: wallet = self.daemon.load_wallet(path, None) except Exception as e: self.logger.exception('') custom_message_box(icon=QMessageBox.Warning, parent=None, title=_('Error'), text=_('Cannot load wallet') + ' (1):\n' + repr(e)) # if app is starting, still let wizard to appear if not app_is_starting: return if not wallet: try: wallet = self._start_wizard_to_select_or_create_wallet(path) except (WalletFileException, BitcoinException) as e: self.logger.exception('') custom_message_box(icon=QMessageBox.Warning, parent=None, title=_('Error'), text=_('Cannot load wallet') + ' (2):\n' + repr(e)) if not wallet: return # create or raise window try: for window in self.windows: if window.wallet.storage.path == wallet.storage.path: break else: window = self._create_window_for_wallet(wallet) except Exception as e: self.logger.exception('') custom_message_box(icon=QMessageBox.Warning, parent=None, title=_('Error'), text=_('Cannot create window for wallet') + ':\n' + repr(e)) if app_is_starting: wallet_dir = os.path.dirname(path) path = os.path.join(wallet_dir, get_new_wallet_name(wallet_dir)) self.start_new_window(path, uri) return if uri: window.pay_to_URI(uri) window.bring_to_top() window.setWindowState(window.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive) window.activateWindow() return window def _start_wizard_to_select_or_create_wallet( self, path) -> Optional[Abstract_Wallet]: wizard = InstallWizard(self.config, self.app, self.plugins, gui_object=self) try: path, storage = wizard.select_storage(path, self.daemon.get_wallet) # storage is None if file does not exist if storage is None: wizard.path = path # needed by trustedcoin plugin wizard.run('new') storage, db = wizard.create_storage(path) if db.check_unfinished_multisig(): wizard.show_message(_('Saved unfinished multisig wallet')) return else: db = WalletDB(storage.read(), manual_upgrades=False) if db.upgrade_done: storage.backup_old_version() wizard.run_upgrades(storage, db) if getattr(storage, 'backup_message', None): custom_message_box(icon=QMessageBox.Warning, parent=None, title=_('Information'), text=storage.backup_message) storage.backup_message = '' if db.check_unfinished_multisig(): wizard.continue_multisig_setup(storage) storage, db = wizard.create_storage(storage.path) if db.check_unfinished_multisig(): wizard.show_message(_('Saved unfinished multisig wallet')) return except (UserCancelled, GoBack): return except WalletAlreadyOpenInMemory as e: return e.wallet finally: wizard.terminate() # return if wallet creation is not complete if storage is None or db.get_action(): return wallet = Wallet(db, storage, config=self.config) wallet.start_network(self.daemon.network) self.daemon.add_wallet(wallet) return wallet def close_window(self, window: ElectrumWindow): if window in self.windows: self.windows.remove(window) self.build_tray_menu() # save wallet path of last open window if not self.windows: self.config.save_last_wallet(window.wallet) run_hook('on_close_window', window) self.daemon.stop_wallet(window.wallet.storage.path) def init_network(self): # Show network dialog if config does not exist if self.daemon.network: if self.config.get('auto_connect') is None: wizard = InstallWizard(self.config, self.app, self.plugins, gui_object=self) wizard.init_network(self.daemon.network) wizard.terminate() def main(self): # setup Ctrl-C handling and tear-down code first, so that user can easily exit whenever self.app.setQuitOnLastWindowClosed( False) # so _we_ can decide whether to quit self.app.lastWindowClosed.connect(self._maybe_quit_if_no_windows_open) self.app.aboutToQuit.connect(self._cleanup_before_exit) signal.signal(signal.SIGINT, lambda *args: self.app.quit()) # hook for crash reporter Exception_Hook.maybe_setup(config=self.config) # first-start network-setup try: self.init_network() except UserCancelled: return except GoBack: return except Exception as e: self.logger.exception('') return # start wizard to select/create wallet self.timer.start() path = self.config.get_wallet_path(use_gui_last_wallet=True) try: if not self.start_new_window( path, self.config.get('url'), app_is_starting=True): return except Exception as e: self.logger.error( "error loading wallet (or creating window for it)") send_exception_to_crash_reporter(e) # Let Qt event loop start properly so that crash reporter window can appear. # We will shutdown when the user closes that window, via lastWindowClosed signal. # main loop self.logger.info("starting Qt main loop") self.app.exec_() # on some platforms the exec_ call may not return, so use _cleanup_before_exit def stop(self): self.logger.info('closing GUI') self.app.quit()
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 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 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 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 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 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 TriblerWindow(QMainWindow): resize_event = pyqtSignal() escape_pressed = pyqtSignal() tribler_crashed = pyqtSignal(str) 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 exception_text = "".join(traceback.format_exception(*exc_info)) logging.error(exception_text) self.tribler_crashed.emit(exception_text) 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) 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 self.shutdown_timer = None self.add_torrent_url_dialog_active = False sys.excepthook = self.on_exception 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) 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) self.import_torrent_shortcut = QShortcut(QKeySequence("Ctrl+o"), self) self.import_torrent_shortcut.activated.connect(self.on_add_torrent_browse_file) self.add_torrent_url_shortcut = QShortcut(QKeySequence("Ctrl+i"), self) self.add_torrent_url_shortcut.activated.connect(self.on_add_torrent_from_url) # 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.gui_settings) self.settings_page.initialize_settings_page() self.subscribed_channels_page.initialize() self.edit_channel_page.initialize_edit_channel_page(self.gui_settings) 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.gui_settings) self.channel_page.initialize_channel_page(self.gui_settings) self.trust_page.initialize_trust_page() self.token_mining_page.initialize_token_mining_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.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) self.core_manager.events_manager.tribler_shutdown_signal.connect(self.on_tribler_shutdown_state_update) self.core_manager.events_manager.upgrader_tick.connect( lambda text: self.show_status_bar("Upgrading Tribler database: " + text)) self.core_manager.events_manager.upgrader_finished.connect( lambda _: self.hide_status_bar()) self.core_manager.events_manager.received_search_result.connect( self.search_results_page.received_search_result) # 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() 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: """ 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_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 } 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, six.text_type): 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 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", 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): 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['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.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") 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): 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.encode('utf-8')) 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() if not self.add_torrent_url_dialog_active: 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() 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) 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.discovered_channels_list.setFocus() 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.raise_window() self.left_menu_button_downloads.setChecked(True) 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.stackedWidget.setCurrentIndex(PAGE_SUBSCRIBED_CHANNELS) self.subscribed_channels_page.load_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_clicked(self, channel_info): self.channel_page.initialize_with_channel(channel_info) self.navigation_stack.append(self.stackedWidget.currentIndex()) self.stackedWidget.setCurrentIndex(PAGE_CHANNEL_DETAILS) def on_page_back_clicked(self): try: prev_page = self.navigation_stack.pop() self.stackedWidget.setCurrentIndex(prev_page) 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 max_height = self.home_page_table_view.height() / 3 - 4 cell_height = min(cell_width / 2 + 60, max_height) 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.window().force_shutdown_btn.show() self.delete_tray_icon() self.show_loading_screen() self.hide_status_bar() self.loading_text_label.setText("Shutting down...") if self.debug_window: self.debug_window.setHidden(True) 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() def on_tribler_shutdown_state_update(self, state): self.loading_text_label.setText(state)
def __init__(self): """Init class.""" super(MainWindow, self).__init__() self.statusBar().showMessage(getoutput(SYNCTHING + ' --version')) self.setWindowTitle(__doc__.strip().capitalize()) self.setMinimumSize(640, 480) self.setMaximumSize(1280, 1024) self.resize(self.minimumSize()) self.setWindowIcon(QIcon.fromTheme("text-x-python")) self.center() self.view = QWebView(self) self.view.loadFinished.connect(self.finishLoading) self.view.page().linkHovered.connect( lambda link_txt: self.statusBar().showMessage(link_txt[:99], 3000)) QShortcut("Ctrl++", self, activated=lambda: self.view.setZoomFactor(self.view.zoomFactor() + 0.2)) QShortcut("Ctrl+-", self, activated=lambda: self.view.setZoomFactor(self.view.zoomFactor() - 0.2)) QShortcut("Ctrl+0", self, activated=lambda: self.view.setZoomFactor(1)) QShortcut("Ctrl+q", self, activated=lambda: self.close()) self.menuBar().addMenu("File").addAction("Exit", lambda: self.close()) syncMenu = self.menuBar().addMenu("Sync") syncMenu.addAction("Stop Syncronization", lambda: self.process.kill()) syncMenu.addAction("Start Syncronization", lambda: self.run()) viewMenu = self.menuBar().addMenu("View") viewMenu.addAction( "Zoom In", lambda: self.view.setZoomFactor(self.view.zoomFactor() + .2)) viewMenu.addAction( "Zoom Out", lambda: self.view.setZoomFactor(self.view.zoomFactor() - .2)) viewMenu.addAction( "Zoom To...", lambda: self.view.setZoomFactor(QInputDialog.getInt( self, __doc__, "<b>Zoom factor ?:", 1, 1, 9)[0])) viewMenu.addAction("Zoom Reset", lambda: self.view.setZoomFactor(1)) viewMenu.addSeparator() viewMenu.addAction("Page Source", self.viewSource) viewMenu.addAction("Open Web", lambda: open_new_tab(URL)) windowMenu = self.menuBar().addMenu("&Window") windowMenu.addAction("Minimize", lambda: self.showMinimized()) windowMenu.addAction("Maximize", lambda: self.showMaximized()) windowMenu.addAction("Restore", lambda: self.showNormal()) 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()) windowMenu.addAction("Fullscreen", lambda: self.showFullScreen()) windowMenu.addSeparator() windowMenu.addAction("Increase size", lambda: self.resize( self.size().width() * 1.2, self.size().height() * 1.2)) windowMenu.addAction("Decrease size", lambda: self.resize( self.size().width() // 1.2, self.size().height() // 1.2)) windowMenu.addAction("Minimum size", lambda: self.resize(self.minimumSize())) windowMenu.addAction("Maximum size", lambda: self.resize(self.maximumSize())) windowMenu.addAction("Horizontal Wide", lambda: self.resize( self.maximumSize().width(), self.minimumSize().height())) windowMenu.addAction("Vertical Tall", lambda: self.resize( self.minimumSize().width(), self.maximumSize().height())) windowMenu.addSeparator() windowMenu.addAction("Disable Resize", lambda: self.setFixedSize(self.size())) helpMenu = self.menuBar().addMenu("&Help") helpMenu.addAction("Support Forum", lambda: open_new_tab(HELP_URL_0)) helpMenu.addAction("Lastest Release", lambda: open_new_tab(HELP_URL_1)) helpMenu.addAction("Documentation", lambda: open_new_tab(HELP_URL_2)) helpMenu.addAction("Bugs", lambda: open_new_tab(HELP_URL_3)) helpMenu.addAction("Source Code", lambda: open_new_tab(HELP_URL_4)) helpMenu.addSeparator() helpMenu.addAction("About Qt 5", lambda: QMessageBox.aboutQt(self)) helpMenu.addAction("About Python 3", lambda: open_new_tab('https://www.python.org')) helpMenu.addAction("About" + __doc__, lambda: QMessageBox.about(self, __doc__, HELPMSG)) helpMenu.addSeparator() helpMenu.addAction("Keyboard Shortcuts", lambda: QMessageBox.information(self, __doc__, SHORTCUTS)) helpMenu.addAction("View GitHub Repo", lambda: open_new_tab(__url__)) if not sys.platform.startswith("win"): helpMenu.addAction("Show Source Code", lambda: call( ('xdg-open ' if sys.platform.startswith("linux") else 'open ') + __file__, shell=True)) helpMenu.addSeparator() helpMenu.addAction("Check Updates", lambda: self.check_for_updates()) # process self.process = QProcess() self.process.error.connect(self._process_failed) # backend options self.chrt = QCheckBox("Smooth CPU ", checked=True) self.ionice = QCheckBox("Smooth HDD ", checked=True) self.chrt.setToolTip("Use Smooth CPUs priority (recommended)") self.ionice.setToolTip("Use Smooth HDDs priority (recommended)") self.chrt.setStatusTip(self.chrt.toolTip()) self.ionice.setStatusTip(self.ionice.toolTip()) # main toolbar self.toolbar = self.addToolBar("SyncthinGUI Toolbar") self.toolbar.addAction(QIcon.fromTheme("media-playback-stop"), "Stop Sync", lambda: self.process.kill()) self.toolbar.addAction(QIcon.fromTheme("media-playback-start"), "Restart Sync", lambda: self.run()) self.toolbar.addSeparator() self.toolbar.addWidget(self.chrt) self.toolbar.addWidget(self.ionice) self.setCentralWidget(self.view) # Tray Icon tray = QSystemTrayIcon(QIcon.fromTheme("text-x-python"), self) tray.setToolTip(__doc__.strip().capitalize()) traymenu = QMenu(self) traymenu.addAction(__doc__).setDisabled(True) traymenu.addSeparator() traymenu.addAction("Stop Sync", lambda: self.process.kill()) traymenu.addAction("Restart Sync", lambda: self.run()) traymenu.addSeparator() traymenu.addAction("Show", lambda: self.show_gui()) traymenu.addAction("Hide", lambda: self.hide()) traymenu.addSeparator() traymenu.addAction("Open Web", lambda: open_new_tab(URL)) traymenu.addAction("Quit All", lambda: self.close()) tray.setContextMenu(traymenu) tray.show() self.run()
class MainWindow(Ui_MainWindow, QMainWindow): def __init__(self): QMainWindow.__init__(self) Ui_MainWindow.setupUi(self, self) self.playlistDrop.dragEnterEvent = self.playlistDragEnterEvent self.playlistDrop.dropEvent = self.playlistDropEvent self.playlistModel = PlaylistModel() self.playlistTable.setModel(self.playlistModel) self.playlistTable.customContextMenuRequested.connect(self.playlistContextMenu) self.playlistTable.doubleClicked.connect(self.onPlaylistDoubleClick) self.playback = PlaybackWidget(self) self.playbackLayout.addWidget(self.playback) self.playback.playButton.clicked.connect(self.onPlaySelected) self.playback.nextButton.clicked.connect(self.onNextClicked) self.playback.prevButton.clicked.connect(self.onPrevClicked) self.libraryDock.setTitleBarWidget(QWidget()) self.playlistsDock.setTitleBarWidget(QWidget()) self.toggleLibraryButton.clicked.connect(self.onToggleLibrary) self.togglePlaylistsButton.clicked.connect(self.onTogglePlaylists) self.library = LibraryWidget(self) self.libraryDock.setWidget(self.library) self.libraryDock.hide() self.library.itemsActivated.connect(self.onLibraryItemActivated) self.playlists = PlaylistsWidget(self) self.playlistsDock.setWidget(self.playlists) self.playlistsDock.hide() self.playlists.loadPlaylist.connect(self.onPlaylistLoad) self.dockState = 0 self.settings = QSettings('txplaya', 'txplaya') if u'geometry/main' in self.settings.allKeys(): self.setGeometry(self.settings.value(u'geometry/main')) for col in range(self.playlistModel.columnCount()): width = self.settings.value(u'geometry/playlist/col/%d' % col) self.playlistTable.setColumnWidth(col, int(width)) dockState = int(self.settings.value(u'geometry/dock/state')) self.dockShow(dockState) self.systemTray = QSystemTrayIcon(self.windowIcon()) self.systemTray.setToolTip('Playa') self.systemTray.show() self.systemTray.activated.connect(self.systemTrayToggle) systemTrayMenu = QMenu() systemTrayMenu.addAction(self.restore) systemTrayMenu.addAction(self.quit) self.systemTray.setContextMenu(systemTrayMenu) self.restore.triggered.connect(self.restoreWindow) self.quit.triggered.connect(self.quitEvent) self.quitButton.clicked.connect(self.quitEvent) self.quitFlag = False # keyboard shortcuts focusLibraryShortcut = QShortcut(QKeySequence('Ctrl+F'), self) focusLibraryShortcut.activated.connect(self.onFocusLibrary) deleteTrackShortcut = QShortcut(QKeySequence('Del'), self.playlistTable) deleteTrackShortcut.setContext(Qt.WidgetShortcut) deleteTrackShortcut.activated.connect(self.onDeleteTrack) togglePlaybackShortcut = QShortcut(QKeySequence('Space'), self) togglePlaybackShortcut.activated.connect(self.onTogglePlayback) startShortcut = QShortcut(QKeySequence(Qt.Key_Return), self.playlistTable) startShortcut.setContext(Qt.WidgetShortcut) startShortcut.activated.connect(self.onPlaySelected) undoShortcut = QShortcut(QKeySequence('Ctrl+Z'), self) undoShortcut.activated.connect(self.onPlaylistUndo) redoShortcut = QShortcut(QKeySequence('Ctrl+Shift+Z'), self) redoShortcut.activated.connect(self.onPlaylistRedo) saveShortcut = QShortcut(QKeySequence('Ctrl+S'), self) saveShortcut.activated.connect(self.onPlaylistSave) self.infoStreamStart() QTimer.singleShot(200, self.fetchLibrary) def infoStreamStart(self): self.infoStream = QInfoStream() self.infoStream.trackStarted.connect(self.playback.trackStarted) self.infoStream.trackStarted.connect(self.playlistModel.trackActivated) self.infoStream.playbackFinished.connect(self.playback.finished) self.infoStream.playbackFinished.connect(self.playlistModel.trackActivated) self.infoStream.playbackPaused.connect(self.playback.paused) self.infoStream.playlistChanged.connect(self.onPlaylistChanged) self.infoStream.disconnected.connect(self.reconnectDialog) self.infoStream.timerUpdated.connect(self.playback.timerUpdated) self.infoStream.playlistRegistryUpdated.connect(self.playlistRegistryUpdated) def fetchLibrary(self): from txplayagui.client import getLibrary onHttpResponse(getLibrary(), self.onLibraryLoaded) def playlistDragEnterEvent(self, event): self._playlistDragDropHandle(event, isDropped=False) def playlistDropEvent(self, event): self._playlistDragDropHandle(event, isDropped=True) def _playlistDragDropHandle(self, event, isDropped): from txplayagui.client import moveTrack, libraryInsert mimeData = event.mimeData() # get row rowPosition = event.pos().y() - self.playlistTable.rowHeight(0) rowTarget = self.playlistTable.rowAt(rowPosition) if rowTarget == -1: # new row rowTarget = self.playlistModel.rowCount() if mimeData.hasUrls(): urls = mimeData.urls() if len(urls) > 0: url = urls[0] if url.isLocalFile(): if not isDropped: event.acceptProposedAction() return # file dropped filepath = url.toLocalFile() from txplayagui.client import insert _ = insert(filepath, rowTarget) return # no urls or not local file if not mimeData.hasText(): return try: data = unwrapMime(mimeData) except ValueError: # invalid data passed return # check for proper flag source = data.get('source') if source not in ('playlist', 'library'): return if not isDropped: # drag entered event.acceptProposedAction() return if source == 'playlist': rowSource = data['row'] moveTrack(rowSource, rowTarget) elif source == 'library': hashes = [item['hash'] for item in data['items']] libraryInsert(hashes, position=rowTarget) def onLibraryLoaded(self, response): try: data = json.loads(response.data) self.library.rescanFinished(data['library']) if 'msg' in data: print data['msg'] except Exception, err: print 'Library load error:', repr(err)
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 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 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.panelAction = QAction( QCoreApplication.translate("Tray context menu", "Toggle Panel", "Menu entry"), self) 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.panelAction.setIcon(QIcon(':/panel')) 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.panelAction) 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.panelAction.triggered.connect(self.showpanel) 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): 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.forecast_data, self.dayforecast_data, self.json_data_bool, 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'): 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['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) 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 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') 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> 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 MainWindow(QMainWindow): def __init__(self): super(MainWindow, self).__init__() def createUI(self): self.home = os.getenv("HOME") # vlc player init self.instance = vlc.Instance('-q') # self.instance = vlc.Instance('-q --network-caching=1000') self.mediaplayer = self.instance.media_player_new() # self.mediaplayer.set_xwindow(self.winId()) # Main window settings self.gridLayout = QGridLayout(self) self.gridLayout.setObjectName("gridLayout") self.videoFrame = QFrame(self) self.videoFrame.setObjectName("videoFrame") self.gridLayout.addWidget(self.videoFrame, 0, 0, 1, 1) self.mediaplayer.set_xwindow(self.winId()) # self.mediaplayer.set_xwindow(self.videoFrame.winId()) self.setWindowIcon( QIcon(scriptDir + os.path.sep + 'pics' + os.path.sep + 'logo.png')) self.resize(cfg.value('Width', 456, type=int), cfg.value('Height', 256, type=int)) self.setGeometry( cfg.value('Left', 456, type=int) + WINDOW_DECORATION_WIDTH_BORDER, cfg.value('Top', 256, type=int) + WINDOW_DECORATION_HEIGHT_TITLE, cfg.value('Width', 456, type=int), cfg.value('Height', 256, type=int)) pal = self.palette() pal.setColor(self.backgroundRole(), Qt.blue) self.setPalette(pal) # self.setAutoFillBackground(True) self.currentCursor = self.cursor() # Save status audio mute self.AudioMuteOnStart = self.mediaplayer.audio_get_mute() self.AudioVolumeOnStart = self.mediaplayer.audio_get_volume() self.Volume = cfg.value('Volume', 80, type=int) # Registered DBUS service DBUSName = 'tv.ok' DBUSConn = QDBusConnection.connectToBus(QDBusConnection.SessionBus, DBUSName) DBUSConn.registerService(DBUSName) DBUSConn.registerObject("/", self, QDBusConnection.ExportAllContents) # Timer 1 second init. Once second call function t1secEvent self.t1sec = QTimer(self) self.t1sec.timeout.connect(self.t1secEvent) self.t1sec.start(1000) # Select channel saved previous run self.chNum = cfg.value('Channel', 1, type=int) self.chPrev = self.chNum + 1 self.chChange() self.trayIcon = QSystemTrayIcon() self.trayIcon.setToolTip('TVOK Python') self.trayIcon.activated.connect(self.ToggleMute) self.swapIcon() self.selectChannel = '' self.tChSelect = QTimer(self) self.tChSelect.timeout.connect(self.tChSelectTimeout) # self.label = QLabel(self) # self.label.setText("<font color='yellow'>TEST</font>") def osdView(self, mess): # Send OSD # If DBUS daemon org.kochkin.okindd is running dbus_interface = QDBusInterface("org.kochkin.okindd", "/Text") if dbus_interface.isValid(): dbus_interface.call('printText', 'Tvok', mess, 5000) @pyqtSlot(int) def channelNum(self, digit): if (digit >= 0) and (digit <= 9): self.selectChannel = self.selectChannel + str(digit) if int(self.selectChannel) > len(pl): self.selectChannel = self.selectChannel[:-1] if int(self.selectChannel) < 1: self.selectChannel = self.selectChannel[:-1] self.osdView(self.selectChannel + ': ' + pl[int(self.selectChannel) - 1][0]) self.tChSelect.start(2000) @pyqtSlot() def tChSelectTimeout(self): self.tChSelect.stop() self.chNum = int(self.selectChannel) self.selectChannel = '' self.chChange() def swapIcon(self): picture = scriptDir + os.path.sep + 'pics' + os.path.sep + 'din-on.png' if not self.mute(): picture = scriptDir + os.path.sep + 'pics' + os.path.sep + 'din-off.png' self.trayIcon.setIcon(QIcon(picture)) self.trayIcon.show() @pyqtSlot(result=bool) def mute(self): return self.mediaplayer.audio_get_mute() @pyqtSlot(result=int) def GetChannelNum(self): return self.chNum @pyqtSlot(result=str) def GetChannel(self): return pl[self.chNum - 1][0] @pyqtSlot(result=int) def GetVolume(self): return self.mediaplayer.audio_get_volume() @pyqtSlot() def VolumeIncrease(self): self.mediaplayer.audio_set_volume(self.mediaplayer.audio_get_volume() + VOLUME_CHANGE) cfg.setValue('Volume', self.mediaplayer.audio_get_volume()) @pyqtSlot() def VolumeDecrease(self): self.mediaplayer.audio_set_volume(self.mediaplayer.audio_get_volume() - VOLUME_CHANGE) cfg.setValue('Volume', self.mediaplayer.audio_get_volume()) # Once second def t1secEvent(self): if self.isFullScreen(): self.setCursor(Qt.BlankCursor) @pyqtSlot() def ToggleMute(self): self.mediaplayer.audio_set_mute(not self.mediaplayer.audio_get_mute()) self.swapIcon() @pyqtSlot() def ChannelNext(self): self.chNum += 1 self.chChange() @pyqtSlot() def ChannelPrev(self): self.chNum -= 1 self.chChange() # On mouse wheel change def wheelEvent(self, event): if event.angleDelta().y() > 0: self.ChannelNext() if event.angleDelta().y() < 0: self.ChannelPrev() @pyqtSlot() def ChannelRestart(self): self.chChange() # Stop current channel and start chNum channel def chChange(self): if self.chNum != self.chPrev: if self.chNum > len(pl): self.chNum = 1 if self.chNum < 1: self.chNum = len(pl) self.setWindowTitle(str(self.chNum) + '. ' + pl[self.chNum - 1][0]) self.osdView(str(self.chNum) + ': ' + pl[self.chNum - 1][0]) self.mediaplayer.stop() self.media = self.instance.media_new(pl[self.chNum - 1][1]) self.mediaplayer.set_media(self.media) playerError = self.mediaplayer.play() if playerError != 0: sys.exit() cfg.setValue('Channel', self.chNum) cfg.setValue('Volume', self.mediaplayer.audio_get_volume()) self.chPrev = self.chNum # If double click mouse - toggle full screen def mouseDoubleClickEvent(self, event): self.ToggleFullScreen() @pyqtSlot() def ToggleFullScreen(self): if self.isFullScreen(): self.showNormal() self.setCursor(self.currentCursor) else: self.showFullScreen() self.setCursor(Qt.BlankCursor) # Mouse pressed for context menu def contextMenuEvent(self, event): menu = QMenu(self) # Fill channels index = 0 for chs in pl: action = menu.addAction(chs[0]) if index == self.chNum - 1: menu.setActiveAction(action) print(index) index += 1 menu.addSeparator() quitAction = menu.addAction(self.tr("Quit")) action = menu.exec_(self.mapToGlobal(event.pos())) if action: value_index = 1 for value in pl: if value[0] == action.iconText(): if self.chNum != value_index: self.chNum = value_index self.chChange() break else: value_index += 1 if action == quitAction: self.close() def closeEvent(self, event): self.mediaplayer.stop() if not self.isFullScreen(): cfg.setValue('Left', self.x()) cfg.setValue('Top', self.y()) cfg.setValue('Width', self.width()) cfg.setValue('Height', self.height()) cfg.setValue('Volume', self.mediaplayer.audio_get_volume()) cfg.sync() self.mediaplayer.audio_set_mute(self.AudioMuteOnStart) self.mediaplayer.audio_set_volume(self.AudioVolumeOnStart) # self.trayIcon.close() exit()
class MainApp(QMainWindow): tray_icon = None active = False def __init__(self, parent=None): super(MainApp, self).__init__(parent) # Init notification notify2.init('webcam-manager') # Init tray icon self.tray_icon = QSystemTrayIcon(QIcon.fromTheme('camera-web'), parent) self.initTrayIcon() self.tray_icon.show() def initTrayIcon(self): # Menu actions toogle_action = QAction(self.tr('&Toogle'), self) toogle_action.triggered.connect(self.onToogle) about_action = QAction(self.tr('&About'), self) about_action.setIcon(QIcon.fromTheme("help-about")) about_action.triggered.connect(self.onAbout) quit_action = QAction(self.tr('&Exit'), self) quit_action.setIcon(QIcon.fromTheme("application-exit")) quit_action.triggered.connect(self.onQuit) tray_menu = QMenu() tray_menu.addAction(toogle_action) tray_menu.addSeparator() tray_menu.addAction(about_action) tray_menu.addAction(quit_action) self.tray_icon.setContextMenu(tray_menu) self.tray_icon.activated.connect(self.onToogle) output = int(self.execCommand('lsmod | grep uvcvideo | wc -l').split()[0]) if output > 0: self.updateTrayIcon(True) else: self.updateTrayIcon(False) def onToogle(self, widget): if self.active: self.disable() else: self.enable() def onQuit(self, widget): qApp.quit() def onAbout(self, widget): dialog = QDialog(self) aboutText = self.tr("""<p>A simple applet for enable/disable webcams.</p> <p>Website: <a href="https://github.com/abbarrasa/openbox"> https://github.com/abbarrasa/openbox</a></p> <p>Based in <a href="https://extensions.gnome.org/extension/1477/webcam-manager/">Webcam Manager</a>.</p> <p>If you want to report a dysfunction or a suggestion, feel free to open an issue in <a href="https://github.com/abbarrasa/openbox/issues"> github</a>.""") creditsText = self.tr("""(c) 2018 Alberto Buitrago <%s>""") % base64.b64decode('YWJiYXJyYXNhQGdtYWlsLmNvbQ==').decode('utf-8') licenseText = self.tr("""<p>This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.</p> <p>This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.</p> <p>You should have received a copy of the GNU General Public License along with this program. If not, see <a href="https://www.gnu.org/licenses/gpl-3.0.html"> GNU General Public License version 3</a>.</p>""") layout = QVBoxLayout() titleLayout = QHBoxLayout() titleLabel = QLabel('<font size="4"><b>{0} {1}</b></font>'.format('Webcam Manager', VERSION)) contentsLayout = QHBoxLayout() aboutBrowser = QTextBrowser() aboutBrowser.append(aboutText) aboutBrowser.setOpenExternalLinks(True) creditsBrowser = QTextBrowser() creditsBrowser.append(creditsText) creditsBrowser.setOpenExternalLinks(True) licenseBrowser = QTextBrowser() licenseBrowser.append(licenseText) licenseBrowser.setOpenExternalLinks(True) TabWidget = QTabWidget() TabWidget.addTab(aboutBrowser, self.tr('About')) TabWidget.addTab(creditsBrowser, self.tr('Contributors')) TabWidget.addTab(licenseBrowser, self.tr('License')) aboutBrowser.moveCursor(QTextCursor.Start) creditsBrowser.moveCursor(QTextCursor.Start) licenseBrowser.moveCursor(QTextCursor.Start) icon = QIcon.fromTheme('camera-web') pixmap = icon.pixmap(QSize(64, 64)) imageLabel = QLabel() imageLabel.setPixmap(pixmap) titleLayout.addWidget(imageLabel) titleLayout.addWidget(titleLabel) titleLayout.addStretch() contentsLayout.addWidget(TabWidget) buttonLayout = QHBoxLayout() buttonBox = QDialogButtonBox(QDialogButtonBox.Ok) buttonLayout.addWidget(buttonBox) layout.addLayout(titleLayout) layout.addLayout(contentsLayout) layout.addLayout(buttonLayout) buttonBox.clicked.connect(dialog.accept) dialog.setLayout(layout) dialog.setMinimumSize(QSize(480, 400)) dialog.setWindowTitle(self.tr('About Webcam Manager')) dialog.setWindowIcon(QIcon.fromTheme('help-about')) dialog.show() def enable(self): tool = self.getGUISudo() cmd = '%s modprobe -a uvcvideo' % tool self.execCommand(cmd) output = int(self.execCommand('lsmod | grep uvcvideo | wc -l').split()[0]) if output > 0: self.updateTrayIcon(True) self.showNotification('Webcam enabled!', 'Webcam is turned on and ready to use') def disable(self): tool = self.getGUISudo() cmd = '%s modprobe -r uvcvideo' % tool self.execCommand(cmd) output = int(self.execCommand('lsmod | grep uvcvideo | wc -l').split()[0]) if output == 0: self.updateTrayIcon(False) self.showNotification('Webcam disabled!', 'Webcam is turned off') def getGUISudo(self): tools = ['kdesu', 'lxqt-sudo', 'gksu', 'gksudo', 'pkexec', 'sudo'] for tool in tools: if distutils.spawn.find_executable(tool) is not None: return tool def execCommand(self, cmd): try: output = subprocess.check_output(cmd, shell=True) return output except subprocess.CalledProcessError as e: self.showNotification('Error!', e.output) def updateTrayIcon(self, active): self.active = active if self.active: self.tray_icon.setIcon(QIcon.fromTheme('camera-on')) self.tray_icon.setToolTip('Webcam is enabled') else: self.tray_icon.setIcon(QIcon.fromTheme('camera-off')) self.tray_icon.setToolTip('Webcam is disabled') def showNotification(self, title, message): n = notify2.Notification(title, message, 'camera-web') n.show()
class BlenderLauncher(QMainWindow, BaseWindow, Ui_MainWindow): def __init__(self, app): super().__init__() self.setupUi(self) # Server self.server = QLocalServer() self.server.listen("blender-launcher-server") self.server.newConnection.connect(self.new_connection) # Global scope self.app = app self.favorite = None self.status = "None" self.app_state = AppState.IDLE self.cashed_builds = [] self.manager = PoolManager(200) self.timer = None # Setup window self.setWindowTitle("Blender Launcher") self.app.setWindowIcon(QIcon(":resources/icons/tray.ico")) # Setup font QFontDatabase.addApplicationFont( ":/resources/fonts/OpenSans-SemiBold.ttf") self.font = QFont("Open Sans SemiBold", 10) self.font.setHintingPreference(QFont.PreferNoHinting) self.app.setFont(self.font) # Setup style file = QFile(":/resources/styles/global.qss") file.open(QFile.ReadOnly | QFile.Text) self.style_sheet = QTextStream(file).readAll() self.app.setStyleSheet(self.style_sheet) # Check library folder if is_library_folder_valid() is False: self.dlg = DialogWindow( self, title="Information", text="First, choose where Blender\nbuilds will be stored", accept_text="Continue", cancel_text=None, icon=DialogIcon.INFO) self.dlg.accepted.connect(self.set_library_folder) else: self.draw() def set_library_folder(self): library_folder = Path.cwd().as_posix() new_library_folder = QFileDialog.getExistingDirectory( self, "Select Library Folder", library_folder) if new_library_folder: set_library_folder(new_library_folder) self.draw() def draw(self): self.HeaderLayout = QHBoxLayout() self.HeaderLayout.setContentsMargins(1, 1, 1, 0) self.HeaderLayout.setSpacing(0) self.CentralLayout.addLayout(self.HeaderLayout) self.SettingsButton = \ QPushButton(QIcon(":resources/icons/settings.svg"), "") self.SettingsButton.setIconSize(QSize(20, 20)) self.SettingsButton.setFixedSize(36, 32) self.WikiButton = \ QPushButton(QIcon(":resources/icons/wiki.svg"), "") self.WikiButton.setIconSize(QSize(20, 20)) self.WikiButton.setFixedSize(36, 32) self.MinimizeButton = \ QPushButton(QIcon(":resources/icons/minimize.svg"), "") self.MinimizeButton.setIconSize(QSize(20, 20)) self.MinimizeButton.setFixedSize(36, 32) self.CloseButton = \ QPushButton(QIcon(":resources/icons/close.svg"), "") self.CloseButton.setIconSize(QSize(20, 20)) self.CloseButton.setFixedSize(36, 32) self.HeaderLabel = QLabel("Blender Launcher") self.HeaderLabel.setAlignment(Qt.AlignCenter) self.HeaderLayout.addWidget(self.SettingsButton, 0, Qt.AlignLeft) self.HeaderLayout.addWidget(self.WikiButton, 0, Qt.AlignLeft) self.HeaderLayout.addWidget(self.HeaderLabel, 1) self.HeaderLayout.addWidget(self.MinimizeButton, 0, Qt.AlignRight) self.HeaderLayout.addWidget(self.CloseButton, 0, Qt.AlignRight) self.SettingsButton.setProperty("HeaderButton", True) self.WikiButton.setProperty("HeaderButton", True) self.MinimizeButton.setProperty("HeaderButton", True) self.CloseButton.setProperty("HeaderButton", True) self.CloseButton.setProperty("CloseButton", True) # Tab layout self.TabWidget = QTabWidget() self.CentralLayout.addWidget(self.TabWidget) self.LibraryTab = QWidget() self.LibraryTabLayout = QVBoxLayout() self.LibraryTabLayout.setContentsMargins(0, 0, 0, 0) self.LibraryTab.setLayout(self.LibraryTabLayout) self.TabWidget.addTab(self.LibraryTab, "Library") self.DownloadsTab = QWidget() self.DownloadsTabLayout = QVBoxLayout() self.DownloadsTabLayout.setContentsMargins(0, 0, 0, 0) self.DownloadsTab.setLayout(self.DownloadsTabLayout) self.TabWidget.addTab(self.DownloadsTab, "Downloads") self.LibraryToolBox = BaseToolBoxWidget(self) self.LibraryStableListWidget = \ self.LibraryToolBox.add_list_widget("Stable Releases") self.LibraryDailyListWidget = \ self.LibraryToolBox.add_list_widget("Daily Builds") self.LibraryExperimentalListWidget = \ self.LibraryToolBox.add_list_widget("Experimental Branches") self.LibraryCustomListWidget = \ self.LibraryToolBox.add_list_widget("Custom Builds") self.LibraryTab.layout().addWidget(self.LibraryToolBox) self.DownloadsToolBox = BaseToolBoxWidget(self) self.DownloadsStableListWidget = \ self.DownloadsToolBox.add_list_widget("Stable Releases") self.DownloadsDailyListWidget = \ self.DownloadsToolBox.add_list_widget("Daily Builds") self.DownloadsExperimentalListWidget = \ self.DownloadsToolBox.add_list_widget("Experimental Branches") self.DownloadsTab.layout().addWidget(self.DownloadsToolBox) self.LibraryToolBox.setCurrentIndex(get_default_library_page()) # Connect buttons self.SettingsButton.clicked.connect(self.show_settings_window) self.WikiButton.clicked.connect(lambda: webbrowser.open( "https://github.com/DotBow/Blender-Launcher/wiki")) self.MinimizeButton.clicked.connect(self.showMinimized) self.CloseButton.clicked.connect(self.close) self.StatusBar.setFont(self.font) self.statusbarLabel = QLabel() self.statusbarVersion = QLabel(self.app.applicationVersion()) self.StatusBar.addPermanentWidget(self.statusbarLabel, 1) self.StatusBar.addPermanentWidget(self.statusbarVersion) # Draw library self.draw_library() # Setup tray icon context Menu quit_action = QAction("Quit", self) quit_action.triggered.connect(self.quit) hide_action = QAction("Hide", self) hide_action.triggered.connect(self.hide) show_action = QAction("Show", self) show_action.triggered.connect(self._show) launch_favorite_action = QAction( QIcon(":resources/icons/favorite.svg"), "Blender", self) launch_favorite_action.triggered.connect(self.launch_favorite) tray_menu = QMenu() tray_menu.setFont(self.font) tray_menu.addAction(launch_favorite_action) tray_menu.addAction(show_action) tray_menu.addAction(hide_action) tray_menu.addAction(quit_action) # Setup tray icon self.tray_icon = QSystemTrayIcon(self) self.tray_icon.setIcon(QIcon(":resources/icons/tray.ico")) self.tray_icon.setToolTip("Blender Launcher") self.tray_icon.activated.connect(self.tray_icon_activated) self.tray_icon.setContextMenu(tray_menu) self.tray_icon.show() # Forse style update self.style().unpolish(self.app) self.style().polish(self.app) # Show window if get_launch_minimized_to_tray() is False: self._show() def _show(self): self.activateWindow() self.show() self.set_status() def launch_favorite(self): try: self.favorite.launch() except Exception: self.dlg = DialogWindow( self, text="Favorite build not found!", accept_text="OK", cancel_text=None) def tray_icon_activated(self, reason): if reason == QSystemTrayIcon.Trigger: self._show() elif reason == QSystemTrayIcon.MiddleClick: self.launch_favorite() def quit(self): download_widgets = [] download_widgets.extend(self.DownloadsStableListWidget.items()) download_widgets.extend(self.DownloadsDailyListWidget.items()) download_widgets.extend(self.DownloadsExperimentalListWidget.items()) for widget in download_widgets: if widget.state == DownloadState.DOWNLOADING: self.dlg = DialogWindow( self, title="Warning", text="Download task in progress!<br>\ Are you sure you want to quit?", accept_text="Yes", cancel_text="No", icon=DialogIcon.WARNING) self.dlg.accepted.connect(self.quit2) return self.quit2() def quit2(self): if self.timer is not None: self.timer.cancel() self.tray_icon.hide() self.app.quit() def draw_library(self, clear=False): self.set_status("Reading local builds") if clear: self.timer.cancel() self.scraper.quit() self.DownloadsStableListWidget.clear() self.DownloadsDailyListWidget.clear() self.DownloadsExperimentalListWidget.clear() self.favorite = None self.LibraryStableListWidget.clear() self.LibraryDailyListWidget.clear() self.LibraryExperimentalListWidget.clear() self.library_drawer = LibraryDrawer(self) self.library_drawer.build_found.connect(self.draw_to_library) self.library_drawer.finished.connect(self.draw_downloads) self.library_drawer.start() def draw_downloads(self): self.app_state = AppState.CHECKINGBUILDS self.set_status("Checking for new builds") self.scraper = Scraper(self, self.manager) self.scraper.links.connect(self.draw_new_builds) self.scraper.error.connect(self.connection_error) self.scraper.start() def connection_error(self): set_locale() utcnow = strftime(('%H:%M'), localtime()) self.set_status("Connection Error at " + utcnow) self.app_state = AppState.IDLE self.timer = threading.Timer(600.0, self.draw_downloads) self.timer.start() def draw_new_builds(self, builds): self.cashed_builds.clear() self.cashed_builds.extend(builds) library_widgets = [] download_widgets = [] library_widgets.extend(self.LibraryStableListWidget.items()) library_widgets.extend(self.LibraryDailyListWidget.items()) library_widgets.extend(self.LibraryExperimentalListWidget.items()) download_widgets.extend(self.DownloadsStableListWidget.items()) download_widgets.extend(self.DownloadsDailyListWidget.items()) download_widgets.extend(self.DownloadsExperimentalListWidget.items()) for widget in download_widgets: if widget.build_info in builds: builds.remove(widget.build_info) elif widget.state != DownloadState.DOWNLOADING: widget.destroy() for widget in library_widgets: if widget.build_info in builds: builds.remove(widget.build_info) for build_info in builds: self.draw_to_downloads(build_info) set_locale() utcnow = strftime(('%H:%M'), localtime()) self.set_status("Last check at " + utcnow) self.app_state = AppState.IDLE self.timer = threading.Timer(600.0, self.draw_downloads) self.timer.start() def draw_from_cashed(self, build_info): if self.app_state == AppState.IDLE: if build_info in self.cashed_builds: i = self.cashed_builds.index(build_info) self.draw_to_downloads(self.cashed_builds[i]) def draw_to_downloads(self, build_info): branch = build_info.branch if branch == 'stable': list_widget = self.DownloadsStableListWidget elif branch == 'daily': list_widget = self.DownloadsDailyListWidget else: list_widget = self.DownloadsExperimentalListWidget item = BaseListWidgetItem(build_info.commit_time) widget = DownloadWidget(self, list_widget, item, build_info) item.setSizeHint(widget.sizeHint()) list_widget.addItem(item) list_widget.setItemWidget(item, widget) def draw_to_library(self, path): category = Path(path).parent.name if category == 'stable': list_widget = self.LibraryStableListWidget elif category == 'daily': list_widget = self.LibraryDailyListWidget elif category == 'experimental': list_widget = self.LibraryExperimentalListWidget elif category == 'custom': list_widget = self.LibraryCustomListWidget else: return item = BaseListWidgetItem() widget = LibraryWidget(self, item, path, list_widget) list_widget.insertItem(0, item) list_widget.setItemWidget(item, widget) def set_status(self, status=None): if status is not None: self.status = status self.statusbarLabel.setText("Status: {0}".format(self.status)) def show_settings_window(self): self.settings_window = SettingsWindow(self) def clear_temp(self): temp_folder = Path(get_library_folder()) / ".temp" self.remover = Remover(temp_folder) self.remover.start() def closeEvent(self, event): event.ignore() self.hide() def new_connection(self): self._show()
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)