def show_notification(self): """Show a notification that the Course downloader has completed all downloads """ system_tray_icon = QSystemTrayIcon(self) system_tray_icon.show() text = 'Downloads are completed!' system_tray_icon.showMessage('Course Downloader', text)
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 MainWindow(QMainWindow): def __init__(self, app): QMainWindow.__init__(self) # Config app icon self.app_icon = QtGui.QIcon("assets/icon.png") self.setWindowIcon(self.app_icon) self.tray_icon = QSystemTrayIcon(self.app_icon, self) self.tray_icon.activated.connect(self.tray_icon_event) menu = QMenu(self) quit_action = menu.addAction("Fechar aplicação") quit_action.triggered.connect(app.quit) tray_menu = QMenu() tray_menu.addAction(quit_action) self.tray_icon.setContextMenu(tray_menu) self.tray_icon.show() def tray_icon_event(self, reason): if reason == QSystemTrayIcon.DoubleClick: if not self.isVisible(): self.show() def hide_window_and_tray(self): if self.tray_icon.isVisible(): self.tray_icon.hide() if self.isVisible(): self.hide() # When user clicks on window quit button def closeEvent(self, event): event.ignore() self.hide() self.tray_icon.showMessage("File upload", "Aplicação foi minimizada", QSystemTrayIcon.Information, 1000)
class SystemTray(QWidget): """只有托盘""" def __init__(self, config_list): super().__init__() self.tray = QSystemTrayIcon() self.tray.setIcon(QIcon('icons/app.ico')) self.add_menu(config_list) def add_menu(self, config_list): """托盘菜单""" tray_menu = QMenu() # 添加菜单 for config in config_list: params = config.get('params') sys_name = params.get('name') tray_menu.addAction(OpenAction(params, self)) stop_bat = params.get('stop_bat', None) if stop_bat: tray_menu.addAction(StopAction(sys_name, stop_bat, self)) tray_menu.addAction(ExitAction(self)) self.tray.setContextMenu(tray_menu) def display(self): """icon的值: 0-没有图标 1-是提示 2-是警告 3-是错误""" self.tray.show() self.tray.showMessage(u"启动成功", '请通过右键操作')
class BrowserScreen(QWebEngineView): def __init__(self): QWebEngineView.__init__(self) self.resize(800, 600) self.show() #增加了<script src="qrc:///qtwebchannel/test.js"></script> self.setHtml(""" <script src="qrc:///qtwebchannel/test.js"></script> <script>function message() { return "Clicked!"; }</script> <h1>QtWebKit + Python sample program</h1> <input type="button" value="Click JavaScript!" onClick="alert('[javascript] ' + message())"/> <input type="button" value="Click Python!" onClick="python.alert('[python] ' + python.message())"/> <br /> <iframe src="http://www.so.com/" width="750" height="500" scrolling="no" frameborder="0" align="center"></iframe> """) self.createTrayIcon() self.trayIcon.show() def createTrayIcon(self): self.trayIcon = QSystemTrayIcon(self) self.trayIcon.setIcon(QIcon("images/trash.png")) def showMessage(self, msg): self.trayIcon.showMessage("This is Python", msg, QSystemTrayIcon.MessageIcon(0), 15 * 1000)
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()
class SystemTray(object): # 程序托盘类 def __init__(self, w): self.app = app self.w = w QApplication.setQuitOnLastWindowClosed( False) # 禁止默认的closed方法,只能使用qapp.quit()的方法退出程序 self.w.show() # 不设置显示则为启动最小化到托盘 self.tp = QSystemTrayIcon(self.w) self.initUI() self.run() def initUI(self): # 设置托盘图标 self.tp.setIcon(QIcon('./d.ico')) def quitApp(self): # 退出程序 self.w.show() # w.hide() #设置退出时是否显示主窗口 re = QMessageBox.question(self.w, "提示", "退出系统", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if re == QMessageBox.Yes: self.tp.setVisible(False) # 隐藏托盘控件,托盘图标刷新不及时,提前隐藏 qApp.quit() # 退出程序 def message(self): # 提示信息被点击方法 print("弹出的信息被点击了") def act(self, reason): # 主界面显示方法 # 鼠标点击icon传递的信号会带有一个整形的值,1是表示单击右键,2是双击,3是单击左键,4是用鼠标中键点击 if reason == 2 or reason == 3: self.w.show() def run(self): a1 = QAction('&显示(Show)', triggered=self.w.show) a2 = QAction('&退出(Exit)', triggered=self.quitApp) tpMenu = QMenu() tpMenu.addAction(a1) tpMenu.addAction(a2) self.tp.setContextMenu(tpMenu) self.tp.show() # 不调用show不会显示系统托盘消息,图标隐藏无法调用 # 信息提示 # 参数1:标题 # 参数2:内容 # 参数3:图标(0没有图标 1信息图标 2警告图标 3错误图标),0还是有一个小图标 self.tp.showMessage('Hello', '我藏好了', icon=0) # 绑定提醒信息点击事件 self.tp.messageClicked.connect(self.message) # 绑定托盘菜单点击事件 self.tp.activated.connect(self.act) sys.exit(self.app.exec_()) # 持续对app的连接
class dx_SystemTray(QWidget): dx_SystemTray_Signal = pyqtSignal(str) def __init__(self, parent=None): super(dx_SystemTray, self).__init__(parent) self.tray = QSystemTrayIcon() # 创建QIcon对象,用于设置图标(图片过大会出错) -- 运行python命令的目录必须在文件目录,不然会报错 # self.trayIconPix = QPixmap(16,16) # self.trayIconPix.fill(QColor(100,100,100)) self.trayIconPix = QPixmap("./images/me.png") self.Icon = QIcon(self.trayIconPix) # 设置托盘图标(QIcon图标过大或者出错会导致托盘显示不出来) self.tray.setIcon(self.Icon) # 创建QAction showAction = QAction("&显示", self, triggered=self.showApp) quitAction = QAction("&退出", self, triggered=self.closeApp) # 创建菜单对象 self.trayMenu = QMenu(self) # 将动作对象添加到菜单 self.trayMenu.addAction(showAction) # 增加分割线 self.trayMenu.addSeparator() self.trayMenu.addAction(quitAction) # 将菜单栏加入到右键按钮中 self.tray.setContextMenu(self.trayMenu) self.tray.show() # 显示程序主页面 def showApp(self): self.dx_SystemTray_Signal.emit("show") # 程序退出 def closeApp(self): self.dx_SystemTray_Signal.emit("close") # 显示消息 def showMsg(self, Msg_type, Msg_str): if (Msg_type == 1): showMsgType = QSystemTrayIcon.Information elif (Msg_type == 2): showMsgType = QSystemTrayIcon.Warning elif (Msg_type == 3): showMsgType = QSystemTrayIcon.Critical self.tray.showMessage( "霄哥的神秘工具", # "程序缩小至系统托盘!", Msg_str, Msg_type, 2000)
def show_notification(self) -> None: """Show a notification that the Pomodoro has completed one run, and to take a break. """ system_tray_icon = QSystemTrayIcon(QIcon('src/main/icons/Icon.ico'), self) system_tray_icon.show() text = str(self.time_limit) + \ ' minutes have passed. ' if self.time_limit > 1 else '1 minutes has passed. ' system_tray_icon.showMessage('Time Up!', text + 'Take a break!')
class MainWindow(QMainWindow): """ Сheckbox and system tray icons. Will initialize in the constructor. """ check_box = None tray_icon = None # Override the class constructor def __init__(self): # Be sure to call the super class method QMainWindow.__init__(self) self.setMinimumSize(QSize(480, 80)) # Set sizes self.setWindowTitle("System Tray Application") # Set a title central_widget = QWidget(self) # Create a central widget self.setCentralWidget(central_widget) # Set the central widget grid_layout = QGridLayout(self) # Create a QGridLayout central_widget.setLayout(grid_layout) # Set the layout into the central widget grid_layout.addWidget(QLabel("Application, which can minimize to Tray", self), 0, 0) # Add a checkbox, which will depend on the behavior of the c_stock_program when the window is closed self.check_box = QCheckBox('Minimize to Tray') grid_layout.addWidget(self.check_box, 1, 0) grid_layout.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Expanding), 2, 0) # Init QSystemTrayIcon self.tray_icon = QSystemTrayIcon(self) self.tray_icon.setIcon(self.style().standardIcon(QStyle.SP_ComputerIcon)) ''' Define and add steps to work with the system tray icon show - show window hide - hide window exit - exit from application ''' show_action = QAction("Show", self) quit_action = QAction("Exit", self) hide_action = QAction("Hide", self) show_action.triggered.connect(self.show) hide_action.triggered.connect(self.hide) quit_action.triggered.connect(qApp.quit) tray_menu = QMenu() tray_menu.addAction(show_action) tray_menu.addAction(hide_action) tray_menu.addAction(quit_action) self.tray_icon.setContextMenu(tray_menu) self.tray_icon.show() # Override closeEvent, to intercept the window closing event # The window will be closed only if there is no check mark in the check box def closeEvent(self, event): event.ignore() self.hide() self.tray_icon.showMessage("Tray Program","Application was minimized to Tray",QSystemTrayIcon.Information,2000)
def showMessage(self, title: str, body: str, icon: int = 1, limit: int = 5): tray = QSystemTrayIcon(_pkg.icon_path("logo.png", True)) icon_ = tray.MessageIcon(icon) tray.showMessage(title, body, icon_, limit * 2000) tray.show()
def show_message(title, message, level=0): #QMessageBox.standardIcon( # QStyle.SP_MessageBoxInformation), "Information", # QSystemTrayIcon.Information) print("Running show message") tray_icon = QSystemTrayIcon() tray_icon.setIcon(QIcon(":/images/icons/heart.png")) # icon = tray_icon.Information tray_icon.show() tray_icon.showMessage(title, message)
def toast(title: str = 'Notification', msg: str = 'Here Comes The Message!', delay: int = 2, icon: str = '', msg_icon: str = ''): App = QApplication([]) Tray = QSystemTrayIcon(QIcon(icon)) Tray.show() QTimer.singleShot(delay * 1000, App.exit) Tray.showMessage(title, msg, QIcon(msg_icon), delay * 1000) App.exec_()
class StayAwakeApp(QtWidgets.QMainWindow): def __init__(self): super(StayAwakeApp, self).__init__() self.ui = StayAwake.Ui_MainWindow() self.ui.setupUi(self) self.ui.label_2.setPixmap(QtGui.QPixmap(":icons/32x32.png")) # set logo in app window (workaround for pyinstaller self.ui.toggle.clicked.connect(self.togglePressed) # listener for when toggle button is pressed self.running = False # flag to check if program is on or off self.preventSleep = None # initializing to hold PreventSleep instance self.trayIcon = None # initializing to hold tray icon instance self.setupTray() # setup tray icon and menu # setup tray icon and menu def setupTray(self): self.trayIcon = QSystemTrayIcon(self) # instance of QSystemTrayIcon self.trayIcon.setIcon(QIcon(":icons/StayAwakeOpaque.ico")) # set tray icon image openAction = QAction("Open", self) # "open" right click option in tray quitAction = QAction("Quit", self) # "quit" right click option in tray openAction.triggered.connect(self.show) # open app trigger quitAction.triggered.connect(qApp.quit) # quit app trigger trayMenu = QMenu() # QMenu class instance trayMenu.addAction(openAction) # add open action trayMenu.addAction(quitAction) # add quit action self.trayIcon.setContextMenu(trayMenu) # sets menu actions self.trayIcon.show() # makes tray icon visible # handle toggle button states and calling prevent sleep def togglePressed(self): if self.running: self.ui.toggle.setText("Off") # set toggle button text to "Off" self.running = False # update state self.preventSleep.terminate() # terminate prevent sleep thread else: self.ui.toggle.setText("On") # set toggle button text to "On" self.running = True # update state self.preventSleep = PreventSleep() # create instance of PreventSleep class self.preventSleep.start() # start prevent sleep thread # making "minimize" button minimizes to tray instead of taskbar def changeEvent(self, event): if event.type() == QtCore.QEvent.WindowStateChange: if self.windowState() & QtCore.Qt.WindowMinimized: # if minimize button is pressed event.ignore() # ignore "minimize to taskbar" event self.hide() # hide the window self.trayIcon.show() # show tray icon self.trayIcon.showMessage("Stay Awake", "Stay Awake was minimized to tray.", QSystemTrayIcon.Information) # show tray message popup
class AppWindow(QMainWindow): def __init__(self): super().__init__() self.tray_icon = QSystemTrayIcon(self) self.tray_icon.setIcon(self.style().standardIcon( QStyle.SP_ComputerIcon)) 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) self.tray_icon.setContextMenu(tray_menu) self.tray_icon.show() self.tray_icon.activated.connect(self.hand_tray_activated) self.ui = Control_Panel() self.ui.setupUi(self) self.setWindowFlags(QtCore.Qt.WindowCloseButtonHint | QtCore.Qt.WindowMinimizeButtonHint) self.show() self.ui.th.hide_signal.connect(self.minimize_to_tray) def hand_tray_activated(self, reason): # On double clicking the tray icon the windows is shown again if reason == QSystemTrayIcon.DoubleClick: self.show() def minimize_to_tray(self): # when go button is clicked the app is minimized to tray self.hide() self.tray_icon.showMessage("Hand of The King", "Application was minimized to tray", QSystemTrayIcon.Information, 2000) def closeEvent(self, event): # overriding the closeEvent function of the window to cleanup all open windows and release the camera self.tray_icon.hide() self.ui.application_on = False self.ui.gesture_detector.camera.release() self.ui.hand_window.close() event.accept()
class SystemTray(QWidget): def __init__(self, parent=None): super(SystemTray, self).__init__(parent) self.tray_icon_menu = QMenu(self) self.tray_icon = QSystemTrayIcon(self) self.tray_icon.setContextMenu(self.tray_icon_menu) self.icon_management = IconManagement(self.tray_icon) self.connection_handler = ConnectionHandler(FREQUENCY_CHECK_MS, TIME_OUT_CALL_S, self) self.connection_handler.value_changed.connect(self.internet_connection) self.connection_handler.start() def add_action(self, name, triggered_action): action = QAction(QCoreApplication.translate(trad_context, name), self, triggered=triggered_action) self.tray_icon_menu.addAction(action) def add_separator(self): self.tray_icon_menu.addSeparator() def show(self): super(SystemTray, self).show() self.tray_icon.show() @pyqtSlot() def event_started(self): self.icon_management.start() @pyqtSlot() def event_finished(self): self.icon_management.stop() @pyqtSlot(Exception) def conductor_problem(self, e): self.notify("Demerio", "There was a problem : %s" % (e, )) self.icon_management.conductor_problem() @pyqtSlot(bool) def internet_connection(self, internet_is_ok): if not internet_is_ok: self.notify("Demerio", "Internet connection is lost") self.icon_management.internet_is_ok(internet_is_ok) def notify(self, title, message): self.tray_icon.showMessage(title, message, BAR_NOTIFICATION_TIME)
class SystemTray(QWidget): def __init__(self, parent=None): super(SystemTray, self).__init__(parent) self.tray_icon_menu = QMenu(self) self.tray_icon = QSystemTrayIcon(self) self.tray_icon.setContextMenu(self.tray_icon_menu) self.icon_management = IconManagement(self.tray_icon) self.connection_handler = ConnectionHandler(FREQUENCY_CHECK_MS, TIME_OUT_CALL_S, self) self.connection_handler.value_changed.connect(self.internet_connection) self.connection_handler.start() def add_action(self, name, triggered_action): action = QAction(QCoreApplication.translate(trad_context, name), self, triggered = triggered_action) self.tray_icon_menu.addAction(action) def add_separator(self): self.tray_icon_menu.addSeparator() def show(self): super(SystemTray, self).show() self.tray_icon.show() @pyqtSlot() def event_started(self): self.icon_management.start() @pyqtSlot() def event_finished(self): self.icon_management.stop() @pyqtSlot(Exception) def conductor_problem(self, e): self.notify("Demerio", "There was a problem : %s" % (e,)) self.icon_management.conductor_problem() @pyqtSlot(bool) def internet_connection(self, internet_is_ok): if not internet_is_ok: self.notify("Demerio", "Internet connection is lost") self.icon_management.internet_is_ok(internet_is_ok) def notify(self, title, message): self.tray_icon.showMessage(title, message, BAR_NOTIFICATION_TIME)
class MainWindow(QMainWindow): """ Сheckbox and system tray icons. Will initialize in the constructor. """ check_box = None tray_icon = None # Override the class constructor def __init__(self): # Be sure to call the super class method QMainWindow.__init__(self) self.tray_icon = QSystemTrayIcon(self) self.tray_icon.setIcon(self.style().standardIcon( QStyle.SP_ComputerIcon)) 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.quit) tray_menu = QMenu() tray_menu.addAction(show_action) tray_menu.addAction(hide_action) tray_menu.addAction(quit_action) self.tray_icon.setContextMenu(tray_menu) self.tray_icon.show() def quit(self): self.tray_icon.hide() qApp.quit() # Override closeEvent, to intercept the window closing event # The window will be closed only if there is no check mark in the check box def closeEvent(self, event): event.ignore() self.hide() self.tray_icon.showMessage("Tray Program", "Application was minimized to Tray", QSystemTrayIcon.Information, 2000)
class BaseTray(object): def __init__(self, icon_path): self.tray = QSystemTrayIcon() self.tray.setIcon(QIcon(icon_path)) self.tray_menu = QMenu() self._set_menu() def _set_menu(self): pass def show_message(self, text): icon = self.tray.MessageIcon() self.tray.showMessage('message', text, icon, 1000) def show(self): self.tray.show() def quit(self): self.tray.hide() sys.exit()
def main_gui(): """ A simple PyQt5 GUI application that loads a main window from a designer file and creates a system tray icon with a right-click menu and can generate notifications. :return: """ import sys import pkg_resources import os from PyQt5.QtWidgets import QApplication from PyQt5 import uic app = QApplication(sys.argv) # Or load a UI designer file designer_filepath = pkg_resources.resource_filename( __name__, os.path.join("resources", "main_window.ui")) win2 = uic.loadUi(designer_filepath) win2.show() win2.myLabel.setText('Hello world!') # System tray icon from PyQt5.QtWidgets import QSystemTrayIcon from PyQt5.uic.properties import QtGui from PyQt5.QtWidgets import QAction, QMenu tray_icon = QSystemTrayIcon() icon_path = pkg_resources.resource_filename( __name__, os.path.join("resources", "devdungeon500x500.jpg")) tray_icon.setIcon(QtGui.QIcon(icon_path)) tray_menu = QMenu() exit_action = QAction("Exit") exit_action.triggered.connect(sys.exit) tray_menu.addAction(exit_action) tray_icon.setContextMenu(tray_menu) # Set right-click menu tray_icon.show() # Notifications tray_icon.showMessage("Hello!", "App loaded", QSystemTrayIcon.Information, 3000) sys.exit(app.exec_())
class Applejack(QApplication): def __init__(self): QApplication.__init__(self, sys.argv) self.logWin = QMainWindow() self.logWin.setMinimumSize(100, 100) self.logWin.setWindowTitle('Sign in IPM') self.logWin.setWindowIcon(self.style().standardIcon(QStyle.SP_ComputerIcon)) self.cw = QWidget(self.logWin) self.logWin.setCentralWidget(self.cw) self.gl = QGridLayout(self.logWin) self.cw.setLayout(self.gl) self.gl.addWidget(QLabel('Login:'******'Password:'******'Log in', self.logWin) self.gl.addWidget(self.btn, 2, 0) self.btn.clicked.connect(self.on_click_login) self.tray = QSystemTrayIcon(self.logWin) self.tray.setIcon(self.style().standardIcon(QStyle.SP_ComputerIcon)) self.logWin.show() self.tray.show() def output(self, cond, str): self.tray.showMessage(cond, str, msecs=5000) def on_click_login(self): if self.le.text() != '' and self.pe.text() != '': self.logWin.hide() Applet(login=self.le.text(), password=self.pe.text(), fOUT=self.output).start()
class IconWidget(QWidget): tray_icon = None def __init__(self): QWidget.__init__(self) quit_action = QAction("Exit", self) quit_action.triggered.connect(qApp.quit) tray_menu = QMenu() tray_menu.addAction(quit_action) self.tray_icon = QSystemTrayIcon(self) self.tray_icon.setIcon(self.style().standardIcon( QStyle.SP_ComputerIcon)) self.tray_icon.setContextMenu(tray_menu) self.tray_icon.show() import os self.tray_icon.showMessage("Tray Program", os.path.dirname(os.path.abspath(__file__)), QSystemTrayIcon.Information, 2000)
class IconWidget(QWidget): tray_icon = None secs_passed = 0 def __init__(self): QWidget.__init__(self) quit_action = QAction("Exit", self) quit_action.triggered.connect(qApp.quit) stat_action = QAction("Stat", self) stat_action.triggered.connect(self.show_stat) tray_menu = QMenu() tray_menu.addAction(stat_action) tray_menu.addAction(quit_action) self.tray_icon = QSystemTrayIcon(self) self.tray_icon.setIcon(self.style().standardIcon( QStyle.SP_DriveHDIcon)) self.tray_icon.setContextMenu(tray_menu) self.tray_icon.show() self.tray_icon.showMessage(APP_NAME, '%s running!' % APP_NAME, QSystemTrayIcon.Information, 1000) start_new_thread(self.write_txt, ()) def show_stat(self): self.tray_icon.showMessage( APP_NAME, 'Written %s for %d secs.' % (TXT_PATH, self.secs_passed), QSystemTrayIcon.Information, 2000) def write_txt(self): while True: file = open(TXT_PATH, 'w') file.write('[%s] Written %s for %d secs.' % (time.asctime(), TXT_PATH, self.secs_passed)) file.close() time.sleep(1) self.secs_passed += 1
class MainWindow(QMainWindow): def __init__(self): QMainWindow.__init__(self) self.tray_icon = QSystemTrayIcon(self) # Set icon to a standard or custom icon self.tray_icon.setIcon(self.style().standardIcon( QStyle.SP_ComputerIcon)) # self.tray_icon.setIcon(QtGui.QIcon("icons/devdungeon32x32.png")) exit_action = QAction("Exit", self) exit_action.triggered.connect(self.exit_app) tray_menu = QMenu() tray_menu.addAction(exit_action) self.tray_icon.setContextMenu(tray_menu) # Set right-click menu self.tray_icon.show() def notify(self, message): """Generate a desktop notification""" self.tray_icon.showMessage("Pssst!", message, QSystemTrayIcon.Information, 3000) def exit_app(self): self.tray_icon.hide( ) # Do this or icon will linger until you hover after exit qApp.quit() def closeEvent(self, event): """ By overriding closeEvent, we can ignore the event and instead hide the window, effectively performing a "close-to-system-tray" action. To exit, the right-click->Exit option from the system tray must be used. """ event.ignore() self.hide() self.notify("App minimize to system tray.")
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)
class LidControlMenu(object): def __init__(self, app, controller): """ """ self._controller = controller # system tray self._trayIcon = QSystemTrayIcon( QIcon(config.iconPath + "icon-sq.png"), app) menu = QMenu("Lid Control") activateAction = menu.addAction("Lid Control") activateAction.triggered.connect(self.MenuShowActivated) menu.addSeparator() # exit menu entry exitAction = menu.addAction(QIcon(config.iconPath + "icon-exit.svg"), "Exit") exitAction.triggered.connect(app.quit) self._trayIcon.setContextMenu(menu) # handle activation self._trayIcon.activated.connect(self.IconActivated) # endif def IconActivated(self): controller.OnActivateToggle() def MenuShowActivated(self): controller.OnActivate() def Show(self): self._trayIcon.show() def ShowMessage(self, title, message): self._trayIcon.showMessage(title, message)
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 Parse99(QApplication): def __init__(self, *args): super(QApplication, self).__init__(*args) # Tray Icon self._system_tray = QSystemTrayIcon() self._system_tray.setIcon(QIcon('ui/icon.png')) self._system_tray.setToolTip("Parse99") self._system_tray.show() # Settings self.settings = settings.Settings("parse99") # Plugins self._plugins = {'maps': Maps(self.settings)} # Timer self._timer = QTimer() self._timer.timeout.connect(self._parse) # Thread self._thread = None # Menu self._system_tray.setContextMenu(self._get_menu()) # File self._log_file = "" self._file_size = 0 self._last_line_read = 0 self._log_new_lines = [] # Start self.toggle('on') def _settings_valid(self): valid = True if self.settings.get_value('general', 'first_run') is None: self._system_tray.showMessage( "Parse99", """It looks like this is the first time the program is being run. Please setup the application using the Settings option once you right click the system tray icon.""" ) self.edit_settings() self.settings.set_value('general', 'first_run', True) valid = False elif self.settings.get_value('general', 'eq_directory') is None: self._system_tray.showMessage( "Parse99", "Please enter the General settings and \ choose the location of your Everquest Installation." ) self.edit_settings() valid = False elif self.settings.get_value('characters', None) is None: self._system_tray.showMessage( "Parse99", "No characters have been made. \ Please create at least one character using settings." ) self.edit_settings(tab="characters") valid = False elif self.settings.get_value('general', 'current_character') is None: self._system_tray.showMessage( "Parse99", "No character has been selected. \ Please choose a character from the Character menu." ) valid = False return valid def toggle(self, switch): if switch == 'off': if self._thread is not None: self._timer.stop() self._thread.stop() self._thread.join() elif switch == 'on': if self._settings_valid(): characters = self.settings.get_value('characters', None) log_file = characters[ self.settings.get_value('general', 'current_character') ]['log_file'] self._thread = FileReader( log_file, int(self.settings.get_value('general', 'parse_interval')) ) self._thread.start() self._timer.start( 1000 * int(self.settings.get_value('general', 'parse_interval')) ) def _parse(self): for line in self._thread.get_new_lines(): for plugin in self._plugins.keys(): if self._plugins[plugin].is_active(): self._plugins[plugin].parse(line) def _get_menu(self): # main menu menu = QMenu() main_menu_action_group = QActionGroup(menu) main_menu_action_group.setObjectName("main") # character menu map_action = QAction(menu) map_action.setText("Toggle Map") main_menu_action_group.addAction(map_action) separator = QAction(menu) separator.setSeparator(True) main_menu_action_group.addAction(separator) characters_action = QAction(menu) characters_action.setText("Switch Characters") main_menu_action_group.addAction(characters_action) separator = QAction(menu) separator.setSeparator(True) main_menu_action_group.addAction(separator) settings_action = QAction(menu) settings_action.setText("Settings") main_menu_action_group.addAction(settings_action) quit_action = QAction(menu) quit_action.setText("Quit") main_menu_action_group.addAction(quit_action) menu.addActions(main_menu_action_group.actions()) menu.triggered[QAction].connect(self._menu_actions) return menu def update_menu(self): self._system_tray.contextMenu().disconnect() self._system_tray.setContextMenu(self._get_menu()) def _menu_actions(self, action): # ag = action group, at = action text ag = action.actionGroup().objectName() at = action.text().lower() if ag == "main": if at == "quit": try: self.toggle('off') self._system_tray.setVisible(False) for plugin in self._plugins.keys(): self._plugins[plugin].close() self.quit() except Exception as e: print("menu actions, quit", e) elif at == "settings": self.edit_settings(tab="general") elif at == "switch characters": print("switch characters") self.edit_settings(tab="characters") elif at == "toggle map": self._plugins['maps'].toggle() def edit_settings(self, **kwargs): try: if not self.settings.gui.isVisible(): self.toggle('off') self.settings.gui.set_show_tab(kwargs.get('tab', None)) self.settings.gui.exec() self.toggle('on') except Exception as e: print("parse99.edit_settings():", e)
class 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 NetDotTsinghuaApplication(QApplication): """NetDotTsinghuaApplication""" start_worker = pyqtSignal() username_changed = pyqtSignal(str) update_all = pyqtSignal() def __init__(self, argv): super().__init__(argv) icon = QIcon(":/icon.png") self.setQuitOnLastWindowClosed(False) # Run without windows. self.setWindowIcon(icon) self.account_setting_dialog = None self.worker = Worker() self.worker_thread = QThread() self.worker.moveToThread(self.worker_thread) # For convenience. worker = self.worker config = worker.config acc = worker.account # Set up tray menu. self.tray = QSystemTrayIcon(icon, self) self.tray_menu = QMenu() # Status section. self.status_action = self.add_unabled_action() self.status_changed(worker.account.status) self.status = acc.status self.last_session = None # Account info section. self.tray_menu.addSeparator() self.username_action = self.add_unabled_action() self.usage_action = self.add_unabled_action() self.balance_action = self.add_unabled_action() self.refresh_username(config['username']) self.refresh_account_info(None, None) # Sessions section. self.sessions = [] self.session_menus = [] self.last_check = None self.tray_menu.addSeparator() self.sessions_title_action = self.add_unabled_action() self.last_check_action = self.add_unabled_action() self.refresh_sessions([]) # Actions. self.tray_menu.addSeparator() self.tray_menu.addAction('上线').triggered.connect(acc.login) self.tray_menu.addAction('下线').triggered.connect(acc.logout) self.tray_menu.addAction('现在刷新').triggered.connect(acc.update_all) # Config section. self.tray_menu.addSeparator() self.auto_manage_action = self.tray_menu.addAction('自动管理') self.auto_manage_action.setCheckable(True) self.auto_manage_action.setChecked(config['auto_manage']) self.auto_manage_action.toggled.connect(worker.auto_manage_changed) self.account_setting_action = self.tray_menu.addAction('账号设置...') self.account_setting_action.triggered.connect(self.account_setting) # About. self.tray_menu.addSeparator() self.tray_menu.addAction('关于').triggered.connect(self.show_about) # Quit. self.tray_menu.addSeparator() self.tray_menu.addAction('退出').triggered.connect(self.quit) self.tray.setContextMenu(self.tray_menu) self.tray.show() # Connect signals. self.start_worker.connect(worker.setup) self.username_changed.connect(self.refresh_username) self.username_changed.connect(worker.username_changed) self.update_all.connect(acc.update_all) acc.status_changed.connect(self.status_changed) acc.info_updated.connect(self.refresh_account_info) acc.last_session_updated.connect(self.last_session_changed) acc.sessions_updated.connect(self.refresh_sessions) # About to show. self.tray_menu.aboutToShow.connect(self.update_time) self.tray_menu.aboutToShow.connect(self.refresh_status) def add_unabled_action(self, text=''): action = self.tray_menu.addAction(text) action.setEnabled(False) return action def exec(self): self.worker_thread.start() self.start_worker.emit() # Start timers & check status. logging.debug('GUI thread enters event loop') return super().exec() def refresh_status(self): logging.debug('Refreshing status in the menu') s = STATUS_STR[self.status] # Show session usage if possible. if self.last_session and self.status in ('ONLINE', 'OTHERS_ACCOUNT_ONLINE'): s = s + ' - ' + _usage_str(self.last_session.byte) self.status_action.setText(s) def refresh_username(self, username): logging.debug('Refreshing username in the menu') if not username: self.username_action.setText('未设置账号') self.usage_action.setVisible(False) self.balance_action.setVisible(False) else: self.username_action.setText(username) self.usage_action.setVisible(True) self.balance_action.setVisible(True) def refresh_account_info(self, balance, byte): logging.debug('Refreshing account info section in the menu') self.usage_action.setText('本月流量:{}'.format(_usage_str(byte))) self.balance_action.setText('当前余额:{}'.format(_balance_str(balance))) def status_changed(self, status): # Show tray message. if status == 'ONLINE': self.tray.showMessage('当前在线', '本人账号在线') elif status == 'OTHERS_ACCOUNT_ONLINE': self.tray.showMessage('当前在线', '他人账号在线') elif status == 'OFFLINE': self.tray.showMessage('当前离线', '可以登录校园网') self.status = status def last_session_changed(self, session): self.last_session = session def refresh_sessions(self, sessions): logging.debug('Refreshing sessions section in the menu') self.sessions = sessions self.last_check = datetime.now() if len(sessions): self.sessions_title_action.setText('当前在线') else: self.sessions_title_action.setText('无设备在线') # Remove old actions for menu in self.session_menus: self.tray_menu.removeAction(menu.menuAction()) self.session_menus.clear() # Add new actions. for session in sessions: menu = SessionMenu(self.worker.account, session) self.tray_menu.insertMenu(self.last_check_action, menu).setText(session.device_name) self.session_menus.append(menu) def update_time(self): self.last_check_action.setText( '上次更新:{}'.format(_time_passed_str(self.last_check))) @pyqtSlot() def account_setting(self): if self.account_setting_dialog is None: self.account_setting_dialog = AccountSettingDialog() existed = False else: existed = True dialog = self.account_setting_dialog dialog.show() dialog.raise_() dialog.activateWindow() if existed: # Avoid multiple dialogs. return if dialog.exec(): username = dialog.username.text() # Set password if needed. if username: acc = Account(username) acc.password = dialog.password.text() if username != self.worker.account.username: # If username changed, emit signal and clear current account info. self.username_changed.emit(username) self.refresh_account_info(None, None) else: # Update all because password might has changed. self.update_all.emit() self.account_setting_dialog = None @pyqtSlot() def show_about(self): msg = """<p><b>net.tsinghua {}</b><br> <small>Copyright Ⓒ 2015 Thomas Lee</small></p> <p><a href="https://github.com/ThomasLee969/net.tsinghua">Github 主页</a> </p>""".format(__VERSION__) QMessageBox.about(None, '关于 net.tsinghua', msg)
class MainWindow(QMainWindow): restartOdooMenuItem = QtCore.pyqtSignal() stopOdooMenuItem = QtCore.pyqtSignal() restartPostgreMenuItem = QtCore.pyqtSignal() stopPostgreMenuItem = QtCore.pyqtSignal() def __init__(self): super(MainWindow, self).__init__() if OdooInstallationFound == False: if not os.path.isdir(appfolder + '\\Temp'): os.makedirs(appfolder + '\\Temp') unzipToPath = appfolder + '\\Temp\\Unzip' destinationPath = appfolder + "\\Runtime\\Odoo" self.downloadFileWorker(mainSettings['github']['downloadPath']) if os.path.isfile(appfolder + '\\Temp\\GitHub-Odoo.zip'): if not os.path.isdir(appfolder + '\\Temp\\unzip'): os.makedirs(appfolder + '\\Temp\\unzip') self.zipFileWorker(appfolder + '\\Temp\\GitHub-Odoo.zip', unzipToPath) self.zipWindow.close() #Check if the file is etxracted to subfolder - Files on github includes branch name -> Correct this countFolders = 0 extractFolder = None for name in os.listdir(unzipToPath): extractFolder = name countFolders += 1 if countFolders == 1: shutil.move(unzipToPath + "\\" + extractFolder + "\\", destinationPath) self.startCybeSystemsApplication() else: self.startCybeSystemsApplication() if os.path.isdir(appfolder + '\\Temp'): shutil.rmtree(appfolder + '\\Temp',ignore_errors=True) def zipFileWorker(self,file, destination_folder): self.zipWindow = ZipWindow(file, destination_folder) self.zipWindow.show() def downloadFileWorker(self,url): self.httpWin = HttpWindow(url) self.httpWin.exec() def startCybeSystemsApplication(self): #Set Loading TrayIcon self.setWindowIcon(QtGui.QIcon(appfolder + '/ressource/icons/icon.png')) img = QtGui.QImage() img.load(appfolder + '/ressource/icons/icon_loading.png') self.pixmap = QtGui.QPixmap.fromImage(img) self.icon = QtGui.QIcon() self.icon.addPixmap(self.pixmap) self.tray = QSystemTrayIcon(self.icon, self) self.tray.show() traymenu = QMenu() #Set Real Icon self.tray.hide() img = QtGui.QImage() img.load(appfolder + '/ressource/icons/icon.png') self.pixmap = QtGui.QPixmap.fromImage(img) self.icon = QtGui.QIcon() self.icon.addPixmap(self.pixmap) self.tray = QSystemTrayIcon(self.icon, self) self.tray.activated.connect(self.onTrayIconActivated) self.tray.setContextMenu(traymenu) self.tray.show() #Load Stylesheet if mainSettings['other']['theme'].lower() != 'default': if mainSettings['other']['theme'].lower() == 'steamlike': stylesheetFile = open(appfolder + '/ressource/ui/steamlike.stylesheet', "r") elif mainSettings['other']['theme'].lower() == 'darkorange': stylesheetFile = open(appfolder + '/ressource/ui/darkorange.stylesheet', "r") elif mainSettings['other']['theme'].lower() == 'maya': stylesheetFile = open(appfolder + '/ressource/ui/maya.stylesheet', "r") stylesheet = stylesheetFile.read() traymenu.setStyleSheet(stylesheet) stylesheetFile.close() trayoption_openBrowser_entry = QAction(QtGui.QIcon(self.icon), "Open Odoo", self) trayoption_openBrowser_entry.triggered.connect(lambda: webbrowser.open(mainSettings['odoo']['startpage'])) traymenu.addAction(trayoption_openBrowser_entry) trayoption_openBrowser_entry.setIcon(QtGui.QIcon(appfolder + '/ressource/icons/world.png')) traymenu.addSeparator() tools = traymenu.addMenu('&Odoo') tools.setIcon(QtGui.QIcon(appfolder + '/ressource/icons/icon.png')) tools_odoo_restart = QAction(QtGui.QIcon(self.icon), "Restart Server", self) tools_odoo_restart.triggered.connect(lambda: (self.restartOdooMenuItem.emit())) tools.addAction(tools_odoo_restart) tools_odoo_restart.setIcon(QtGui.QIcon(appfolder + '/ressource/icons/start_server.png')) tools_odoo_stop = QAction(QtGui.QIcon(self.icon), "Stop Server", self) tools_odoo_stop.triggered.connect(lambda: (self.stopOdooMenuItem.emit())) tools.addAction(tools_odoo_stop) tools_odoo_stop.setIcon(QtGui.QIcon(appfolder + '/ressource/icons/stop_server.png')) #traymenu.addSeparator() tools = traymenu.addMenu('&PostgreSQL') tools.setIcon(QtGui.QIcon(appfolder + '/ressource/icons/postgresql.png')) tools_postgre_restart = QAction(QtGui.QIcon(self.icon), "Restart Server", self) tools_postgre_restart.triggered.connect(lambda: (self.restartPostgreMenuItem.emit())) tools.addAction(tools_postgre_restart) tools_postgre_restart.setIcon(QtGui.QIcon(appfolder + '/ressource/icons/start_server.png')) tools_postgre_stop = QAction(QtGui.QIcon(self.icon), "Stop Server", self) tools_postgre_stop.triggered.connect(lambda: (self.stopPostgreMenuItem.emit())) tools.addAction(tools_postgre_stop) tools_postgre_stop.setIcon(QtGui.QIcon(appfolder + '/ressource/icons/stop_server.png')) tools.addSeparator() tools_pgadmin = QAction(QtGui.QIcon(self.icon), "pgAdmin III", self) tools_pgadmin.triggered.connect(lambda: self.startpgadmin()) tools.addAction(tools_pgadmin) tools_pgadmin.setIcon(QtGui.QIcon(appfolder + '/ressource/icons/cog.png')) traymenu.addSeparator() trayoption_quickconfig = QAction(QtGui.QIcon(self.icon), "Show Output/Config", self) trayoption_quickconfig.triggered.connect(lambda: self.showCommandLineWindowTryOption()) traymenu.addAction(trayoption_quickconfig) trayoption_quickconfig.setIcon(QtGui.QIcon(appfolder + '/ressource/icons/application_osx_terminal.png')) traymenu.addSeparator() trayoption_exit_entry = QAction(QtGui.QIcon(self.icon), "Exit", self) trayoption_exit_entry.triggered.connect(lambda: self.trayOptionExit()) traymenu.addAction(trayoption_exit_entry) trayoption_exit_entry.setIcon(QtGui.QIcon(appfolder + '/ressource/icons/cancel.png')) self.tray.showMessage('Odoo is Loading - Please wait','\nLeft click to open CommandWindow\nRight click to open Traymenu') self.showCommandLineWindow() def startpgadmin(self): os.startfile(appfolder + '/Runtime/PostgreSQL/bin/pgAdmin3.exe') def showCommandLineWindow(self): self.ShowCommandLineWindow=CommandLineWindow(self) self.ShowCommandLineWindow.setWindowIcon(QtGui.QIcon(appfolder + '/ressource/icons/icon.png')) self.ShowCommandLineWindow.show() if mainSettings['other']['minimizeToTray']: self.ShowCommandLineWindow.setVisible(False) else: self.ShowCommandLineWindow.setVisible(True) def toggleCommandLineWindow(self): if self.ShowCommandLineWindow.isMinimized(): self.ShowCommandLineWindow.setVisible(True) self.ShowCommandLineWindow.showNormal() self.ShowCommandLineWindow.activateWindow() elif self.ShowCommandLineWindow.isVisible (): self.ShowCommandLineWindow.showNormal() self.ShowCommandLineWindow.setVisible(False) else: self.ShowCommandLineWindow.setVisible(True) self.ShowCommandLineWindow.showNormal() self.ShowCommandLineWindow.activateWindow() def showCommandLineWindowTryOption(self): self.ShowCommandLineWindow.setVisible(True) self.ShowCommandLineWindow.showNormal() self.ShowCommandLineWindow.activateWindow() def onTrayIconActivated(self,reason): if reason == QSystemTrayIcon.DoubleClick: pass if reason == QSystemTrayIcon.Trigger: self.toggleCommandLineWindow() if reason == QSystemTrayIcon.Context: pass def center(self): qr = self.frameGeometry() cp = QDesktopWidget().availableGeometry().center() qr.moveCenter(cp) self.move(qr.topLeft()) def trayOptionExit(self,msgbox=True): if msgbox: quit_msg = "Stop all running Server ?" reply = QMessageBox.question(self.center(), 'Exit', quit_msg, QMessageBox.Yes, QMessageBox.No) if reply == QMessageBox.Yes: confirmed = True else: confirmed = False else: confirmed = True if confirmed: runtimeSettings["closeMainWindow"] = True self.ShowCommandLineWindow.setVisible(True) app = QApplication.instance() app.closeAllWindows() self.tray.hide() os._exit(1)
class SystemTrayIcon(QMainWindow): def __init__(self, parent=None): super(SystemTrayIcon, self).__init__(parent) self.setAttribute(Qt.WA_DeleteOnClose) self.settings = QSettings() self.language = self.settings.value('Language') or '' self.temp_decimal_bool = self.settings.value('Decimal') or False # initialize the tray icon type in case of first run: issue#42 self.tray_type = self.settings.value('TrayType') or 'icon&temp' cond = conditions.WeatherConditions() self.temporary_city_status = False self.conditions = cond.trans self.clouds = cond.clouds self.wind = cond.wind self.wind_dir = cond.wind_direction self.wind_codes = cond.wind_codes self.inerror = False self.tentatives = 0 self.baseurl = 'http://api.openweathermap.org/data/2.5/weather?id=' self.accurate_url = 'http://api.openweathermap.org/data/2.5/find?q=' self.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 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)
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): colorWidget = None mode_steady = None mode_blink = None mode_pulse = None speed = None notifier = None mode = Command.Mode.Steady prev_mode = Command.Mode.Blink blink_min = 0 blink_max = 3000 pulse_min = 0 pulse_max = 3000 def __init__(self, notifier, *args, **kwargs): super(MainWindow, self).__init__(*args, **kwargs) self.setWindowTitle(_("Status Light Controller")) self.setWindowIcon(QIcon('horizontal-traffic-light.ico')) self.notifier = notifier layout = QGridLayout() # Color Picker section self.colorWidget = Color('black') self.colorWidget.setMouseReleased(self.change_color) layout.addWidget(self.colorWidget, 0, 0, 1, 2) # status_set = QPushButton(_("Set Status")) # status_set.clicked.connect(self.set_status) # layout.addWidget(status_set, 0, 1, 1, 2) # Mode section mode_layout = QVBoxLayout() self.mode_steady = QRadioButton(_("Steady")) self.mode_steady.setChecked(True) self.mode_steady.toggled.connect( lambda: self.change_mode(Command.Mode.Steady)) mode_layout.addWidget(self.mode_steady) self.mode_blink = QRadioButton(_("Blink")) self.mode_blink.toggled.connect( lambda: self.change_mode(Command.Mode.Blink)) mode_layout.addWidget(self.mode_blink) self.mode_pulse = QRadioButton(_("Pulse")) self.mode_pulse.toggled.connect( lambda: self.change_mode(Command.Mode.Pulse)) mode_layout.addWidget(self.mode_pulse) layout.addLayout(mode_layout, 0, 2, 1, 2) # Speed section speed_label = QLabel(_("Speed:")) layout.addWidget(speed_label, 1, 0, 1, 1) self.speed = QSlider(Qt.Horizontal) self.speed.setTickPosition(QSlider.TicksBothSides) self.speed.setTickInterval(100) self.speed.setSingleStep(10) self.speed.setMinimum(self.blink_min) self.speed.setMaximum(self.blink_max) self.speed.setValue((self.blink_max - self.blink_min) / 2) self.speed.setEnabled(False) self.speed.valueChanged[int].connect(self.change_speed) layout.addWidget(self.speed, 1, 1, 1, 3) widget = QWidget() widget.setLayout(layout) self.setCentralWidget(widget) # System Tray Icon self.tray_icon = QSystemTrayIcon(self) self.tray_icon.setIcon(QIcon('horizontal-traffic-light.ico')) 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_app) tray_menu = QMenu() tray_menu.addAction(show_action) tray_menu.addAction(hide_action) tray_menu.addAction(quit_action) self.tray_icon.setContextMenu(tray_menu) self.tray_icon.show() def change_color(self): color = QColorDialog.getColor() self.colorWidget.setColor(color) self.notifier.set_rgb("{:02x}".format(color.red()), "{:02x}".format(color.green()), "{:02x}".format(color.blue())) def change_mode(self, mode): self.notifier.set_mode(mode) if mode == Command.Mode.Steady: self.speed.setEnabled(False) else: if self.prev_mode != mode: self.convert_range(self.prev_mode, mode) self.prev_mode = mode self.speed.setEnabled(True) self.change_speed() self.mode = mode def convert_range(self, old_mode, new_mode): if old_mode == Command.Mode.Blink: old_min = self.blink_min old_max = self.blink_max elif old_mode == Command.Mode.Pulse: old_min = self.pulse_min old_max = self.pulse_max if new_mode == Command.Mode.Blink: new_min = self.blink_min new_max = self.blink_max elif new_mode == Command.Mode.Pulse: new_min = self.pulse_min new_max = self.pulse_max old_val = self.speed.value() old_range = old_max - old_min new_range = new_max - new_min new_val = (((old_val - old_min) * new_range) / old_range) + new_min self.speed.setMinimum(new_min) self.speed.setMaximum(new_max) self.speed.setValue(new_val) def change_speed(self): val = self.speed.value() speed = (self.speed.maximum() + 1) - val self.notifier.set_speed(speed) def closeEvent(self, event): event.ignore() self.hide() self.tray_icon.showMessage('Status Light Controller', 'Application was minimized to tray', QSystemTrayIcon.Information, 2000) def close_app(self, event): self.notifier.set_rgb('00', '00', '00') qApp.quit()
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 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): def __init__(self, parent=None): QMainWindow.__init__(self, parent) self.ui = Ui_MainWindow() self.ui.setupUi(self) self.connect_handlers() # Sub Views self.server_form = ServerForm() self.server_form.set_main_window(self) self.server_form.hide() # Initiate empty list self.data = [] self.notifications = [] # Refresh list self.refresh_list() # Set icons icon = QtGui.QIcon(os.path.join(os.path.dirname(__file__), "icon.png")) self.setWindowIcon(icon) self.msgIcon = QSystemTrayIcon.MessageIcon(QSystemTrayIcon.Information) self.systray_icon = QSystemTrayIcon(icon) self.systray_icon.activated.connect(self.toggle_window) self.systray_icon.show() def toggle_window(self): if self.isVisible(): self.hide() else: self.show() def update_list(self): save_data(self.data, JSON_PATH) def refresh_list(self): # List Data self.data = read_data(JSON_PATH) self.ui.serverList.clear() for x in self.data: # print x self.ui.serverList.addItem(x['name']) def connect_handlers(self): user_interface = self.ui # Bind Add Events user_interface.addButton.clicked.connect(self.handle_add_events) user_interface.actionAdd_Server.triggered.connect(self.handle_add_events) # Bind Edit Events user_interface.editButton.clicked.connect(self.handle_edit_events) # Bind Del Events user_interface.delButton.clicked.connect(self.handle_del_events) # Bind Toggle Events user_interface.toggleButton.clicked.connect(self.handle_toggle_events) user_interface.actionStart_Monitoring.triggered.connect(self.handle_toggle_events) user_interface.actionStop_Monitoring.triggered.connect(self.handle_toggle_events) # Bind Quit Events user_interface.actionQuit.triggered.connect(self.handle_quit_events) # Bind Signals NOTIFIER.notify.connect(self.push_notifications) def handle_add_events(self): self.server_form.show() self.hide() def handle_edit_events(self): try: item = self.ui.serverList.selectedIndexes()[0] index = item.row() except: index = None if index is not None: self.server_form.set_entry_id(index) else: self.server_form.set_default_texts() self.server_form.show() self.hide() def handle_del_events(self): try: item = self.ui.serverList.selectedIndexes()[0] index = item.row() del self.data[index] except: pass self.update_list() self.refresh_list() def handle_toggle_events(self): try: timer = self.timer self.timer.cancel() del self.timer self.ui.toggleButton.setText("START") self.ui.actionStop_Monitoring.setDisabled(True) self.ui.actionStart_Monitoring.setEnabled(True) self.systray_icon.showMessage('STOP', "Server monitoring was stopped!", self.msgIcon) except: self.ui.actionStop_Monitoring.setEnabled(True) self.ui.actionStart_Monitoring.setDisabled(True) self.ui.toggleButton.setText("STOP") self.handle_timed_loop() self.systray_icon.showMessage('START', "Server monitoring has started!", self.msgIcon) def handle_quit_events(self): self.timer.cancel() self.close() def handle_timed_loop(self): self.notifications = monitor_servers(self.data) self.timer = threading.Timer(10, self.handle_timed_loop) self.timer.start() NOTIFIER.notify.emit() def push_notifications(self): # print "ok- called" for x in self.notifications: self.systray_icon.showMessage('ALERT', x, self.msgIcon) # pass def closeEvent(self, event): try: timer = self.timer self.timer.cancel() del self.timer except: pass event.accept()
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 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 QtApplication(QApplication, Application): pluginsLoaded = Signal() applicationRunning = Signal() def __init__(self, tray_icon_name: str = None, **kwargs) -> None: plugin_path = "" if sys.platform == "win32": if hasattr(sys, "frozen"): plugin_path = os.path.join(os.path.dirname(os.path.abspath(sys.executable)), "PyQt5", "plugins") Logger.log("i", "Adding QT5 plugin path: %s", plugin_path) QCoreApplication.addLibraryPath(plugin_path) else: import site for sitepackage_dir in site.getsitepackages(): QCoreApplication.addLibraryPath(os.path.join(sitepackage_dir, "PyQt5", "plugins")) elif sys.platform == "darwin": plugin_path = os.path.join(self.getInstallPrefix(), "Resources", "plugins") if plugin_path: Logger.log("i", "Adding QT5 plugin path: %s", plugin_path) QCoreApplication.addLibraryPath(plugin_path) # use Qt Quick Scene Graph "basic" render loop os.environ["QSG_RENDER_LOOP"] = "basic" super().__init__(sys.argv, **kwargs) # type: ignore self._qml_import_paths = [] #type: List[str] self._main_qml = "main.qml" #type: str self._qml_engine = None #type: Optional[QQmlApplicationEngine] self._main_window = None #type: Optional[MainWindow] self._tray_icon_name = tray_icon_name #type: Optional[str] self._tray_icon = None #type: Optional[str] self._tray_icon_widget = None #type: Optional[QSystemTrayIcon] self._theme = None #type: Optional[Theme] self._renderer = None #type: Optional[QtRenderer] self._job_queue = None #type: Optional[JobQueue] self._version_upgrade_manager = None #type: Optional[VersionUpgradeManager] self._is_shutting_down = False #type: bool self._recent_files = [] #type: List[QUrl] self._configuration_error_message = None #type: Optional[ConfigurationErrorMessage] def addCommandLineOptions(self) -> None: super().addCommandLineOptions() # This flag is used by QApplication. We don't process it. self._cli_parser.add_argument("-qmljsdebugger", help = "For Qt's QML debugger compatibility") def initialize(self) -> None: super().initialize() # Initialize the package manager to remove and install scheduled packages. self._package_manager = self._package_manager_class(self, parent = self) self._mesh_file_handler = MeshFileHandler(self) #type: MeshFileHandler self._workspace_file_handler = WorkspaceFileHandler(self) #type: WorkspaceFileHandler # Remove this and you will get Windows 95 style for all widgets if you are using Qt 5.10+ self.setStyle("fusion") self.setAttribute(Qt.AA_UseDesktopOpenGL) major_version, minor_version, profile = OpenGLContext.detectBestOpenGLVersion() if major_version is None and minor_version is None and profile is None and not self.getIsHeadLess(): Logger.log("e", "Startup failed because OpenGL version probing has failed: tried to create a 2.0 and 4.1 context. Exiting") QMessageBox.critical(None, "Failed to probe OpenGL", "Could not probe OpenGL. This program requires OpenGL 2.0 or higher. Please check your video card drivers.") sys.exit(1) else: opengl_version_str = OpenGLContext.versionAsText(major_version, minor_version, profile) Logger.log("d", "Detected most suitable OpenGL context version: %s", opengl_version_str) if not self.getIsHeadLess(): OpenGLContext.setDefaultFormat(major_version, minor_version, profile = profile) self._qml_import_paths.append(os.path.join(os.path.dirname(sys.executable), "qml")) self._qml_import_paths.append(os.path.join(self.getInstallPrefix(), "Resources", "qml")) Logger.log("i", "Initializing job queue ...") self._job_queue = JobQueue() self._job_queue.jobFinished.connect(self._onJobFinished) Logger.log("i", "Initializing version upgrade manager ...") self._version_upgrade_manager = VersionUpgradeManager(self) def startSplashWindowPhase(self) -> None: super().startSplashWindowPhase() self._package_manager.initialize() # Read preferences here (upgrade won't work) to get the language in use, so the splash window can be shown in # the correct language. try: preferences_filename = Resources.getPath(Resources.Preferences, self._app_name + ".cfg") self._preferences.readFromFile(preferences_filename) except FileNotFoundError: Logger.log("i", "Preferences file not found, ignore and use default language '%s'", self._default_language) signal.signal(signal.SIGINT, signal.SIG_DFL) # This is done here as a lot of plugins require a correct gl context. If you want to change the framework, # these checks need to be done in your <framework>Application.py class __init__(). i18n_catalog = i18nCatalog("uranium") self._configuration_error_message = ConfigurationErrorMessage(self, i18n_catalog.i18nc("@info:status", "Your configuration seems to be corrupt."), lifetime = 0, title = i18n_catalog.i18nc("@info:title", "Configuration errors") ) # Remove, install, and then loading plugins self.showSplashMessage(i18n_catalog.i18nc("@info:progress", "Loading plugins...")) # Remove and install the plugins that have been scheduled self._plugin_registry.initializeBeforePluginsAreLoaded() self._loadPlugins() self._plugin_registry.checkRequiredPlugins(self.getRequiredPlugins()) self.pluginsLoaded.emit() self.showSplashMessage(i18n_catalog.i18nc("@info:progress", "Updating configuration...")) with self._container_registry.lockFile(): VersionUpgradeManager.getInstance().upgrade() # Load preferences again because before we have loaded the plugins, we don't have the upgrade routine for # the preferences file. Now that we have, load the preferences file again so it can be upgraded and loaded. try: preferences_filename = Resources.getPath(Resources.Preferences, self._app_name + ".cfg") with open(preferences_filename, "r", encoding = "utf-8") as f: serialized = f.read() # This performs the upgrade for Preferences self._preferences.deserialize(serialized) self._preferences.setValue("general/plugins_to_remove", "") self._preferences.writeToFile(preferences_filename) except FileNotFoundError: Logger.log("i", "The preferences file cannot be found, will use default values") # Force the configuration file to be written again since the list of plugins to remove maybe changed self.showSplashMessage(i18n_catalog.i18nc("@info:progress", "Loading preferences...")) try: self._preferences_filename = Resources.getPath(Resources.Preferences, self._app_name + ".cfg") self._preferences.readFromFile(self._preferences_filename) except FileNotFoundError: Logger.log("i", "The preferences file '%s' cannot be found, will use default values", self._preferences_filename) self._preferences_filename = Resources.getStoragePath(Resources.Preferences, self._app_name + ".cfg") # FIXME: This is done here because we now use "plugins.json" to manage plugins instead of the Preferences file, # but the PluginRegistry will still import data from the Preferences files if present, such as disabled plugins, # so we need to reset those values AFTER the Preferences file is loaded. self._plugin_registry.initializeAfterPluginsAreLoaded() # Check if we have just updated from an older version self._preferences.addPreference("general/last_run_version", "") last_run_version_str = self._preferences.getValue("general/last_run_version") if not last_run_version_str: last_run_version_str = self._version last_run_version = Version(last_run_version_str) current_version = Version(self._version) if last_run_version < current_version: self._just_updated_from_old_version = True self._preferences.setValue("general/last_run_version", str(current_version)) self._preferences.writeToFile(self._preferences_filename) # Preferences: recent files self._preferences.addPreference("%s/recent_files" % self._app_name, "") file_names = self._preferences.getValue("%s/recent_files" % self._app_name).split(";") for file_name in file_names: if not os.path.isfile(file_name): continue self._recent_files.append(QUrl.fromLocalFile(file_name)) if not self.getIsHeadLess(): # Initialize System tray icon and make it invisible because it is used only to show pop up messages self._tray_icon = None if self._tray_icon_name: self._tray_icon = QIcon(Resources.getPath(Resources.Images, self._tray_icon_name)) self._tray_icon_widget = QSystemTrayIcon(self._tray_icon) self._tray_icon_widget.setVisible(False) def initializeEngine(self) -> None: # TODO: Document native/qml import trickery self._qml_engine = QQmlApplicationEngine(self) self._qml_engine.setOutputWarningsToStandardError(False) self._qml_engine.warnings.connect(self.__onQmlWarning) for path in self._qml_import_paths: self._qml_engine.addImportPath(path) if not hasattr(sys, "frozen"): self._qml_engine.addImportPath(os.path.join(os.path.dirname(__file__), "qml")) self._qml_engine.rootContext().setContextProperty("QT_VERSION_STR", QT_VERSION_STR) self._qml_engine.rootContext().setContextProperty("screenScaleFactor", self._screenScaleFactor()) self.registerObjects(self._qml_engine) Bindings.register() self._qml_engine.load(self._main_qml) self.engineCreatedSignal.emit() recentFilesChanged = pyqtSignal() @pyqtProperty("QVariantList", notify=recentFilesChanged) def recentFiles(self) -> List[QUrl]: return self._recent_files def _onJobFinished(self, job: Job) -> None: if isinstance(job, WriteFileJob): if not job.getResult() or not job.getAddToRecentFiles(): # For a write file job, if it failed or it doesn't need to be added to the recent files list, we do not # add it. return elif (not isinstance(job, ReadMeshJob) and not isinstance(job, ReadFileJob)) or not job.getResult(): return if isinstance(job, (ReadMeshJob, ReadFileJob, WriteFileJob)): self.addFileToRecentFiles(job.getFileName()) def addFileToRecentFiles(self, file_name: str) -> None: file_path = QUrl.fromLocalFile(file_name) if file_path in self._recent_files: self._recent_files.remove(file_path) self._recent_files.insert(0, file_path) if len(self._recent_files) > 10: del self._recent_files[10] pref = "" for path in self._recent_files: pref += path.toLocalFile() + ";" self.getPreferences().setValue("%s/recent_files" % self.getApplicationName(), pref) self.recentFilesChanged.emit() def run(self) -> None: super().run() def hideMessage(self, message: Message) -> None: with self._message_lock: if message in self._visible_messages: message.hide(send_signal = False) # we're in handling hideMessageSignal so we don't want to resend it self._visible_messages.remove(message) self.visibleMessageRemoved.emit(message) def showMessage(self, message: Message) -> None: with self._message_lock: if message not in self._visible_messages: self._visible_messages.append(message) message.setLifetimeTimer(QTimer()) message.setInactivityTimer(QTimer()) self.visibleMessageAdded.emit(message) # also show toast message when the main window is minimized self.showToastMessage(self._app_name, message.getText()) def _onMainWindowStateChanged(self, window_state: int) -> None: if self._tray_icon and self._tray_icon_widget: visible = window_state == Qt.WindowMinimized self._tray_icon_widget.setVisible(visible) # Show toast message using System tray widget. def showToastMessage(self, title: str, message: str) -> None: if self.checkWindowMinimizedState() and self._tray_icon_widget: # NOTE: Qt 5.8 don't support custom icon for the system tray messages, but Qt 5.9 does. # We should use the custom icon when we switch to Qt 5.9 self._tray_icon_widget.showMessage(title, message) def setMainQml(self, path: str) -> None: self._main_qml = path def exec_(self, *args: Any, **kwargs: Any) -> None: self.applicationRunning.emit() super().exec_(*args, **kwargs) @pyqtSlot() def reloadQML(self) -> None: # only reload when it is a release build if not self.getIsDebugMode(): return if self._qml_engine and self._theme: self._qml_engine.clearComponentCache() self._theme.reload() self._qml_engine.load(self._main_qml) # Hide the window. For some reason we can't close it yet. This needs to be done in the onComponentCompleted. for obj in self._qml_engine.rootObjects(): if obj != self._qml_engine.rootObjects()[-1]: obj.hide() @pyqtSlot() def purgeWindows(self) -> None: # Close all root objects except the last one. # Should only be called by onComponentCompleted of the mainWindow. if self._qml_engine: for obj in self._qml_engine.rootObjects(): if obj != self._qml_engine.rootObjects()[-1]: obj.close() @pyqtSlot("QList<QQmlError>") def __onQmlWarning(self, warnings: List[QQmlError]) -> None: for warning in warnings: Logger.log("w", warning.toString()) engineCreatedSignal = Signal() def isShuttingDown(self) -> bool: return self._is_shutting_down def registerObjects(self, engine) -> None: #type: ignore #Don't type engine, because the type depends on the platform you're running on so it always gives an error somewhere. engine.rootContext().setContextProperty("PluginRegistry", PluginRegistry.getInstance()) def getRenderer(self) -> QtRenderer: if not self._renderer: self._renderer = QtRenderer() return cast(QtRenderer, self._renderer) mainWindowChanged = Signal() def getMainWindow(self) -> Optional[MainWindow]: return self._main_window def setMainWindow(self, window: MainWindow) -> None: if window != self._main_window: if self._main_window is not None: self._main_window.windowStateChanged.disconnect(self._onMainWindowStateChanged) self._main_window = window if self._main_window is not None: self._main_window.windowStateChanged.connect(self._onMainWindowStateChanged) self.mainWindowChanged.emit() def setVisible(self, visible: bool) -> None: if self._main_window is not None: self._main_window.visible = visible @property def isVisible(self) -> bool: if self._main_window is not None: return self._main_window.visible #type: ignore #MyPy doesn't realise that self._main_window cannot be None here. return False def getTheme(self) -> Optional[Theme]: if self._theme is None: if self._qml_engine is None: Logger.log("e", "The theme cannot be accessed before the engine is initialised") return None self._theme = UM.Qt.Bindings.Theme.Theme.getInstance(self._qml_engine) return self._theme # Handle a function that should be called later. def functionEvent(self, event: QEvent) -> None: e = _QtFunctionEvent(event) QCoreApplication.postEvent(self, e) # Handle Qt events def event(self, event: QEvent) -> bool: if event.type() == _QtFunctionEvent.QtFunctionEvent: event._function_event.call() return True return super().event(event) def windowClosed(self, save_data: bool = True) -> None: Logger.log("d", "Shutting down %s", self.getApplicationName()) self._is_shutting_down = True # garbage collect tray icon so it gets properly closed before the application is closed self._tray_icon_widget = None if save_data: try: self.savePreferences() except Exception as e: Logger.log("e", "Exception while saving preferences: %s", repr(e)) try: self.applicationShuttingDown.emit() except Exception as e: Logger.log("e", "Exception while emitting shutdown signal: %s", repr(e)) try: self.getBackend().close() except Exception as e: Logger.log("e", "Exception while closing backend: %s", repr(e)) if self._tray_icon_widget: self._tray_icon_widget.deleteLater() self.quit() def checkWindowMinimizedState(self) -> bool: if self._main_window is not None and self._main_window.windowState() == Qt.WindowMinimized: return True else: return False ## Get the backend of the application (the program that does the heavy lifting). # The backend is also a QObject, which can be used from qml. @pyqtSlot(result = "QObject*") def getBackend(self) -> Backend: return self._backend ## Property used to expose the backend # It is made static as the backend is not supposed to change during runtime. # This makes the connection between backend and QML more reliable than the pyqtSlot above. # \returns Backend \type{Backend} @pyqtProperty("QVariant", constant = True) def backend(self) -> Backend: return self.getBackend() ## Create a class variable so we can manage the splash in the CrashHandler dialog when the Application instance # is not yet created, e.g. when an error occurs during the initialization splash = None # type: Optional[QSplashScreen] def createSplash(self) -> None: if not self.getIsHeadLess(): try: QtApplication.splash = self._createSplashScreen() except FileNotFoundError: QtApplication.splash = None else: if QtApplication.splash: QtApplication.splash.show() self.processEvents() ## Display text on the splash screen. def showSplashMessage(self, message: str) -> None: if not QtApplication.splash: self.createSplash() if QtApplication.splash: QtApplication.splash.showMessage(message, Qt.AlignHCenter | Qt.AlignVCenter) self.processEvents() elif self.getIsHeadLess(): Logger.log("d", message) ## Close the splash screen after the application has started. def closeSplash(self) -> None: if QtApplication.splash: QtApplication.splash.close() QtApplication.splash = None ## Create a QML component from a qml file. # \param qml_file_path: The absolute file path to the root qml file. # \param context_properties: Optional dictionary containing the properties that will be set on the context of the # qml instance before creation. # \return None in case the creation failed (qml error), else it returns the qml instance. # \note If the creation fails, this function will ensure any errors are logged to the logging service. def createQmlComponent(self, qml_file_path: str, context_properties: Dict[str, "QObject"] = None) -> Optional["QObject"]: if self._qml_engine is None: # Protect in case the engine was not initialized yet return None path = QUrl.fromLocalFile(qml_file_path) component = QQmlComponent(self._qml_engine, path) result_context = QQmlContext(self._qml_engine.rootContext()) #type: ignore #MyPy doens't realise that self._qml_engine can't be None here. if context_properties is not None: for name, value in context_properties.items(): result_context.setContextProperty(name, value) result = component.create(result_context) for err in component.errors(): Logger.log("e", str(err.toString())) if result is None: return None # We need to store the context with the qml object, else the context gets garbage collected and the qml objects # no longer function correctly/application crashes. result.attached_context = result_context return result ## Delete all nodes containing mesh data in the scene. # \param only_selectable. Set this to False to delete objects from all build plates @pyqtSlot() def deleteAll(self, only_selectable = True) -> None: Logger.log("i", "Clearing scene") if not self.getController().getToolsEnabled(): return nodes = [] for node in DepthFirstIterator(self.getController().getScene().getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. if not isinstance(node, SceneNode): continue if (not node.getMeshData() and not node.callDecoration("getLayerData")) and not node.callDecoration("isGroup"): continue # Node that doesnt have a mesh and is not a group. if only_selectable and not node.isSelectable(): continue if not node.callDecoration("isSliceable") and not node.callDecoration("getLayerData") and not node.callDecoration("isGroup"): continue # Only remove nodes that are selectable. if node.getParent() and cast(SceneNode, node.getParent()).callDecoration("isGroup"): continue # Grouped nodes don't need resetting as their parent (the group) is resetted) nodes.append(node) if nodes: op = GroupedOperation() for node in nodes: op.addOperation(RemoveSceneNodeOperation(node)) # Reset the print information self.getController().getScene().sceneChanged.emit(node) op.push() Selection.clear() ## Get the MeshFileHandler of this application. def getMeshFileHandler(self) -> MeshFileHandler: return self._mesh_file_handler def getWorkspaceFileHandler(self) -> WorkspaceFileHandler: return self._workspace_file_handler @pyqtSlot(result = QObject) def getPackageManager(self) -> PackageManager: return self._package_manager ## Gets the instance of this application. # # This is just to further specify the type of Application.getInstance(). # \return The instance of this application. @classmethod def getInstance(cls, *args, **kwargs) -> "QtApplication": return cast(QtApplication, super().getInstance(**kwargs)) def _createSplashScreen(self) -> QSplashScreen: return QSplashScreen(QPixmap(Resources.getPath(Resources.Images, self.getApplicationName() + ".png"))) def _screenScaleFactor(self) -> float: # OSX handles sizes of dialogs behind our backs, but other platforms need # to know about the device pixel ratio if sys.platform == "darwin": return 1.0 else: # determine a device pixel ratio from font metrics, using the same logic as UM.Theme fontPixelRatio = QFontMetrics(QCoreApplication.instance().font()).ascent() / 11 # round the font pixel ratio to quarters fontPixelRatio = int(fontPixelRatio * 4) / 4 return fontPixelRatio
class MainWindow(QWidget): def __init__(self): super().__init__() self.running = False self.setWindowTitle('PySwicher v{}'.format(VERSION)) # Logging config self.log_textbox = QPlainTextEditLogger(self) logging.getLogger().addHandler(self.log_textbox) self.log_textbox.setFormatter(logging.Formatter('[%(asctime)s][%(levelname)s]: %(message)s')) self.log_textbox.setLevel(self.get_numeric_loglevel(options['log_level'])) self.log_to_file = False # System tray configuration self.tray_menu = QMenu(self) self.systemTrayIcon = QSystemTrayIcon() self.systemTrayIcon.setVisible(False) self.systemTrayIcon.setIcon(QtGui.QIcon('C:\\Users\\Admin\\Pictures\\tray_stop.jpg')) self.systemTrayIcon.activated.connect(self.sys_tray) self.exit_action = self.tray_menu.addAction('Exit') self.exit_action.triggered.connect(self.exit_app) self.systemTrayIcon.setContextMenu(self.tray_menu) self.click_tray_timer = QtCore.QTimer(self) # Fix for systemtray click trigger self.click_tray_timer.setSingleShot(True) self.click_tray_timer.timeout.connect(self.click_timeout) self.main_window_ui() self.starter() def set_log_to_file(self, state): logger = logging.getLogger(__name__) file = logging.FileHandler(HOME + '\\switcher.log') if state == QtCore.Qt.Checked: self.log_to_file = True file.setFormatter( logging.Formatter('%(filename)s[LINE:%(lineno)d]# %(levelname)-8s [%(asctime)s] %(message)s')) file.setLevel(self.get_numeric_loglevel(options['log_level'])) logger.addHandler(file) else: if 'file' in logger.handlers: logger.removeHandler(file) self.log_to_file = False def starter(self): if not self.running: self.running = True self.start_btn.setText('Stop switcher') self.systemTrayIcon.setIcon(QtGui.QIcon('C:\\Users\\Admin\\Pictures\\tray_stop.jpg')) start_app() elif self.running: self.running = False self.start_btn.setText('Start switcher') self.systemTrayIcon.setIcon(QtGui.QIcon('C:\\Users\\Admin\\Pictures\\tray_start.jpg')) stop_app() return def main_window_ui(self): grid = QGridLayout(self) self.setLayout(grid) grid.setSpacing(5) # Here goes options layout self.topleft = QFrame(self) self.topleft.setFrameShape(QFrame.StyledPanel) self.topleft_grid = QGridLayout(self) self.topleft.setLayout(self.topleft_grid) self.switch_comb_label = QLabel('Switch combination:') self.switch_comb_text = QLineEdit() self.switch_comb_text.setText(options['switch_combination']) self.hotkey_label = QLabel('Hotkey:') self.hotkey_comb_text = QLineEdit() self.hotkey_comb_text.setText(options['hotkey']) self.topleft_grid.addWidget(self.switch_comb_label, 0, 0) self.topleft_grid.addWidget(self.switch_comb_text, 1, 0) self.topleft_grid.addWidget(self.hotkey_label, 2, 0) self.topleft_grid.addWidget(self.hotkey_comb_text, 3, 0) grid.addWidget(self.topleft, 0, 0) self.topright = QFrame(self) self.topright.setFrameShape(QFrame.StyledPanel) self.topright_grid = QGridLayout(self) self.topright.setLayout(self.topright_grid) self.info_label = QLabel('===INFO===') self.info_label.setAlignment(QtCore.Qt.AlignHCenter) self.info_author = QLabel('Author: Kurashov Sergey') self.info_author.setAlignment(QtCore.Qt.AlignHCenter) self.info_contacts = QLabel('Contacts: [email protected]') self.info_contacts.setAlignment(QtCore.Qt.AlignHCenter) self.info_sourcecode = QLabel('<a href="https://github.com/shimielder/win_switcher">Sourcecode on GitHub</a>') self.info_sourcecode.setAlignment(QtCore.Qt.AlignHCenter) self.info_sourcecode.setOpenExternalLinks(True) self.topright_grid.addWidget(self.info_label, 0, 0) self.topright_grid.addWidget(self.info_author, 1, 0) self.topright_grid.addWidget(self.info_contacts, 2, 0) self.topright_grid.addWidget(self.info_sourcecode, 3, 0) grid.addWidget(self.topright, 0, 1) self.middle = QFrame(self) self.middle.setFrameShape(QFrame.StyledPanel) self.middle_grid = QGridLayout(self) self.middle.setLayout(self.middle_grid) self.dictionsries_label = QLabel('Dictionaries to switch:') self.dict_one = QPlainTextEdit() self.dict_one.clear() self.dict_one.appendPlainText(options['layouts'][0]) self.dict_two = QPlainTextEdit() self.dict_two.clear() self.dict_two.appendPlainText(options['layouts'][1]) self.middle_grid.addWidget(self.dictionsries_label, 0, 0, 1, 4) self.middle_grid.addWidget(self.dict_one, 1, 0, 1, 4) self.middle_grid.addWidget(self.dict_two, 2, 0, 1, 4) grid.addWidget(self.middle, 1, 0, 1, 2) self.bottom = QFrame(self) self.bottom.setFrameShape(QFrame.StyledPanel) self.bottom_grid = QGridLayout(self) self.bottom.setLayout(self.bottom_grid) self.loglevel_label = QLabel('Logging level:') self.loglevel_dropmenu = QComboBox(self) self.loglevel_dropmenu.addItems(LOGGING_LEVELS) self.loglevel_dropmenu.setCurrentIndex(LOGGING_LEVELS.index((options['log_level'].upper()))) self.loglevel_dropmenu.activated[str].connect(self.set_logginglevel) # self.log_to_file_label = QLabel('Check to save logs to file') self.log_to_file_chk = QCheckBox('Check to save logs to file') self.log_to_file_chk.stateChanged.connect(self.set_log_to_file) self.logging_output_label = QLabel('Logging output:') self.logging_output_label.setAlignment(QtCore.Qt.AlignHCenter) self.bottom_grid.addWidget(self.loglevel_label, 0, 0) self.bottom_grid.addWidget(self.loglevel_dropmenu, 0, 1) self.bottom_grid.addWidget(self.log_to_file_chk, 0, 3, 1, 1) self.bottom_grid.addWidget(self.logging_output_label, 1, 0, 1, 4) self.bottom_grid.addWidget(self.log_textbox.widget, 2, 0, 2, 4) grid.addWidget(self.bottom, 2, 0, 1, 2) self.bottom_buttons = QFrame(self) # self.bottom_buttons.setFrameShape(QFrame.StyledPanel) self.bottom_buttons_grid = QGridLayout(self) self.bottom_buttons.setLayout(self.bottom_buttons_grid) self.start_btn = QPushButton('Start switcher') self.start_btn.clicked.connect(self.starter) self.apply_btn = QPushButton('Apply changes') self.apply_btn.clicked.connect(self.apply) self.exit_btn = QPushButton('Exit app') self.exit_btn.clicked.connect(self.exit_app) self.bottom_buttons_grid.addWidget(self.start_btn, 0, 0) self.bottom_buttons_grid.addWidget(self.apply_btn, 0, 1) self.bottom_buttons_grid.addWidget(self.exit_btn, 0, 2) grid.addWidget(self.bottom_buttons, 3, 0, 1, 2) self.resize(480, 320) self.show() def get_numeric_loglevel(self, loglevel): numeric_level = getattr(logging, loglevel.upper(), None) if not isinstance(numeric_level, int): numeric_level = 0 return numeric_level def set_logginglevel(self, loglevel): return self.log_textbox.setLevel(self.get_numeric_loglevel(loglevel)) @QtCore.pyqtSlot(QSystemTrayIcon.ActivationReason) def sys_tray(self, reason): """ По-умолчанию, trigger срабатывает всегда. Для обхода этого я повесил таймер на событие. Взято отсюда: https://riverbankcomputing.com/pipermail/pyqt/2010-November/028394.html """ if reason == QSystemTrayIcon.Trigger: self.click_tray_timer.start(QApplication.doubleClickInterval()) elif reason == QSystemTrayIcon.DoubleClick: self.click_tray_timer.stop() if self.isHidden(): self.showNormal() self.systemTrayIcon.setVisible(False) def click_timeout(self): self.starter() def apply(self): self.starter() options['layouts'] = [] options['layouts'].append(self.dict_one.toPlainText()) options['layouts'].append(self.dict_two.toPlainText()) options['log_level'] = LOGGING_LEVELS[self.loglevel_dropmenu.currentIndex()] self.set_logginglevel(options['log_level']) options['switch_combination'] = self.switch_comb_text.text() options['hotkey'] = self.hotkey_comb_text.text() logging.debug('Options from GUI: {}'.format(options)) save_to(options) self.starter() return options def exit_app(self): if self.running: self.starter() self.systemTrayIcon.setVisible(False) self.destroy() def closeEvent(self, event): self.exit_app() def changeEvent(self, event): if event.type() == QtCore.QEvent.WindowStateChange: if self.windowState() & QtCore.Qt.WindowMinimized: event.ignore() self.hide() self.systemTrayIcon.setVisible(True) self.systemTrayIcon.showMessage('', 'Running in the background.') super(MainWindow, self).changeEvent(event)
class Onkyo(QMainWindow): def __init__(self): super(Onkyo, self).__init__() self.initUI() self.settings = QSettings('onkyoqt', 'settings') self.host = self.settings.value('host', type=str) self.receiver = eiscp.eISCP(self.host) def btn_inputpc_click(self): try: self.receiver.command('input-selector=pc') except Exception as e: self.createTrayError(e) def btn_inputusb_click(self): try: self.receiver.command('input-selector=usb') except Exception as e: self.createTrayError(e) def btn_volumeup_click(self): try: volume_now = self.receiver.command('volume=level-up') self.sendTextToStatusBar(volume_now) except Exception as e: self.createTrayError(e) def btn_volumedown_click(self): try: volume_now = self.receiver.command('volume=level-down') self.sendTextToStatusBar(volume_now) except Exception as e: self.createTrayError(e) def btn_usbstop_click(self): try: self.receiver.command('network-usb=stop') except: pass def btn_usbplay_click(self): try: self.receiver.command('network-usb=play') except: pass def btn_usbpause_click(self): try: self.receiver.command('network-usb=pause') except: pass def btn_usbprew_click(self): try: self.receiver.command('network-usb=trdn') except: pass def btn_usbnext_click(self): try: self.receiver.command('network-usb=trup') except: pass def btn_poweroff_click(self): try: self.receiver.command('system-power=standby') except Exception as e: pass def btn_mute_click(self): try: volume_now = self.receiver.command('volume=0') self.sendTextToStatusBar(volume_now) except Exception as e: self.createTrayError(e) def initUI(self): self.settings = QSettings('onkyoqt', 'settings') self.hide_window = self.settings.value('hide') or 'False' self.hide_window = eval(self.hide_window) self.btn_inputpc = QPushButton('PC', self) self.btn_inputusb = QPushButton('USB', self) self.btn_volumeup = QPushButton('Vol +', self) self.btn_volumedown = QPushButton('Vol -', self) self.btn_usbstop = QPushButton('Stop', self) self.btn_usbplay = QPushButton('Play', self) self.btn_usbpause = QPushButton('Pause', self) self.btn_usbprew = QPushButton('Prew', self) self.btn_usbnext = QPushButton('Next', self) self.btn_poweroff = QPushButton('Power OFF', self) self.btn_mute = QPushButton('Mute', self) self.btn_quit = QPushButton('Quit', self) self.btn_inputpc.clicked.connect(self.btn_inputpc_click) self.btn_inputusb.clicked.connect(self.btn_inputusb_click) self.btn_volumeup.clicked.connect(self.btn_volumeup_click) self.btn_volumedown.clicked.connect(self.btn_volumedown_click) self.btn_usbstop.clicked.connect(self.btn_usbstop_click) self.btn_usbplay.clicked.connect(self.btn_usbplay_click) self.btn_usbpause.clicked.connect(self.btn_usbpause_click) self.btn_usbprew.clicked.connect(self.btn_usbprew_click) self.btn_usbnext.clicked.connect(self.btn_usbnext_click) self.btn_poweroff.clicked.connect(self.btn_poweroff_click) self.btn_quit.clicked.connect(QApplication.instance().quit) self.btn_mute.clicked.connect(self.btn_mute_click) self.mainWidget = QWidget(self) self.mainLayout = QGridLayout(self.mainWidget) self.mainLayout.addWidget(self.btn_inputpc, 0, 0, 1, 2) self.mainLayout.addWidget(self.btn_volumeup, 0, 3, 1, 2) self.mainLayout.addWidget(self.btn_inputusb, 1, 0, 1, 2) self.mainLayout.addWidget(self.btn_volumedown, 1, 3, 1, 2) self.mainLayout.addWidget(self.btn_usbstop, 2, 0) self.mainLayout.addWidget(self.btn_usbplay, 2, 1) self.mainLayout.addWidget(self.btn_usbpause, 2, 2) self.mainLayout.addWidget(self.btn_usbprew, 2, 3) self.mainLayout.addWidget(self.btn_usbnext, 2, 4) self.mainLayout.addWidget(self.btn_poweroff, 4, 0, 1, 2) self.mainLayout.addWidget(self.btn_mute, 3, 3, 1, 2) self.mainLayout.addWidget(self.btn_quit, 4, 3, 1, 2) self.mainWidget.setLayout(self.mainLayout) self.setCentralWidget(self.mainWidget) self.setWindowIcon(QIcon('/usr/share/icons/onkyo.png')) self.setFixedSize(280, 200) self.setWindowTitle('Onkyo Control') if self.hide_window: self.hide() else: self.show() self.createActions() self.createTrayIcon() self.createShortcuts() self.trayIcon.show() self.statusBar() self.createMenu() def createShortcuts(self): self.shortcut = QShortcut(QKeySequence("Ctrl+Q"), self) self.shortcut.activated.connect(QApplication.instance().quit) self.shortcut = QShortcut(QKeySequence("Ctrl+M"), self) self.shortcut.activated.connect(self.btn_mute_click) def createActions(self): self.minimizeAction = QAction("Mi&nimize", self, triggered=self.hide) self.maximizeAction = QAction("Ma&ximize", self, triggered=self.showMaximized) self.restoreAction = QAction(QIcon('/usr/share/icons/onkyo_restore.png'), "&Restore", self, triggered=self.showNormal) self.quitAction = QAction(QIcon('/usr/share/icons/onkyo_exit.png'), "&Quit", self, triggered=QApplication.instance().quit) self.PoweroffAction = QAction(QIcon('/usr/share/icons/onkyo_poweroff.png'), "&Power OFF", self, triggered=self.btn_poweroff_click) self.MuteAction = QAction(QIcon('/usr/share/icons/onkyo_mute.png'), "&Mute", self, triggered=self.btn_mute_click) self.VolumeupAction = QAction(QIcon('/usr/share/icons/onkyo_volumeup.png'), "&Volume UP", self, triggered=self.btn_volumeup_click) self.VolumedownAction = QAction(QIcon('/usr/share/icons/onkyo_volumedown.png'), "&Volume Down", self, triggered=self.btn_volumedown_click) self.aboutAction = QAction(QIcon('/usr/share/icons/onkyo_about.png'),"&About", self, triggered=self.about) self.settingsAction = QAction(QIcon('/usr/share/icons/onkyo_settings.png'), "&Settings", self, triggered=self.config) def createTrayIcon(self): self.trayIconMenu = QMenu(self) self.trayIconMenu.addAction(self.restoreAction) self.trayIconMenu.addSeparator() self.trayIconMenu.addAction(self.VolumeupAction) self.trayIconMenu.addAction(self.VolumedownAction) self.trayIconMenu.addAction(self.MuteAction) self.trayIconMenu.addSeparator() self.trayIconMenu.addAction(self.PoweroffAction) self.trayIconMenu.addSeparator() self.trayIconMenu.addAction(self.aboutAction) self.trayIconMenu.addAction(self.quitAction) self.trayIcon = QSystemTrayIcon(QIcon("/usr/share/icons/onkyo.png"), self) self.trayIcon.setContextMenu(self.trayIconMenu) def createMenu(self): menubar = self.menuBar() fileMenu = menubar.addMenu('&File') fileMenu.addAction(self.settingsAction) fileMenu.addAction(self.quitAction) helpMenu = menubar.addMenu('&Help') helpMenu.addAction(self.aboutAction) def sendTextToStatusBar(self, text): self.statusBar().showMessage(str(text)) def createTrayError(self, e): return self.trayIcon.showMessage('Error', 'Send command to receiver failed:\n' + str(e), QSystemTrayIcon.Critical, 5 * 1000) def about(self): title = self.tr("""<b>Onkyo QT</b> <br/>License: GPLv3 <br/>Python {0} - on {1}""").format(platform.python_version(), platform.system()) image = ':/logo' text = self.tr("""<p>Author: Andry Kondratiev <a href="mailto:[email protected]">[email protected]</a> <p>Website: <a href="https://github.com/massdest/onkyoqtpy"> https://github.com/massdest/onkyoqtpy</a> """) contributors = QCoreApplication.translate("About dialog", """ Author: Andry Kondratiev<br/> """, "List of contributors") dialog = about_dlg.AboutDialog(title, text, image, contributors, self) dialog.exec_() def config(self): dialog = settings.OnkyoSettings(self) dialog.applied_signal.connect(self.config_save) if dialog.exec_() == 1: self.config_save() def config_save(self): logging.debug('Config saving...') self.host = self.settings.value('host')
class MainApp(QMainWindow, Ui_MainWindow): _translate = QCoreApplication.translate tab_list = [] # TODO - add dutch translation files def __init__(self, isolated, *args): super(MainApp, self).__init__(*args) Lumberjack.info('spawning the <<< MainApp >>> hey says: I am the Main man here see!') self.load_settings() self.setup_tray(isolated) self.dbhelper = DbHelper() self.setupUi(self) self.iconize_controls() self.load_styling() self.tabWidget = QTabWidget(self.centralwidget) self.tabWidget.setTabsClosable(True) self.tabWidget.setMovable(True) self.tabWidget.setTabBarAutoHide(True) self.tabWidget.setObjectName("tabWidget") self.verticalLayout.addWidget(self.tabWidget) builderLabel = QLabel('made by: MazeFX Solutions') self.statusbar.addPermanentWidget(builderLabel) self.menuPAT.triggered.connect(self.handle_menu_event) self.menuLists.triggered.connect(self.handle_menu_event) self.menuHelp.triggered.connect(self.handle_menu_event) self.tabWidget.tabCloseRequested.connect(self.close_tab) self.actionHome.trigger() self._retranslateUi(self) def iconize_controls(self): Lumberjack.info('< MainApp > - -> (iconize_controls)') homeIcon = qta.icon('fa.home', color='white') self.actionHome.setIcon(homeIcon) wrenchIcon = qta.icon('fa.wrench', color='white') self.actionSettings.setIcon(wrenchIcon) bankIcon = qta.icon('fa.bank', color='white') self.actionListBankAccounts.setIcon(bankIcon) contractIcon = QIcon(':/app_icons/rc/handshake_icon.svg') self.actionListContracts.setIcon(contractIcon) atIcon = qta.icon('fa.at', color='white') self.actionListEmailAddresses.setIcon(atIcon) envelopeIcon = qta.icon('fa.envelope', color='white') self.actionListLetters.setIcon(envelopeIcon) relationIcon = qta.icon('fa.group', color='white') self.actionListRelations.setIcon(relationIcon) transactionIcon = qta.icon('fa.money', color='white') self.actionListTransactions.setIcon(transactionIcon) userIcon = qta.icon('fa.user', color='white') self.actionListUsers.setIcon(userIcon) helpIcon = qta.icon('fa.question', color='white') self.actionHelp.setIcon(helpIcon) aboutIcon = qta.icon('fa.info', color='white') self.actionAbout.setIcon(aboutIcon) def setup_tray(self, isolated): Lumberjack.info('< MainApp > - -> (setup_tray)') self.trayIcon = QSystemTrayIcon(QIcon(':/app_icons/rc/PAT_icon.png'), self) self.trayMenu = QMenu(self) showAction = self.trayMenu.addAction("Open PAT") self.trayMenu.addSeparator() exitAction = self.trayMenu.addAction("Exit") self.trayIcon.setContextMenu(self.trayMenu) self.trayMenu.triggered.connect(self.handle_tray_event) self.trayIcon.activated.connect(self.handle_tray_event) self.trayIcon.show() if isolated: self.trayIcon.showMessage('PAT Service', 'PAT service is now running..') def handle_tray_event(self, *args): Lumberjack.info('< MainApp > - -> (handle_tray_event)') print(Fore.MAGENTA + '$! Received a tray action with args: ', args) if args[0] == 3: self.show() return elif hasattr(args[0], 'text'): print(Fore.MAGENTA + '$! Tray event has text!!') if args[0].text() == 'Open PAT': self.show() elif args[0].text() == 'Exit': self.close() def _retranslateUi(self, MainWindow): pass def handle_menu_event(self, *args): Lumberjack.info('< MainApp > - -> (handle_menu_event)') Lumberjack.debug('(handle_menu_event) - args = {}'.format(args)) action_text = args[0].text() icon = args[0].icon() Lumberjack.debug('(handle_menu_event) - Action text selector = {}'.format(action_text)) print(Fore.MAGENTA + '$! Action text received: ', action_text) if action_text == 'Home': Lumberjack.info('(handle_menu_event) >User action> : Adding Home tab to self') self.add_tab(HomeTab, 'Home', icon) if action_text == 'Settings': Lumberjack.info('(handle_menu_event) >User action> : Showing settings dialog') self.show_settings() elif action_text == 'Bank accounts': Lumberjack.info('(handle_menu_event) >User action> : Adding Bank account List tab to self') self.add_tab(BankAccountListTab, 'Bank accounts', icon) elif action_text == 'Contracts': Lumberjack.info('(handle_menu_event) >User action> : Adding Contracts List tab to self') self.add_tab(ContractListTab, 'Contracts', icon) elif action_text == 'Email addresses': Lumberjack.info('(handle_menu_event) >User action> : Adding EmailAddress List tab to self') self.add_tab(EmailAddressListTab, 'Email addresses', icon) elif action_text == 'Letters': Lumberjack.info('(handle_menu_event) >User action> : Adding Letter List tab to self') self.add_tab(LetterListTab, 'Letters', icon) elif action_text == 'Users': Lumberjack.info('(handle_menu_event) >User action> : Adding User List tab to self') self.add_tab(UserListTab, 'Users', icon) elif action_text == 'Relations': Lumberjack.info('(handle_menu_event) >User action> : Adding Relation List tab to self') self.add_tab(RelationListTab, 'Relations', icon) elif action_text == 'Transactions': Lumberjack.info('(handle_menu_event) >User action> : Adding Transaction List tab to self') self.add_tab(TransactionListTab, 'Transactions', icon) elif action_text == 'Help': Lumberjack.info('(handle_menu_event) >User action> : Showing help dialog') # TODO - build help dialog and help files elif action_text == 'About': Lumberjack.info('(handle_menu_event) >User action> : Showing about dialog') # TODO build About dialog. def show_settings(self): Lumberjack.info('< MainApp > - -> (show_settings)') settings_dialog = SettingsDialog() settings_dialog.exec_() def add_tab(self, tab_cls, tab_name, icon): Lumberjack.info('< MainApp > - -> (add_tab)') new_tab = tab_cls(self.dbhelper) print(Fore.MAGENTA + 'Adding a tab with class: ', str(tab_cls)) new_tab.setObjectName(str(tab_cls)) self.tabWidget.addTab(new_tab, icon, self._translate("MainWindow", tab_name)) print(Fore.MAGENTA + 'New tab added to tab list.') self.tabWidget.setCurrentIndex(self.tabWidget.indexOf(new_tab)) self.tab_list.append(new_tab) def close_tab(self, index): # TODO - Check if index stays correct when moving tabs around requesting_tab = self.tab_list[index] print(Fore.MAGENTA + 'requesting tab is: ', requesting_tab) if hasattr(requesting_tab, 'form'): if requesting_tab.form.edit_mode: print(Fore.MAGENTA + 'Tab is in edit mode.') requesting_tab.form.toggle_edit_mode(False, None, None) if requesting_tab.form.edit_mode is None: print(Fore.MAGENTA + 'Tab is now in equil.') self.tabWidget.removeTab(index) del self.tab_list[index] else: self.tabWidget.removeTab(index) del self.tab_list[index] def load_settings(self): self.settings = QSettings() db_path = self.settings.value('db_base_path') db_name = self.settings.value('db_name') if db_path is not None and db_name is not None: db_file = os.path.join(db_path, db_name) Lumberjack.debug('__init__ - db_file = {}'.format(db_file)) if os.path.exists(db_file): return Lumberjack.warning('(load_settings) - database not found') settings_dialog = SettingsDialog() settings_dialog.exec_() int_value = self.settings.value('db_type', type=int) print(Fore.MAGENTA + "load choosen database setting: %s" % repr(int_value)) def load_styling(self): style.set_window_style(self) def closeEvent(self, event): print(Fore.MAGENTA + "User has clicked the red x on the main window") for tab in self.tab_list: if hasattr(tab, 'form'): if tab.form.edit_mode: print(Fore.MAGENTA + 'Tab is in edit mode.') tab.form.toggle_edit_mode(False, None, None) close_dialog = CloseDialog() result = close_dialog.exec_() if result == close_dialog.Minimize: self.hide() event.ignore() elif result == close_dialog.Rejected: event.ignore() elif result == close_dialog.Exit: print(Fore.MAGENTA + "Exiting via save dialog, result = ", result) self.trayIcon.hide() event.accept()
class 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 Example(QMainWindow): def __init__(self): super().__init__() self.initUI() self.trayIcon = None self.kill = False self.setupTrayIcon() try: cherrypy_server.start_server() except Exception as e: logging.getLogger(__name__).warn("Problem starting print server! {}".format(e)) traceback.print_exc() def reallyClose(self): self.kill = True self.close() def openConsole(self): from web.server import cfg url = r'https://localhost:{}'.format(cfg.port) webbrowser.open(url) def initUI(self): openAction = QAction("&Open Console", self) openAction.setShortcut('Ctrl+O') openAction.setStatusTip('Open Console') openAction.triggered.connect(self.openConsole) exitAction = QAction(QIcon(os.path.join('web', 'static', 'img', 'exit-icon-3.png')), '&Exit', self) exitAction.setShortcut('Ctrl+Q') exitAction.setStatusTip('Exit application') exitAction.triggered.connect(self.reallyClose) self.statusBar() menubar = self.menuBar() fileMenu = menubar.addMenu('&File') fileMenu.addAction(openAction) fileMenu.addSeparator() fileMenu.addAction(exitAction) wrapper = QWidget() txt = QTextEdit() txt.setReadOnly(True) txt.setLineWrapMode(QTextEdit.NoWrap) txt.setUndoRedoEnabled(False) txt.setAcceptDrops(False) txt.setAcceptRichText(False) font = txt.font() font.setFamily("Courier") font.setPointSize(9) txt.setFont(font) policy = txt.sizePolicy() policy.setVerticalStretch(1) txt.setSizePolicy(policy) layout = QGridLayout() layout.addWidget(txt) wrapper.setLayout(layout) self.setCentralWidget(wrapper) def _write(s): txt.moveCursor(QTextCursor.End) txt.insertPlainText(str(s)) txt.moveCursor(QTextCursor.End) setup_logging(_write) self.setGeometry(300, 300, 800, 600) self.setWindowTitle('Antix Print Server') def setupTrayIcon(self): _icon = QIcon(os.path.join('web', 'static', 'img', 'Logo.144x144.png')) self.trayIcon = QSystemTrayIcon(_icon, self) menu = QMenu(self) menu.addAction("Show", self.show) menu.addAction("Hide", self.hide) menu.addAction("Exit", self.reallyClose) self.trayIcon.setContextMenu(menu) self.trayIcon.show() self.trayIcon.showMessage("Antix Printer Server", "Is running") def closeEvent(self, evnt): if self.kill: self.trayIcon.hide() try: cherrypy_server.stop_server() except Exception as e: logging.getLogger(__name__).warn("Problem stopping server! {}".format(e)) qApp.exit(0) else: evnt.ignore() self.hide()
class MainWindow(QtWidgets.QMainWindow, MainF): def __init__(self): super().__init__() self.setupUi(self) global f1, f2, f3, f4 f1 = False f2 = True f3 = False f4 = True def setDirectory(event): dirlist = QFileDialog.getExistingDirectory(self, "Выбрать папку", ".") self.dirLineEdit.setText(dirlist) # self.plainTextEdit.appendHtml("<br>Выбрали папку: <b>{}</b>".format(dirlist)) self.dirLineEdit.mouseDoubleClickEvent = setDirectory self.SearchButton.clicked.connect(lambda: self.Search()) # self.pushButton.clicked.connect(lambda: self.pr()) self.action_help.triggered.connect(lambda: self.help()) self.action_settings.triggered.connect(lambda: self.settings()) self.tray_icon = QSystemTrayIcon(self) self.tray_icon.setIcon(self.style().standardIcon( QStyle.SP_ComputerIcon)) @pyqtSlot() def action(signal): if signal == 2: self.showNormal() self.tray_icon.activated.connect(action) quit_action = QAction("Exit", self) quit_action.triggered.connect(app.quit) tray_menu = QMenu() tray_menu.addAction(quit_action) self.tray_icon.setContextMenu(tray_menu) self.tray_icon.show() def closeEvent(self, event): if f1: event.ignore() self.hide() self.tray_icon.showMessage("Tray Program", "Application was minimized to Tray", QSystemTrayIcon.Information, 2000) def help(self): self.h = Help() self.h.show() def settings(self): self.s = Settings() self.s.show() def Search(self): global filelist, dir dir = self.dirLineEdit.text() word = self.wordLineEdit.text() txt = "" if dir == "": QMessageBox.critical(self, "Ошибка ", "Введите директроию", QMessageBox.Ok) else: try: filelist = [] log = open("log.txt", 'w') log.write("Remover log:\n") log.write("Dir: " + dir + "\nWord: " + word + "\n\n") print(os.listdir(dir)) for f in os.listdir(dir): log.write(f) if f.find(word) != -1: print(f) filelist.append(f) log.write(" " * (20 - len(f)) + "Содержит в названии " + word + "\n") else: if f.endswith(".txt"): # print(f) file = open(dir + "/" + f, 'r') if file.read().find(word) != -1: filelist.append(f) log.write(" " * (20 - len(f)) + "В файле найдено " + word + "\n") else: log.write("\n") # pass else: log.write("\n") # pass # log.close() self.listw = ListWindow(self) self.listw.show() except: QMessageBox.critical(self, "Ошибка ", "Такой дириктории нет", QMessageBox.Ok)
class Window(QMainWindow): """ Main GUI class for application """ def __init__(self): QWidget.__init__(self) # loaind ui from xml uic.loadUi(os.path.join(DIRPATH, 'app.ui'), self) # self.show_msgbox("Info", "Lan Messenger") self.users = {} self.host = socket.gethostname() self.ip = get_ip_address() # button event handlers self.btnRefreshBuddies.clicked.connect(self.refreshBuddies) self.btnSend.clicked.connect(self.sendMsg) self.lstBuddies.currentItemChanged.connect( self.on_buddy_selection_changed) self.msg_manager = MessageManager() self.msg_sender = MessageSender(self.host, self.ip) self.message_listener = MessageListener() self.message_listener.message_received.connect(self.handle_messages) self.send_IAI() self.setup_tray_menu() # setting up handlers for menubar actions self.actionAbout.triggered.connect(self.about) self.actionExit.triggered.connect(qApp.quit) self.actionPreferences.triggered.connect(self.show_preferences) def about(self): print("about") ad = AboutDialog() ad.display() def show_preferences(self): print("preferences") pd = PrefsDialog() pd.display() def setup_tray_menu(self): # setting up QSystemTrayIcon self.tray_icon = QSystemTrayIcon(self) self.tray_icon.setIcon( self.style().standardIcon(QStyle.SP_ComputerIcon)) # tray actions show_action = QAction("Show", self) quit_action = QAction("Exit", self) hide_action = QAction("Hide", self) # action handlers show_action.triggered.connect(self.show) hide_action.triggered.connect(self.hide) quit_action.triggered.connect(qApp.quit) # tray menu tray_menu = QMenu() tray_menu.addAction(show_action) tray_menu.addAction(hide_action) tray_menu.addAction(quit_action) self.tray_icon.setContextMenu(tray_menu) self.tray_icon.show() def closeEvent(self, event): event.ignore() self.hide() self.tray_icon.showMessage( "PyLanMessenger", "PyLanMessenger was minimized to Tray", QSystemTrayIcon.Information, 2000 ) def handle_messages(self, data): log.debug("UI handling message: %s" % data) pkt = Packet() pkt.json_to_obj(data) if pkt.op == "IAI": self.handle_IAI(pkt.ip, pkt.host) if pkt.op == "MTI": self.handle_MTI(pkt.ip, pkt.host) if pkt.op == "TCM": self.handle_TCM(pkt.ip, pkt.host, pkt.msg) def send_IAI(self): # broadcast a message that IAI - "I Am In" the n/w pkt = Packet(op="IAI", ip=self.ip, host=self.host).to_json() self.msg_sender.send_broadcast_message(pkt) def send_MTI(self): # broadcast a message that MTI - "Me Too In" the n/w pkt = Packet(op="MTI", ip=self.ip, host=self.host).to_json() self.msg_sender.send_broadcast_message(pkt) def handle_IAI(self, ip, host): """ handle "I am In" packet reply with MTI for IAI me too in when other says "i am in" """ self.send_MTI() if host not in self.users: print("adding host", host) self.users[host] = ip self.lstBuddies.addItem(str(host)) def handle_MTI(self, ip, host): """ handle Me Too In packet """ if host not in self.users: self.users[host] = ip self.lstBuddies.addItem(str(host)) def handle_TCM(self, ip, host, msg): self.add_chat_msg(ip, host, "%s: %s" % (host, msg)) def refreshBuddies(self): self.lstBuddies.clear() self.users = {} self.send_IAI() def sendMsg(self): try: receiver_host = self.lstBuddies.currentItem().text() except: log.warning("no host found from selection") return msg = self.teMsg.toPlainText() receiver_ip = self.users[receiver_host] # sending msg to receiver self.msg_sender.send_to_ip(receiver_ip, receiver_host, msg.strip()) # adding my message in chat area in UI self.add_chat_msg( receiver_ip, receiver_host, "%s: %s" % (self.host, msg)) # cleaning up textbox for typed message in UI self.teMsg.setText("") def add_chat_msg(self, ip, other_host, msg): self.msg_manager.add_chat_msg(ip, other_host, msg) # showing msg in UI self.teMsgsList.append(msg) def on_buddy_selection_changed(self): if self.lstBuddies.count() == 0: return # no buddy selected if not self.lstBuddies.currentItem(): return sel_user = self.lstBuddies.currentItem().text() log.debug("You selected buddy is: \"%s\"" % sel_user) self.teMsgsList.clear() msgs = self.msg_manager.get_message_for_user(sel_user) for m in msgs: self.teMsgsList.append(m) def show_msgbox(self, title, text): """ Function for showing error/info message box """ msg = QMessageBox() msg.setIcon(QMessageBox.Information) msg.setText(text) msg.setWindowTitle(title) msg.setStandardButtons(QMessageBox.Ok) retval = msg.exec_()