class Icon(QWidget): def __init__(self, app, newEntry, listWin): super().__init__() self.newEntry = newEntry self.listWin = listWin self.app = app self.initUI() def initUI(self): menu = QMenu() Ajouter = QAction(QIcon(''), '&Ajouter un tag', menu) Ajouter.triggered.connect(self.newEntry.show) menu.addAction(Ajouter) ouvrir = QAction(QIcon(''), '&Ouvrir', menu) ouvrir.triggered.connect(self.listWin.show) menu.addAction(ouvrir) Quitter = QAction(QIcon(''), '&Quitter', menu) Quitter.triggered.connect(self.app.exit) menu.addAction(Quitter) self.icon = QSystemTrayIcon() self.icon.setIcon(QIcon('./icone.png')) self.icon.setContextMenu(menu) self.icon.show()
class Systray(QObject): trayIconMenu = None def __init__(self, parent): super().__init__(parent) self.trayIconMenu = ContextMenu(None) icon = QIcon(":/image/thunder.ico") self.trayIcon = QSystemTrayIcon(self) self.trayIcon.setIcon(icon) self.trayIcon.setContextMenu(self.trayIconMenu) self.trayIcon.setVisible(True) self.trayIcon.activated.connect(self.slotSystrayActivated) @pyqtSlot(QSystemTrayIcon.ActivationReason) def slotSystrayActivated(self, reason): if reason == QSystemTrayIcon.Context: # right pass elif reason == QSystemTrayIcon.MiddleClick: # middle pass elif reason == QSystemTrayIcon.DoubleClick: # double click pass elif reason == QSystemTrayIcon.Trigger: # left if app.mainWin.isHidden() or app.mainWin.isMinimized(): app.mainWin.restore() else: app.mainWin.minimize()
def _createTray(self): from PyQt5.QtWidgets import QSystemTrayIcon from PyQt5.QtGui import QIcon from piony.common.system import expand_pj tray = QSystemTrayIcon() tray.setIcon(QIcon(expand_pj(":/res/tray-normal.png"))) tray.show() return tray
class Systray(QObject): app = None trayIconMenu = None def __init__(self, app): super().__init__() self.app = app self.mainWin = self.app.mainWin self.trayIconMenu = QMenu(None) icon = QIcon(":/image/thunder.ico") self.trayIcon = QSystemTrayIcon(None) self.trayIcon.setIcon(icon) self.trayIcon.setContextMenu(self.trayIconMenu) self.trayIcon.setVisible(True) self.trayIcon.activated.connect(self.slotSystrayActivated) self.app.lastWindowClosed.connect(self.slotTeardown) self.trayIconMenu.addAction(self.mainWin.action_exit) @pyqtSlot() def slotTeardown(self): print("teardown Systray") # On Ubuntu 13.10, systrayicon won't destroy itself gracefully, stops the whole program from exiting. del self.trayIcon @pyqtSlot(QSystemTrayIcon.ActivationReason) def slotSystrayActivated(self, reason): if reason == QSystemTrayIcon.Context: # right pass elif reason == QSystemTrayIcon.MiddleClick: # middle pass elif reason == QSystemTrayIcon.DoubleClick: # double click pass elif reason == QSystemTrayIcon.Trigger: # left if self.mainWin.isHidden() or self.mainWin.isMinimized(): self.mainWin.restore() else: self.mainWin.minimize()
class MainApp(QMainWindow): tray_icon = None active = False def __init__(self, parent=None): super(MainApp, self).__init__(parent) # Init notification notify2.init('webcam-manager') # Init tray icon self.tray_icon = QSystemTrayIcon(QIcon.fromTheme('camera-web'), parent) self.initTrayIcon() self.tray_icon.show() def initTrayIcon(self): # Menu actions toogle_action = QAction(self.tr('&Toogle'), self) toogle_action.triggered.connect(self.onToogle) about_action = QAction(self.tr('&About'), self) about_action.setIcon(QIcon.fromTheme("help-about")) about_action.triggered.connect(self.onAbout) quit_action = QAction(self.tr('&Exit'), self) quit_action.setIcon(QIcon.fromTheme("application-exit")) quit_action.triggered.connect(self.onQuit) tray_menu = QMenu() tray_menu.addAction(toogle_action) tray_menu.addSeparator() tray_menu.addAction(about_action) tray_menu.addAction(quit_action) self.tray_icon.setContextMenu(tray_menu) self.tray_icon.activated.connect(self.onToogle) output = int(self.execCommand('lsmod | grep uvcvideo | wc -l').split()[0]) if output > 0: self.updateTrayIcon(True) else: self.updateTrayIcon(False) def onToogle(self, widget): if self.active: self.disable() else: self.enable() def onQuit(self, widget): qApp.quit() def onAbout(self, widget): dialog = QDialog(self) aboutText = self.tr("""<p>A simple applet for enable/disable webcams.</p> <p>Website: <a href="https://github.com/abbarrasa/openbox"> https://github.com/abbarrasa/openbox</a></p> <p>Based in <a href="https://extensions.gnome.org/extension/1477/webcam-manager/">Webcam Manager</a>.</p> <p>If you want to report a dysfunction or a suggestion, feel free to open an issue in <a href="https://github.com/abbarrasa/openbox/issues"> github</a>.""") creditsText = self.tr("""(c) 2018 Alberto Buitrago <%s>""") % base64.b64decode('YWJiYXJyYXNhQGdtYWlsLmNvbQ==').decode('utf-8') licenseText = self.tr("""<p>This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.</p> <p>This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.</p> <p>You should have received a copy of the GNU General Public License along with this program. If not, see <a href="https://www.gnu.org/licenses/gpl-3.0.html"> GNU General Public License version 3</a>.</p>""") layout = QVBoxLayout() titleLayout = QHBoxLayout() titleLabel = QLabel('<font size="4"><b>{0} {1}</b></font>'.format('Webcam Manager', VERSION)) contentsLayout = QHBoxLayout() aboutBrowser = QTextBrowser() aboutBrowser.append(aboutText) aboutBrowser.setOpenExternalLinks(True) creditsBrowser = QTextBrowser() creditsBrowser.append(creditsText) creditsBrowser.setOpenExternalLinks(True) licenseBrowser = QTextBrowser() licenseBrowser.append(licenseText) licenseBrowser.setOpenExternalLinks(True) TabWidget = QTabWidget() TabWidget.addTab(aboutBrowser, self.tr('About')) TabWidget.addTab(creditsBrowser, self.tr('Contributors')) TabWidget.addTab(licenseBrowser, self.tr('License')) aboutBrowser.moveCursor(QTextCursor.Start) creditsBrowser.moveCursor(QTextCursor.Start) licenseBrowser.moveCursor(QTextCursor.Start) icon = QIcon.fromTheme('camera-web') pixmap = icon.pixmap(QSize(64, 64)) imageLabel = QLabel() imageLabel.setPixmap(pixmap) titleLayout.addWidget(imageLabel) titleLayout.addWidget(titleLabel) titleLayout.addStretch() contentsLayout.addWidget(TabWidget) buttonLayout = QHBoxLayout() buttonBox = QDialogButtonBox(QDialogButtonBox.Ok) buttonLayout.addWidget(buttonBox) layout.addLayout(titleLayout) layout.addLayout(contentsLayout) layout.addLayout(buttonLayout) buttonBox.clicked.connect(dialog.accept) dialog.setLayout(layout) dialog.setMinimumSize(QSize(480, 400)) dialog.setWindowTitle(self.tr('About Webcam Manager')) dialog.setWindowIcon(QIcon.fromTheme('help-about')) dialog.show() def enable(self): tool = self.getGUISudo() cmd = '%s modprobe -a uvcvideo' % tool self.execCommand(cmd) output = int(self.execCommand('lsmod | grep uvcvideo | wc -l').split()[0]) if output > 0: self.updateTrayIcon(True) self.showNotification('Webcam enabled!', 'Webcam is turned on and ready to use') def disable(self): tool = self.getGUISudo() cmd = '%s modprobe -r uvcvideo' % tool self.execCommand(cmd) output = int(self.execCommand('lsmod | grep uvcvideo | wc -l').split()[0]) if output == 0: self.updateTrayIcon(False) self.showNotification('Webcam disabled!', 'Webcam is turned off') def getGUISudo(self): tools = ['kdesu', 'lxqt-sudo', 'gksu', 'gksudo', 'pkexec', 'sudo'] for tool in tools: if distutils.spawn.find_executable(tool) is not None: return tool def execCommand(self, cmd): try: output = subprocess.check_output(cmd, shell=True) return output except subprocess.CalledProcessError as e: self.showNotification('Error!', e.output) def updateTrayIcon(self, active): self.active = active if self.active: self.tray_icon.setIcon(QIcon.fromTheme('camera-on')) self.tray_icon.setToolTip('Webcam is enabled') else: self.tray_icon.setIcon(QIcon.fromTheme('camera-off')) self.tray_icon.setToolTip('Webcam is disabled') def showNotification(self, title, message): n = notify2.Notification(title, message, 'camera-web') n.show()
class 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 Coropata(QLabel): clicked = pyqtSignal() def __init__(self): # Really not meant to have a parent yet QLabel.__init__(self, None) self.quitAction = None self.trayIconMenu = None self.trayIcon = None self.createTrayIcon() self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.BypassWindowManagerHint) self.setAttribute(Qt.WA_TranslucentBackground) self.m_hoverbox = Hoverbox() self.m_hoverbox.show() self.m_animation_timer = QTimer(self) self.m_animation_timer.setInterval(100) self.m_physics_timer = QTimer(self) self.m_physics_timer.setInterval(30) self.m_physics_timer.timeout.connect(self.physicsTimerEvent) self.screen_width = QDesktopWidget().screenGeometry().width() self.screen_height = QDesktopWidget().screenGeometry().height() # Initialize properties self.m_velocity = QPoint(0, 0) self.m_pos = QPoint(self.screen_width / 2, (5.0 / 6.0) * self.screen_height) self.m_offset = QPoint(0, 0) self.m_size = QSize(0, 0) self._velocity = self.m_velocity self._pos = self.m_pos self._offset = self.m_offset self._size = self.m_size self.stateMachine = xmlToStateMachine(self, 'coropata.xml') self.stateMachine.start() self.m_animation_timer.start() self.m_physics_timer.start() # Properties ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @pyqtProperty(QSize) def _size(self): return self.m_size @_size.write def _size(self, value): self.m_size = value self.setFixedSize(value) @pyqtProperty(QPoint) def _offset(self): return self.m_offset @_offset.write def _offset(self, value): self.m_offset = value self.move(self.m_pos + self.m_offset) @pyqtProperty(QPoint) def _pos(self): return self.m_pos @_pos.write def _pos(self, value): self.m_pos = value self.move(self.m_pos + self.m_offset) self.m_hoverbox.move(self.m_pos - QPoint(60, 70)) @pyqtProperty(QPoint) def _velocity(self): return self.m_velocity @_velocity.write def _velocity(self, value): self.m_velocity = value # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def mousePressEvent(self, e): self.clicked.emit() def closeEvent(self, e): QApplication.instance().closeAllWindows() def keyPressEvent(self, e): if e.key() == Qt.Key_Q or e.key() == Qt.Key_Escape: QApplication.instance().closeAllWindows() def physicsTimerEvent(self): p = self.m_pos + self.m_velocity if p.x() < -self._size.width(): p.setX(self.screen_width) elif p.x() > self.screen_width + 10: p.setX(-self._size.width()) self._pos = p def createTrayIcon(self): self.quitAction = QAction('Quit', self) self.quitAction.triggered.connect(self.closeEvent) self.trayIconMenu = QMenu(self) self.trayIconMenu.addAction(self.quitAction) self.trayIcon = QSystemTrayIcon(self) self.trayIcon.setContextMenu(self.trayIconMenu) self.trayIcon.setIcon(QIcon(os.path.join('images', 'flower.png'))) self.trayIcon.show()
class Qt4SysTrayIcon: def __init__(self): self.snapshots = snapshots.Snapshots() self.config = self.snapshots.config self.decode = None if len(sys.argv) > 1: if not self.config.setCurrentProfile(sys.argv[1]): logger.warning("Failed to change Profile_ID %s" %sys.argv[1], self) self.qapp = qt4tools.createQApplication(self.config.APP_NAME) translator = qt4tools.translator() self.qapp.installTranslator(translator) self.qapp.setQuitOnLastWindowClosed(False) import icon self.icon = icon self.qapp.setWindowIcon(icon.BIT_LOGO) self.status_icon = QSystemTrayIcon(icon.BIT_LOGO) #self.status_icon.actionCollection().clear() self.contextMenu = QMenu() self.menuProfileName = self.contextMenu.addAction(_('Profile: "%s"') % self.config.profileName()) qt4tools.setFontBold(self.menuProfileName) self.contextMenu.addSeparator() self.menuStatusMessage = self.contextMenu.addAction(_('Done')) self.menuProgress = self.contextMenu.addAction('') self.menuProgress.setVisible(False) self.contextMenu.addSeparator() self.btnPause = self.contextMenu.addAction(icon.PAUSE, _('Pause snapshot process')) action = lambda: os.kill(self.snapshots.pid(), signal.SIGSTOP) self.btnPause.triggered.connect(action) self.btnResume = self.contextMenu.addAction(icon.RESUME, _('Resume snapshot process')) action = lambda: os.kill(self.snapshots.pid(), signal.SIGCONT) self.btnResume.triggered.connect(action) self.btnResume.setVisible(False) self.btnStop = self.contextMenu.addAction(icon.STOP, _('Stop snapshot process')) self.btnStop.triggered.connect(self.onBtnStop) self.contextMenu.addSeparator() self.btnDecode = self.contextMenu.addAction(icon.VIEW_SNAPSHOT_LOG, _('decode paths')) self.btnDecode.setCheckable(True) self.btnDecode.setVisible(self.config.snapshotsMode() == 'ssh_encfs') self.btnDecode.toggled.connect(self.onBtnDecode) self.openLog = self.contextMenu.addAction(icon.VIEW_LAST_LOG, _('View Last Log')) self.openLog.triggered.connect(self.onOpenLog) self.startBIT = self.contextMenu.addAction(icon.BIT_LOGO, _('Start BackInTime')) self.startBIT.triggered.connect(self.onStartBIT) self.status_icon.setContextMenu(self.contextMenu) self.pixmap = icon.BIT_LOGO.pixmap(24) self.progressBar = QProgressBar() self.progressBar.setMinimum(0) self.progressBar.setMaximum(100) self.progressBar.setValue(0) self.progressBar.setTextVisible(False) self.progressBar.resize(24, 6) self.progressBar.render(self.pixmap, sourceRegion = QRegion(0, -14, 24, 6), flags = QWidget.RenderFlags(QWidget.DrawChildren)) self.first_error = self.config.notify() self.popup = None self.last_message = None self.timer = QTimer() self.timer.timeout.connect(self.updateInfo) self.ppid = os.getppid() def prepairExit(self): self.timer.stop() if not self.status_icon is None: self.status_icon.hide() self.status_icon = None if not self.popup is None: self.popup.deleteLater() self.popup = None self.qapp.processEvents() def run(self): self.status_icon.show() self.timer.start(500) logger.info("[qt4systrayicon] begin loop", self) self.qapp.exec_() logger.info("[qt4systrayicon] end loop", self) self.prepairExit() def updateInfo(self): if not tools.processAlive(self.ppid): self.prepairExit() self.qapp.exit(0) return paused = tools.processPaused(self.snapshots.pid()) self.btnPause.setVisible(not paused) self.btnResume.setVisible(paused) message = self.snapshots.takeSnapshotMessage() if message is None and self.last_message is None: message = (0, _('Working...')) if not message is None: if message != self.last_message: self.last_message = message if self.decode: message = (message[0], self.decode.log(message[1])) self.menuStatusMessage.setText('\n'.join(tools.wrapLine(message[1],\ size = 80,\ delimiters = '',\ new_line_indicator = '') \ )) self.status_icon.setToolTip(message[1]) pg = progress.ProgressFile(self.config) if pg.fileReadable(): pg.load() percent = pg.intValue('percent') if percent != self.progressBar.value(): self.progressBar.setValue(percent) self.progressBar.render(self.pixmap, sourceRegion = QRegion(0, -14, 24, 6), flags = QWidget.RenderFlags(QWidget.DrawChildren)) self.status_icon.setIcon(QIcon(self.pixmap)) self.menuProgress.setText(' | '.join(self.getMenuProgress(pg))) self.menuProgress.setVisible(True) else: self.status_icon.setIcon(self.icon.BIT_LOGO) self.menuProgress.setVisible(False) def getMenuProgress(self, pg): d = (('sent', _('Sent:')), \ ('speed', _('Speed:')),\ ('eta', _('ETA:'))) for key, txt in d: value = pg.strValue(key, '') if not value: continue yield txt + ' ' + value def onStartBIT(self): profileID = self.config.currentProfile() cmd = ['backintime-qt4',] if not profileID == '1': cmd += ['--profile-id', profileID] proc = subprocess.Popen(cmd) def onOpenLog(self): dlg = logviewdialog.LogViewDialog(self, systray = True) dlg.decode = self.decode dlg.cbDecode.setChecked(self.btnDecode.isChecked()) dlg.exec_() def onBtnDecode(self, checked): if checked: self.decode = encfstools.Decode(self.config) self.last_message = None self.updateInfo() else: self.decode = None def onBtnStop(self): os.kill(self.snapshots.pid(), signal.SIGKILL) self.btnStop.setEnabled(False) self.btnPause.setEnabled(False) self.btnResume.setEnabled(False) self.snapshots.setTakeSnapshotMessage(0, 'Snapshot terminated')
class Window(QDialog): def __init__(self, gui_connection): super(Window, self).__init__() self.gui_connection = gui_connection self.gui_connection.changeState.connect(self.changeState) self.gui_connection.whatTime.connect(self.setTime) #init settings dialog self.settings_dialog = settings.SettingsDialog() self.createActions() self.createTrayIcon() self.setTrayIcon('work-full') self.trayIcon.show() self.setWindowTitle("KoffeeBreak") def createActions(self): self.openAction = QAction(QIcon().fromTheme('document-open'), "Open", self, triggered=self.showNormal) self.takeBreakAction = QAction(QIcon().fromTheme("koffeebreak-break-full"), "Take a break", self, triggered=self.start_break) self.pauseAction = QAction(QIcon().fromTheme('media-playback-pause'), "Pause program",self, triggered=self.pauseProgram) self.settingsAction = QAction(QIcon().fromTheme('configure'), "Settings", self, triggered=self.settings_dialog.show) self.quitAction = QAction(QIcon().fromTheme('application-exit'), "Quit", self, triggered=self.close_app) def createTrayIcon(self): self.trayIconMenu = QMenu(self) self.trayIconMenu.addAction(self.openAction) self.trayIconMenu.addAction(self.settingsAction) self.trayIconMenu.addSeparator() self.trayIconMenu.addAction(self.takeBreakAction) self.trayIconMenu.addAction(self.pauseAction) self.trayIconMenu.addSeparator() self.trayIconMenu.addAction(self.quitAction) self.trayIcon = QSystemTrayIcon(self) self.trayIcon.setContextMenu(self.trayIconMenu) def setTrayIcon(self, iconName): icon = QIcon().fromTheme('koffeebreak-' + iconName) self.trayIcon.setIcon(icon) def start_break(self): self.break_screen = break_screen.BreakWindow(self.gui_connection) def changeState(self, state): self.setTrayIcon(state) if state == "break-1-4": pass #self.gui_connection.whatTime.emit() #self.trayIcon.showMessage("Break-1-4", str(self.time)) elif state == "break-2-4": pass elif state == "break-3-4": pass elif state == "break-full": if (not self.break_screen.isVisible()): self.start_break() elif state == "work-1-8": pass elif state == "work-2-8": pass elif state == "work-3-8": pass elif state == "work-4-8": pass elif state == "work-5-8": pass elif state == "work-6-8": pass elif state == "work-7-8": pass elif state == "work-full": if (self.break_screen.isVisible()): self.break_screen.close() def setTime(self, time): self.time = time def pauseProgram(self): self.gui_connection.pauseTimer.emit() def close_app(self): self.gui_connection.closeApp.emit()
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 Window(QMainWindow): """ Main GUI class for application """ def __init__(self): QWidget.__init__(self) # loaind ui from xml uic.loadUi(os.path.join(DIRPATH, 'app.ui'), self) # self.show_msgbox("Info", "Lan Messenger") self.users = {} self.host = socket.gethostname() self.ip = get_ip_address() # button event handlers self.btnRefreshBuddies.clicked.connect(self.refreshBuddies) self.btnSend.clicked.connect(self.sendMsg) self.lstBuddies.currentItemChanged.connect( self.on_buddy_selection_changed) self.msg_manager = MessageManager() self.msg_sender = MessageSender(self.host, self.ip) self.message_listener = MessageListener() self.message_listener.message_received.connect(self.handle_messages) self.send_IAI() self.setup_tray_menu() # setting up handlers for menubar actions self.actionAbout.triggered.connect(self.about) self.actionExit.triggered.connect(qApp.quit) self.actionPreferences.triggered.connect(self.show_preferences) def about(self): print("about") ad = AboutDialog() ad.display() def show_preferences(self): print("preferences") pd = PrefsDialog() pd.display() def setup_tray_menu(self): # setting up QSystemTrayIcon self.tray_icon = QSystemTrayIcon(self) self.tray_icon.setIcon( self.style().standardIcon(QStyle.SP_ComputerIcon)) # tray actions show_action = QAction("Show", self) quit_action = QAction("Exit", self) hide_action = QAction("Hide", self) # action handlers show_action.triggered.connect(self.show) hide_action.triggered.connect(self.hide) quit_action.triggered.connect(qApp.quit) # tray menu tray_menu = QMenu() tray_menu.addAction(show_action) tray_menu.addAction(hide_action) tray_menu.addAction(quit_action) self.tray_icon.setContextMenu(tray_menu) self.tray_icon.show() def closeEvent(self, event): event.ignore() self.hide() self.tray_icon.showMessage( "PyLanMessenger", "PyLanMessenger was minimized to Tray", QSystemTrayIcon.Information, 2000 ) def handle_messages(self, data): log.debug("UI handling message: %s" % data) pkt = Packet() pkt.json_to_obj(data) if pkt.op == "IAI": self.handle_IAI(pkt.ip, pkt.host) if pkt.op == "MTI": self.handle_MTI(pkt.ip, pkt.host) if pkt.op == "TCM": self.handle_TCM(pkt.ip, pkt.host, pkt.msg) def send_IAI(self): # broadcast a message that IAI - "I Am In" the n/w pkt = Packet(op="IAI", ip=self.ip, host=self.host).to_json() self.msg_sender.send_broadcast_message(pkt) def send_MTI(self): # broadcast a message that MTI - "Me Too In" the n/w pkt = Packet(op="MTI", ip=self.ip, host=self.host).to_json() self.msg_sender.send_broadcast_message(pkt) def handle_IAI(self, ip, host): """ handle "I am In" packet reply with MTI for IAI me too in when other says "i am in" """ self.send_MTI() if host not in self.users: print("adding host", host) self.users[host] = ip self.lstBuddies.addItem(str(host)) def handle_MTI(self, ip, host): """ handle Me Too In packet """ if host not in self.users: self.users[host] = ip self.lstBuddies.addItem(str(host)) def handle_TCM(self, ip, host, msg): self.add_chat_msg(ip, host, "%s: %s" % (host, msg)) def refreshBuddies(self): self.lstBuddies.clear() self.users = {} self.send_IAI() def sendMsg(self): try: receiver_host = self.lstBuddies.currentItem().text() except: log.warning("no host found from selection") return msg = self.teMsg.toPlainText() receiver_ip = self.users[receiver_host] # sending msg to receiver self.msg_sender.send_to_ip(receiver_ip, receiver_host, msg.strip()) # adding my message in chat area in UI self.add_chat_msg( receiver_ip, receiver_host, "%s: %s" % (self.host, msg)) # cleaning up textbox for typed message in UI self.teMsg.setText("") def add_chat_msg(self, ip, other_host, msg): self.msg_manager.add_chat_msg(ip, other_host, msg) # showing msg in UI self.teMsgsList.append(msg) def on_buddy_selection_changed(self): if self.lstBuddies.count() == 0: return # no buddy selected if not self.lstBuddies.currentItem(): return sel_user = self.lstBuddies.currentItem().text() log.debug("You selected buddy is: \"%s\"" % sel_user) self.teMsgsList.clear() msgs = self.msg_manager.get_message_for_user(sel_user) for m in msgs: self.teMsgsList.append(m) def show_msgbox(self, title, text): """ Function for showing error/info message box """ msg = QMessageBox() msg.setIcon(QMessageBox.Information) msg.setText(text) msg.setWindowTitle(title) msg.setStandardButtons(QMessageBox.Ok) retval = msg.exec_()
class MainApp: def __init__(self, qapp, dbus_service_identifier): self.proxy = None self.log = logging.getLogger() log_formatter = logging.Formatter( "%(asctime)s [%(levelname)8.8s] %(message)s", datefmt="%Y-%m-%d %H-%M-%S") stdout_handler = logging.StreamHandler(sys.stdout) stdout_handler.setFormatter(log_formatter) self.log.addHandler(stdout_handler) self.log.setLevel(logging.DEBUG) # Reference to main PyQt.QApplication self.qapp: QApplication = qapp # Save reference to dbus interface identifier self.dbus_service_identifier = dbus_service_identifier # Load all tray icon files self.icon_noconnection = [ QIcon(os.path.join(ASSETS_PATH, "icon_noconnection.png")), QIcon(os.path.join(ASSETS_PATH, "icon.png")) ] self.icon_pause = [ QIcon(os.path.join(ASSETS_PATH, "icon_pause.png")), QIcon(os.path.join(ASSETS_PATH, "icon.png")) ] self.icon_running = [ QIcon(os.path.join(ASSETS_PATH, "icon_running0.png")), QIcon(os.path.join(ASSETS_PATH, "icon_running1.png")), QIcon(os.path.join(ASSETS_PATH, "icon_running2.png")), QIcon(os.path.join(ASSETS_PATH, "icon_running3.png")), QIcon(os.path.join(ASSETS_PATH, "icon_running4.png")), QIcon(os.path.join(ASSETS_PATH, "icon_running5.png")), QIcon(os.path.join(ASSETS_PATH, "icon_running6.png")), QIcon(os.path.join(ASSETS_PATH, "icon_running7.png")) ] self.icon_error = QIcon(os.path.join(ASSETS_PATH, "icon_error.png")) self.icon_ok = QIcon(os.path.join(ASSETS_PATH, "icon_ok.png")) self.icon_attention = QIcon( os.path.join(ASSETS_PATH, "icon_attention.png")) # Load assets for menu self.micon_exit = QIcon(os.path.join(ASSETS_PATH, "micon_exit.png")) self.micon_pause = QIcon(os.path.join(ASSETS_PATH, "micon_pause.png")) self.micon_info = QIcon(os.path.join(ASSETS_PATH, "micon_info.png")) self.micon_run = QIcon(os.path.join(ASSETS_PATH, "micon_run.png")) self.micon_mount = QIcon(os.path.join(ASSETS_PATH, "micon_mount.png")) self.micon_umount = QIcon(os.path.join(ASSETS_PATH, "micon_umount.png")) # self.micon_log = QIcon(os.path.join(DATA_PACKAGE_NAME, "micon_log.png")) # self.micon_console = QIcon(os.path.join(DATA_PACKAGE_NAME, "micon_console.png")) # Setup tray icon self.qapp.setQuitOnLastWindowClosed(False) self.tray = QSystemTrayIcon() self.tray.setIcon(self.icon_noconnection[0]) # Create right-click menu for tray self.menu = QMenu() self.exit_action = QAction("Exit", self.qapp) self.exit_action.triggered.connect(self.__click_exit) self.exit_action.setIcon(self.micon_exit) self.menu.addAction(self.exit_action) self.menu.addSeparator() self.pause_action = QAction("Toggle Pause", self.qapp) self.pause_action.triggered.connect(self.__click_pause) self.pause_action.setIcon(self.micon_pause) self.menu.addAction(self.pause_action) self.menu.addSeparator() self.tray.setContextMenu(self.menu) # Display tray icon self.tray.setVisible(True) self.status: int = Status.NO_CONNECTION self.job_mutex = QMutex() self.job_status: Dict[str, int] = {} self.job_submenu: Dict[str, QMenu] = {} self.job_actions: Dict[str, Dict[str, QAction]] = {} self.animation_timer = QTimer() self.animation_timer.setInterval(200) self.animation_timer.timeout.connect(self.update_icon) self.animation_counter = 0 self.log.info('Start') self.heartbeat_timer = QTimer() self.heartbeat_timer.setInterval(30000) self.heartbeat_timer.timeout.connect(self.heartbeat) self.heartbeat() self.animation_timer.start() self.heartbeat_timer.start() def update_icon(self): with QMutexLocker(self.job_mutex): if self.job_status.values(): sum_status = max(self.job_status.values()) else: sum_status = Status.WARNING icon_status = max(sum_status, self.status) if icon_status == Status.NO_CONNECTION: self.tray.setIcon( self.icon_noconnection[(self.animation_counter // 2) % len(self.icon_noconnection)]) if icon_status == Status.PAUSE: ctr = (self.animation_counter // 2) % 4 if ctr > 1: ctr = 0 self.tray.setIcon(self.icon_pause[ctr]) elif icon_status == Status.OK: self.tray.setIcon(self.icon_ok) elif icon_status == Status.WARNING: self.tray.setIcon(self.icon_attention) elif icon_status == Status.ERROR: self.tray.setIcon(self.icon_error) elif icon_status == Status.RUNNING: self.tray.setIcon(self.icon_running[self.animation_counter % len(self.icon_running)]) self.animation_counter += 1 for job, job_status in self.job_status.items(): if job_status == Status.OK: self.job_submenu[job].setIcon(self.icon_ok) elif job_status == Status.RUNNING: self.job_submenu[job].setIcon(self.icon_running[0]) elif job_status == Status.WARNING: self.job_submenu[job].setIcon(self.icon_attention) elif job_status == Status.ERROR: self.job_submenu[job].setIcon(self.icon_error) def __add_job(self, job_name: str): self.log.info('Add job {}'.format(job_name)) job_menu = QMenu("Job {}".format(job_name), parent=self.menu) job_mount_action = QAction("Mount", parent=job_menu) job_umount_action = QAction("UMount", parent=job_menu) job_info_action = QAction("Info", parent=job_menu) job_run_action = QAction("Run Now", parent=job_menu) job_mount_action.setIcon(self.micon_mount) job_umount_action.setIcon(self.micon_umount) job_info_action.setIcon(self.micon_info) job_run_action.setIcon(self.micon_run) job_mount_action.triggered.connect( lambda: self.__click_mount(job_name)) job_umount_action.triggered.connect( lambda: self.__click_umount(job_name)) job_info_action.triggered.connect(lambda: self.__click_info(job_name)) job_run_action.triggered.connect(lambda: self.__click_run(job_name)) job_menu.addAction(job_run_action) job_menu.addAction(job_info_action) job_menu.addAction(job_mount_action) job_menu.addAction(job_umount_action) job_menu_action = self.menu.addMenu(job_menu) self.job_submenu[job_name] = job_menu self.job_actions[job_name] = { 'mount': job_mount_action, 'umount': job_umount_action, 'info': job_info_action, 'run': job_run_action, 'menu': job_menu_action } self.job_status[job_name] = Status.ERROR def __del_job(self, job_name: str): self.log.info('Delete job {}'.format(job_name)) self.job_submenu[job_name].removeAction( self.job_actions[job_name]['mount']) self.job_submenu[job_name].removeAction( self.job_actions[job_name]['umount']) self.job_submenu[job_name].removeAction( self.job_actions[job_name]['info']) self.job_submenu[job_name].removeAction( self.job_actions[job_name]['run']) self.menu.removeAction(self.job_actions[job_name]['menu']) del self.job_actions[job_name] del self.job_submenu[job_name] del self.job_status[job_name] def __click_mount(self, job_name: str): self.log.info('Click mount for job {}'.format(job_name)) try: if not self.proxy.MountRepo(job_name): self.log.error('Could not mount job {}'.format(job_name)) except DBusError: self.proxy = None self.status = Status.NO_CONNECTION self.log.error('Could not mount job {}'.format(job_name)) def __click_umount(self, job_name: str): self.log.info('Click umount for job {}'.format(job_name)) try: if not self.proxy.UMountRepo(job_name): self.log.error('Could not mount job {}'.format(job_name)) except DBusError: self.proxy = None self.status = Status.NO_CONNECTION self.log.error('Could not mount job {}'.format(job_name)) def __click_run(self, job_name: str): self.log.info('Click run for job {}'.format(job_name)) try: if not self.proxy.RunJob(job_name): self.log.error('Could not run job {}'.format(job_name)) except DBusError: self.proxy = None self.status = Status.NO_CONNECTION self.log.error('Could not run job {}'.format(job_name)) def __click_info(self, job_name: str): self.log.info('Click info for job {}.'.format(job_name)) try: if not self.proxy.RequestJobInfo(job_name): self.log.error( 'Could not get info for job {}'.format(job_name)) except DBusError: self.proxy = None self.status = Status.NO_CONNECTION self.log.error('Could not get info for job {}'.format(job_name)) def __callback_info(self, job_name: str, info: str): self.info_widget = TextWidget(job_name=job_name, info=pretty_info(parse_json(info))) def __click_pause(self): self.log.info('Click pause toggle button.') try: if self.proxy: self.proxy.SetPause(not self.proxy.GetPause()) except DBusError: self.proxy = None self.status = Status.NO_CONNECTION self.log.error('Could not toggle pause.') def heartbeat(self): self.log.info('[Heartbeat] Triggered') if self.proxy is None: self.log.info('[Heartbeat] Not connected yet') try: self.proxy = self.dbus_service_identifier.get_proxy() self.log.info('[Heartbeat] Created proxy obj') except DBusError: self.proxy = None self.status = Status.NO_CONNECTION self.log.warning('[Heartbeat] Proxy creation failed') if self.proxy is not None: self.log.info('[Heartbeat] Connection exists') try: server_jobs = set(self.proxy.GetLoadedJobs()) if self.__status_update not in self.proxy.StatusUpdateNotifier._callbacks: self.proxy.StatusUpdateNotifier.connect( self.__status_update) if self.__pause not in self.proxy.PauseNotifier._callbacks: self.proxy.PauseNotifier.connect(self.__pause) if self.__callback_info not in self.proxy.JobInfoNotifier._callbacks: self.proxy.JobInfoNotifier.connect(self.__callback_info) if self.proxy.GetPause(): self.status = Status.PAUSE else: self.status = Status.OK except DBusError: self.proxy = None self.status = Status.NO_CONNECTION server_jobs = set([]) self.log.error( '[Heartbeat] DBusError while executing GetLoadedJobs()') cur_jobs = set(self.job_status.keys()) jobs_to_del = cur_jobs - server_jobs jobs_to_add = server_jobs - cur_jobs with QMutexLocker(self.job_mutex): for job_name in jobs_to_del: self.__del_job(job_name) for job_name in jobs_to_add: self.__add_job(job_name) for job_name in self.job_status.keys(): try: job_s = self.proxy.GetJobStatus(job_name) except DBusError: self.proxy = None self.status = Status.NO_CONNECTION self.log.error( '[Heartbeat] DBusError while initializing job status using GetJobStatus()' ) return sched = job_s['schedule_status'] retry = int(job_s['job_retry']) self.__store_status(job_name, sched, retry) def __status_update(self, job_name: str, sched: str, retry: int): self.log.info( '[update_notfication] Notified by server: job_name: {}, sched: {}, retry: {}' .format(job_name, sched, retry)) with QMutexLocker(self.job_mutex): if job_name not in self.job_status.keys(): self.log.info( '[update_notfication] job {} does not exist??'.format( job_name)) return else: self.__store_status(job_name, sched, retry) def __pause(self, is_paused: bool): if is_paused: self.status = Status.PAUSE else: self.status = Status.OK def __store_status(self, job_name: str, sched: str, retry: int): if sched == 'running': self.job_status[job_name] = Status.RUNNING elif sched == 'next' or sched == 'wait': if retry == 0: self.job_status[job_name] = Status.OK elif retry < 0: self.job_status[job_name] = Status.ERROR else: self.job_status[job_name] = Status.WARNING else: self.job_status[job_name] = Status.ERROR @staticmethod def __click_exit(): exit()
class ChatWindow(QMainWindow): def __init__(self, interface): super().__init__() self.interface = interface # window setup self.status_bar = self.statusBar() self.tray_icon = QSystemTrayIcon(self) self.tray_icon.setIcon(QIcon('images/icon.ico')) self.tray_icon.setToolTip(settings.APP_NAME) self.tray_menu = QMenu() _stop = QAction('Exit', self, shortcut='Ctrl+Q', statusTip='Exit', triggered=self.close) _stop.setIcon(QIcon('images/exit.png')) self.tray_menu.addAction(_stop) self.tray_icon.setContextMenu(self.tray_menu) self.tray_icon.setVisible(True) # widget setup self.widget_stack = QStackedWidget(self) self.widget_stack.addWidget(ConnectingWidget(self)) self.chat_tabs = QTabWidget(self) self.chat_tabs.setTabsClosable(True) self.chat_tabs.setMovable(True) self.chat_tabs.tabCloseRequested.connect(self.close_tab) self.chat_tabs.currentChanged.connect(self._tab_changed) self.widget_stack.addWidget(self.chat_tabs) self.widget_stack.setCurrentIndex(1) self.status_bar = self.statusBar() # menubar setup new_chat_icon = QIcon('images/new_chat.png') exit_icon = QIcon('images/exit.png') menu_icon = QIcon('images/menu.png') new_chat_action = QAction(new_chat_icon, '&New chat', self) auth_chat_action = QAction(new_chat_icon, '&Authenticate chat', self) exit_action = QAction(exit_icon, '&Exit', self) new_chat_action.triggered.connect(self.open_tab) auth_chat_action.triggered.connect(self._show_auth_dialog) exit_action.triggered.connect(self.close) new_chat_action.setShortcut('Ctrl+N') exit_action.setShortcut('Ctrl+Q') options_menu = QMenu() options_menu.addAction(new_chat_action) options_menu.addAction(auth_chat_action) options_menu.addAction(exit_action) options_menu_button = QToolButton() new_chat_button = QToolButton() exit_button = QToolButton() new_chat_button.clicked.connect(self.open_tab) exit_button.clicked.connect(self.interface.stop) options_menu_button.setIcon(menu_icon) new_chat_button.setIcon(new_chat_icon) exit_button.setIcon(exit_icon) options_menu_button.setMenu(options_menu) options_menu_button.setPopupMode(QToolButton.InstantPopup) toolbar = QToolBar(self) toolbar.addWidget(options_menu_button) toolbar.addWidget(new_chat_button) toolbar.addWidget(exit_button) self.addToolBar(Qt.LeftToolBarArea, toolbar) # window setup self.setWindowIcon(QIcon('images/icon.png')) vbox = QVBoxLayout() vbox.addWidget(self.widget_stack) _cw = QWidget() _cw.setLayout(vbox) self.setCentralWidget(_cw) utils.resize_window(self, 700, 400) utils.center_window(self) def open_tab(self, zone=None): tab = ChatTabWidget(self) tab._zone = zone self.chat_tabs.addTab(tab, constants.BLANK_TAB_TITLE) self.chat_tabs.setCurrentWidget(tab) tab.setFocus() return tab def close_tab(self, index): tab = self.chat_tabs.widget(index) if tab._zone: conn.synchronous_send(command=constants.CMD_LEAVE, recipient=tab._zone.id) conn._zones.remove(tab._zone) else: # Can't leave nonexistent zone pass self.chat_tabs.removeTab(index) def _tab_changed(self): self.status_bar.showMessage('') def _show_auth_dialog(self): pass
class Window(QMainWindow): """ Main GUI class for application """ def __init__(self): QWidget.__init__(self) # loaind ui from xml uic.loadUi(os.path.join(DIRPATH, 'app.ui'), self) # self.show_msgbox("Info", "Lan Messenger") self.users = {} self.host = socket.gethostname() self.ip = get_ip_address() # button event handlers self.btnRefreshBuddies.clicked.connect(self.refreshBuddies) self.btnSend.clicked.connect(self.sendMsg) self.lstBuddies.currentItemChanged.connect( self.on_buddy_selection_changed) self.msg_manager = MessageManager() self.msg_sender = MessageSender(self.host, self.ip) self.message_listener = MessageListener() self.message_listener.message_received.connect(self.handle_messages) self.send_IAI() self.setup_tray_menu() # setting up handlers for menubar actions self.actionAbout.triggered.connect(self.about) self.actionExit.triggered.connect(qApp.quit) self.actionPreferences.triggered.connect(self.show_preferences) def about(self): print("about") ad = AboutDialog() ad.display() def show_preferences(self): print("preferences") pd = PrefsDialog() pd.display() def setup_tray_menu(self): # setting up QSystemTrayIcon self.tray_icon = QSystemTrayIcon(self) self.tray_icon.setIcon(self.style().standardIcon( QStyle.SP_ComputerIcon)) # tray actions show_action = QAction("Show", self) quit_action = QAction("Exit", self) hide_action = QAction("Hide", self) # action handlers show_action.triggered.connect(self.show) hide_action.triggered.connect(self.hide) quit_action.triggered.connect(qApp.quit) # tray menu tray_menu = QMenu() tray_menu.addAction(show_action) tray_menu.addAction(hide_action) tray_menu.addAction(quit_action) self.tray_icon.setContextMenu(tray_menu) self.tray_icon.show() def closeEvent(self, event): event.ignore() self.hide() self.tray_icon.showMessage("PyLanMessenger", "PyLanMessenger was minimized to Tray", QSystemTrayIcon.Information, 2000) def handle_messages(self, data): log.debug("UI handling message: %s" % data) pkt = Packet() pkt.json_to_obj(data) if pkt.op == "IAI": self.handle_IAI(pkt.ip, pkt.host) if pkt.op == "MTI": self.handle_MTI(pkt.ip, pkt.host) if pkt.op == "TCM": self.handle_TCM(pkt.ip, pkt.host, pkt.msg) def send_IAI(self): # broadcast a message that IAI - "I Am In" the n/w pkt = Packet(op="IAI", ip=self.ip, host=self.host).to_json() self.msg_sender.send_broadcast_message(pkt) def send_MTI(self): # broadcast a message that MTI - "Me Too In" the n/w pkt = Packet(op="MTI", ip=self.ip, host=self.host).to_json() self.msg_sender.send_broadcast_message(pkt) def handle_IAI(self, ip, host): """ handle "I am In" packet reply with MTI for IAI me too in when other says "i am in" """ self.send_MTI() if host not in self.users: print("adding host", host) self.users[host] = ip self.lstBuddies.addItem(str(host)) def handle_MTI(self, ip, host): """ handle Me Too In packet """ if host not in self.users: self.users[host] = ip self.lstBuddies.addItem(str(host)) def handle_TCM(self, ip, host, msg): self.add_chat_msg(ip, host, "%s: %s" % (host, msg)) def refreshBuddies(self): self.lstBuddies.clear() self.users = {} self.send_IAI() def sendMsg(self): try: receiver_host = self.lstBuddies.currentItem().text() except: log.warning("no host found from selection") return msg = self.teMsg.toPlainText() receiver_ip = self.users[receiver_host] # sending msg to receiver self.msg_sender.send_to_ip(receiver_ip, receiver_host, msg.strip()) # adding my message in chat area in UI self.add_chat_msg(receiver_ip, receiver_host, "%s: %s" % (self.host, msg)) # cleaning up textbox for typed message in UI self.teMsg.setText("") def add_chat_msg(self, ip, other_host, msg): self.msg_manager.add_chat_msg(ip, other_host, msg) # showing msg in UI self.teMsgsList.append(msg) def on_buddy_selection_changed(self): if self.lstBuddies.count() == 0: return # no buddy selected if not self.lstBuddies.currentItem(): return sel_user = self.lstBuddies.currentItem().text() log.debug("You selected buddy is: \"%s\"" % sel_user) self.teMsgsList.clear() msgs = self.msg_manager.get_message_for_user(sel_user) for m in msgs: self.teMsgsList.append(m) def show_msgbox(self, title, text): """ Function for showing error/info message box """ msg = QMessageBox() msg.setIcon(QMessageBox.Information) msg.setText(text) msg.setWindowTitle(title) msg.setStandardButtons(QMessageBox.Ok) retval = msg.exec_()
class TriblerWindow(QMainWindow): resize_event = pyqtSignal() escape_pressed = pyqtSignal() received_search_completions = pyqtSignal(object) def on_exception(self, *exc_info): if self.exception_handler_called: # We only show one feedback dialog, even when there are two consecutive exceptions. return self.exception_handler_called = True if self.tray_icon: try: self.tray_icon.deleteLater() except RuntimeError: # The tray icon might have already been removed when unloading Qt. # This is due to the C code actually being asynchronous. logging.debug( "Tray icon already removed, no further deletion necessary." ) self.tray_icon = None # Stop the download loop self.downloads_page.stop_loading_downloads() # Add info about whether we are stopping Tribler or not os.environ['TRIBLER_SHUTTING_DOWN'] = str( self.core_manager.shutting_down) if not self.core_manager.shutting_down: self.core_manager.stop(stop_app_on_shutdown=False) self.setHidden(True) if self.debug_window: self.debug_window.setHidden(True) exception_text = "".join(traceback.format_exception(*exc_info)) logging.error(exception_text) dialog = FeedbackDialog( self, exception_text, self.core_manager.events_manager.tribler_version, self.start_time) dialog.show() def __init__(self, core_args=None, core_env=None, api_port=None): QMainWindow.__init__(self) QCoreApplication.setOrganizationDomain("nl") QCoreApplication.setOrganizationName("TUDelft") QCoreApplication.setApplicationName("Tribler") QCoreApplication.setAttribute(Qt.AA_UseHighDpiPixmaps) self.gui_settings = QSettings() api_port = api_port or int( get_gui_setting(self.gui_settings, "api_port", DEFAULT_API_PORT)) dispatcher.update_worker_settings(port=api_port) self.navigation_stack = [] self.tribler_started = False self.tribler_settings = None self.debug_window = None self.core_manager = CoreManager(api_port) self.pending_requests = {} self.pending_uri_requests = [] self.download_uri = None self.dialog = None self.new_version_dialog = None self.start_download_dialog_active = False self.request_mgr = None self.search_request_mgr = None self.search_suggestion_mgr = None self.selected_torrent_files = [] self.vlc_available = True self.has_search_results = False self.last_search_query = None self.last_search_time = None self.start_time = time.time() self.exception_handler_called = False self.token_refresh_timer = None sys.excepthook = self.on_exception uic.loadUi(get_ui_file_path('mainwindow.ui'), self) TriblerRequestManager.window = self self.tribler_status_bar.hide() # Load dynamic widgets uic.loadUi(get_ui_file_path('torrent_channel_list_container.ui'), self.channel_page_container) self.channel_torrents_list = self.channel_page_container.items_list self.channel_torrents_detail_widget = self.channel_page_container.details_tab_widget self.channel_torrents_detail_widget.initialize_details_widget() self.channel_torrents_list.itemClicked.connect( self.channel_page.clicked_item) uic.loadUi(get_ui_file_path('torrent_channel_list_container.ui'), self.search_page_container) self.search_results_list = self.search_page_container.items_list self.search_torrents_detail_widget = self.search_page_container.details_tab_widget self.search_torrents_detail_widget.initialize_details_widget() self.search_results_list.itemClicked.connect( self.on_channel_item_click) self.search_results_list.itemClicked.connect( self.search_results_page.clicked_item) self.token_balance_widget.mouseReleaseEvent = self.on_token_balance_click def on_state_update(new_state): self.loading_text_label.setText(new_state) self.core_manager.core_state_update.connect(on_state_update) self.magnet_handler = MagnetHandler(self.window) QDesktopServices.setUrlHandler("magnet", self.magnet_handler, "on_open_magnet_link") self.debug_pane_shortcut = QShortcut(QKeySequence("Ctrl+d"), self) self.debug_pane_shortcut.activated.connect( self.clicked_menu_button_debug) # Remove the focus rect on OS X for widget in self.findChildren(QLineEdit) + self.findChildren( QListWidget) + self.findChildren(QTreeWidget): widget.setAttribute(Qt.WA_MacShowFocusRect, 0) self.menu_buttons = [ self.left_menu_button_home, self.left_menu_button_search, self.left_menu_button_my_channel, self.left_menu_button_subscriptions, self.left_menu_button_video_player, self.left_menu_button_downloads, self.left_menu_button_discovered ] self.video_player_page.initialize_player() self.search_results_page.initialize_search_results_page() self.settings_page.initialize_settings_page() self.subscribed_channels_page.initialize() self.edit_channel_page.initialize_edit_channel_page() self.downloads_page.initialize_downloads_page() self.home_page.initialize_home_page() self.loading_page.initialize_loading_page() self.discovering_page.initialize_discovering_page() self.discovered_page.initialize_discovered_page() self.trust_page.initialize_trust_page() self.stackedWidget.setCurrentIndex(PAGE_LOADING) # Create the system tray icon if QSystemTrayIcon.isSystemTrayAvailable(): self.tray_icon = QSystemTrayIcon() use_monochrome_icon = get_gui_setting(self.gui_settings, "use_monochrome_icon", False, is_bool=True) self.update_tray_icon(use_monochrome_icon) # Create the tray icon menu menu = self.create_add_torrent_menu() show_downloads_action = QAction('Show downloads', self) show_downloads_action.triggered.connect( self.clicked_menu_button_downloads) token_balance_action = QAction('Show token balance', self) token_balance_action.triggered.connect( lambda: self.on_token_balance_click(None)) quit_action = QAction('Quit Tribler', self) quit_action.triggered.connect(self.close_tribler) menu.addSeparator() menu.addAction(show_downloads_action) menu.addAction(token_balance_action) menu.addSeparator() menu.addAction(quit_action) self.tray_icon.setContextMenu(menu) else: self.tray_icon = None self.hide_left_menu_playlist() self.left_menu_button_debug.setHidden(True) self.top_menu_button.setHidden(True) self.left_menu.setHidden(True) self.token_balance_widget.setHidden(True) self.settings_button.setHidden(True) self.add_torrent_button.setHidden(True) self.top_search_bar.setHidden(True) # Set various icons self.top_menu_button.setIcon(QIcon(get_image_path('menu.png'))) self.search_completion_model = QStringListModel() completer = QCompleter() completer.setModel(self.search_completion_model) completer.setCompletionMode(QCompleter.UnfilteredPopupCompletion) self.item_delegate = QStyledItemDelegate() completer.popup().setItemDelegate(self.item_delegate) completer.popup().setStyleSheet(""" QListView { background-color: #404040; } QListView::item { color: #D0D0D0; padding-top: 5px; padding-bottom: 5px; } QListView::item:hover { background-color: #707070; } """) self.top_search_bar.setCompleter(completer) # Toggle debug if developer mode is enabled self.window().left_menu_button_debug.setHidden(not get_gui_setting( self.gui_settings, "debug", False, is_bool=True)) # Start Tribler self.core_manager.start(core_args=core_args, core_env=core_env) self.core_manager.events_manager.received_search_result_channel.connect( self.search_results_page.received_search_result_channel) self.core_manager.events_manager.received_search_result_torrent.connect( self.search_results_page.received_search_result_torrent) self.core_manager.events_manager.torrent_finished.connect( self.on_torrent_finished) self.core_manager.events_manager.new_version_available.connect( self.on_new_version_available) self.core_manager.events_manager.tribler_started.connect( self.on_tribler_started) self.core_manager.events_manager.events_started.connect( self.on_events_started) self.core_manager.events_manager.low_storage_signal.connect( self.on_low_storage) # Install signal handler for ctrl+c events def sigint_handler(*_): self.close_tribler() signal.signal(signal.SIGINT, sigint_handler) self.installEventFilter(self.video_player_page) # Resize the window according to the settings center = QApplication.desktop().availableGeometry(self).center() pos = self.gui_settings.value( "pos", QPoint(center.x() - self.width() * 0.5, center.y() - self.height() * 0.5)) size = self.gui_settings.value("size", self.size()) self.move(pos) self.resize(size) self.show() def update_tray_icon(self, use_monochrome_icon): if not QSystemTrayIcon.isSystemTrayAvailable(): return if use_monochrome_icon: self.tray_icon.setIcon( QIcon(QPixmap(get_image_path('monochrome_tribler.png')))) else: self.tray_icon.setIcon( QIcon(QPixmap(get_image_path('tribler.png')))) self.tray_icon.show() def on_low_storage(self): """ Dealing with low storage space available. First stop the downloads and the core manager and ask user to user to make free space. :return: """ self.downloads_page.stop_loading_downloads() self.core_manager.stop(False) close_dialog = ConfirmationDialog( self.window(), "<b>CRITICAL ERROR</b>", "You are running low on disk space (<100MB). Please make sure to have " "sufficient free space available and restart Tribler again.", [("Close Tribler", BUTTON_TYPE_NORMAL)]) close_dialog.button_clicked.connect(lambda _: self.close_tribler()) close_dialog.show() def on_torrent_finished(self, torrent_info): if self.tray_icon: self.window().tray_icon.showMessage( "Download finished", "Download of %s has finished." % torrent_info["name"]) def show_loading_screen(self): self.top_menu_button.setHidden(True) self.left_menu.setHidden(True) self.token_balance_widget.setHidden(True) self.settings_button.setHidden(True) self.add_torrent_button.setHidden(True) self.top_search_bar.setHidden(True) self.stackedWidget.setCurrentIndex(PAGE_LOADING) def on_tribler_started(self): self.tribler_started = True self.top_menu_button.setHidden(False) self.left_menu.setHidden(False) self.token_balance_widget.setHidden(False) self.settings_button.setHidden(False) self.add_torrent_button.setHidden(False) self.top_search_bar.setHidden(False) # fetch the settings, needed for the video player port self.request_mgr = TriblerRequestManager() self.fetch_settings() self.downloads_page.start_loading_downloads() self.home_page.load_popular_torrents() if not self.gui_settings.value( "first_discover", False) and not self.core_manager.use_existing_core: self.window().gui_settings.setValue("first_discover", True) self.discovering_page.is_discovering = True self.stackedWidget.setCurrentIndex(PAGE_DISCOVERING) else: self.clicked_menu_button_home() def on_events_started(self, json_dict): self.setWindowTitle("Tribler %s" % json_dict["version"]) def show_status_bar(self, message): self.tribler_status_bar_label.setText(message) self.tribler_status_bar.show() def hide_status_bar(self): self.tribler_status_bar.hide() def process_uri_request(self): """ Process a URI request if we have one in the queue. """ if len(self.pending_uri_requests) == 0: return uri = self.pending_uri_requests.pop() if uri.startswith('file') or uri.startswith('magnet'): self.start_download_from_uri(uri) def perform_start_download_request(self, uri, anon_download, safe_seeding, destination, selected_files, total_files=0, callback=None): # Check if destination directory is writable if not is_dir_writable(destination): ConfirmationDialog.show_message( self.window(), "Download error <i>%s</i>" % uri, "Insufficient write permissions to <i>%s</i> directory. " "Please add proper write permissions on the directory and " "add the torrent again." % destination, "OK") return selected_files_uri = "" if len(selected_files) != total_files: # Not all files included selected_files_uri = u'&' + u''.join( u"selected_files[]=%s&" % quote_plus_unicode(filename) for filename in selected_files)[:-1] anon_hops = int(self.tribler_settings['download_defaults'] ['number_hops']) if anon_download else 0 safe_seeding = 1 if safe_seeding else 0 post_data = "uri=%s&anon_hops=%d&safe_seeding=%d&destination=%s%s" % ( quote_plus_unicode(uri), anon_hops, safe_seeding, destination, selected_files_uri) post_data = post_data.encode( 'utf-8') # We need to send bytes in the request, not unicode request_mgr = TriblerRequestManager() request_mgr.perform_request( "downloads", callback if callback else self.on_download_added, method='PUT', data=post_data) # Save the download location to the GUI settings current_settings = get_gui_setting(self.gui_settings, "recent_download_locations", "") recent_locations = current_settings.split( ",") if len(current_settings) > 0 else [] if isinstance(destination, unicode): destination = destination.encode('utf-8') encoded_destination = destination.encode('hex') if encoded_destination in recent_locations: recent_locations.remove(encoded_destination) recent_locations.insert(0, encoded_destination) if len(recent_locations) > 5: recent_locations = recent_locations[:5] self.gui_settings.setValue("recent_download_locations", ','.join(recent_locations)) def on_new_version_available(self, version): if version == str(self.gui_settings.value('last_reported_version')): return self.new_version_dialog = ConfirmationDialog( self, "New version available", "Version %s of Tribler is available.Do you want to visit the " "website to download the newest version?" % version, [('IGNORE', BUTTON_TYPE_NORMAL), ('LATER', BUTTON_TYPE_NORMAL), ('OK', BUTTON_TYPE_NORMAL)]) self.new_version_dialog.button_clicked.connect( lambda action: self.on_new_version_dialog_done(version, action)) self.new_version_dialog.show() def on_new_version_dialog_done(self, version, action): if action == 0: # ignore self.gui_settings.setValue("last_reported_version", version) elif action == 2: # ok import webbrowser webbrowser.open("https://tribler.org") self.new_version_dialog.setParent(None) self.new_version_dialog = None def on_search_text_change(self, text): self.search_suggestion_mgr = TriblerRequestManager() self.search_suggestion_mgr.perform_request( "search/completions?q=%s" % text, self.on_received_search_completions) def on_received_search_completions(self, completions): if completions is None: return self.received_search_completions.emit(completions) self.search_completion_model.setStringList(completions["completions"]) def fetch_settings(self): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("settings", self.received_settings, capture_errors=False) def received_settings(self, settings): # If we cannot receive the settings, stop Tribler with an option to send the crash report. if 'error' in settings: raise RuntimeError( TriblerRequestManager.get_message_from_error(settings)) self.tribler_settings = settings['settings'] # Set the video server port self.video_player_page.video_player_port = settings["ports"][ "video_server~port"] # Disable various components based on the settings if not self.tribler_settings['search_community']['enabled']: self.window().top_search_bar.setHidden(True) if not self.tribler_settings['video_server']['enabled']: self.left_menu_button_video_player.setHidden(True) self.downloads_creditmining_button.setHidden( not self.tribler_settings["credit_mining"]["enabled"]) self.downloads_all_button.click() # process pending file requests (i.e. someone clicked a torrent file when Tribler was closed) # We do this after receiving the settings so we have the default download location. self.process_uri_request() # Set token balance refresh timer and load the token balance self.token_refresh_timer = QTimer() self.token_refresh_timer.timeout.connect(self.load_token_balance) self.token_refresh_timer.start(60000) self.load_token_balance() def on_top_search_button_click(self): current_ts = time.time() current_search_query = self.top_search_bar.text() if self.last_search_query and self.last_search_time \ and self.last_search_query == self.top_search_bar.text() \ and current_ts - self.last_search_time < 1: logging.info( "Same search query already sent within 500ms so dropping this one" ) return self.left_menu_button_search.setChecked(True) self.has_search_results = True self.clicked_menu_button_search() self.search_results_page.perform_search(current_search_query) self.search_request_mgr = TriblerRequestManager() self.search_request_mgr.perform_request( "search?q=%s" % current_search_query, None) self.last_search_query = current_search_query self.last_search_time = current_ts def on_settings_button_click(self): self.deselect_all_menu_buttons() self.stackedWidget.setCurrentIndex(PAGE_SETTINGS) self.settings_page.load_settings() self.navigation_stack = [] self.hide_left_menu_playlist() def on_token_balance_click(self, _): self.raise_window() self.deselect_all_menu_buttons() self.stackedWidget.setCurrentIndex(PAGE_TRUST) self.trust_page.load_trust_statistics() self.navigation_stack = [] self.hide_left_menu_playlist() def load_token_balance(self): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("trustchain/statistics", self.received_token_balance, capture_errors=False) def received_token_balance(self, statistics): if not statistics or "statistics" not in statistics: return statistics = statistics["statistics"] if 'latest_block' in statistics: balance = (statistics["latest_block"]["transaction"]["total_up"] - \ statistics["latest_block"]["transaction"]["total_down"]) / 1024 / 1024 self.token_balance_label.setText("%d" % balance) else: self.token_balance_label.setText("0") def raise_window(self): self.setWindowState(self.windowState() & ~Qt.WindowMinimized | Qt.WindowActive) self.raise_() self.activateWindow() def create_add_torrent_menu(self): """ Create a menu to add new torrents. Shows when users click on the tray icon or the big plus button. """ menu = TriblerActionMenu(self) browse_files_action = QAction('Import torrent from file', self) browse_directory_action = QAction('Import torrent(s) from directory', self) add_url_action = QAction('Import torrent from magnet/URL', self) browse_files_action.triggered.connect(self.on_add_torrent_browse_file) browse_directory_action.triggered.connect( self.on_add_torrent_browse_dir) add_url_action.triggered.connect(self.on_add_torrent_from_url) menu.addAction(browse_files_action) menu.addAction(browse_directory_action) menu.addAction(add_url_action) return menu def on_add_torrent_button_click(self, pos): self.create_add_torrent_menu().exec_( self.mapToGlobal(self.add_torrent_button.pos())) def on_add_torrent_browse_file(self): filenames = QFileDialog.getOpenFileNames( self, "Please select the .torrent file", QDir.homePath(), "Torrent files (*.torrent)") if len(filenames[0]) > 0: [ self.pending_uri_requests.append(u"file:%s" % filename) for filename in filenames[0] ] self.process_uri_request() def start_download_from_uri(self, uri): self.download_uri = uri if get_gui_setting(self.gui_settings, "ask_download_settings", True, is_bool=True): # Clear any previous dialog if exists if self.dialog: self.dialog.button_clicked.disconnect() self.dialog.setParent(None) self.dialog = None self.dialog = StartDownloadDialog(self, self.download_uri) self.dialog.button_clicked.connect(self.on_start_download_action) self.dialog.show() self.start_download_dialog_active = True else: # In the unlikely scenario that tribler settings are not available yet, try to fetch settings again and # add the download uri back to self.pending_uri_requests to process again. if not self.tribler_settings: self.fetch_settings() if self.download_uri not in self.pending_uri_requests: self.pending_uri_requests.append(self.download_uri) return self.window().perform_start_download_request( self.download_uri, self.window().tribler_settings['download_defaults'] ['anonymity_enabled'], self.window().tribler_settings['download_defaults'] ['safeseeding_enabled'], self.tribler_settings['download_defaults']['saveas'], [], 0) self.process_uri_request() def on_start_download_action(self, action): if action == 1: if self.dialog and self.dialog.dialog_widget: self.window().perform_start_download_request( self.download_uri, self.dialog.dialog_widget.anon_download_checkbox.isChecked( ), self.dialog.dialog_widget.safe_seed_checkbox.isChecked(), self.dialog.dialog_widget.destination_input.currentText(), self.dialog.get_selected_files(), self.dialog.dialog_widget.files_list_view. topLevelItemCount()) else: ConfirmationDialog.show_error( self, "Tribler UI Error", "Something went wrong. Please try again.") logging.exception( "Error while trying to download. Either dialog or dialog.dialog_widget is None" ) self.dialog.request_mgr.cancel_request( ) # To abort the torrent info request self.dialog.setParent(None) self.dialog = None self.start_download_dialog_active = False if action == 0: # We do this after removing the dialog since process_uri_request is blocking self.process_uri_request() def on_add_torrent_browse_dir(self): chosen_dir = QFileDialog.getExistingDirectory( self, "Please select the directory containing the .torrent files", QDir.homePath(), QFileDialog.ShowDirsOnly) if len(chosen_dir) != 0: self.selected_torrent_files = [ torrent_file for torrent_file in glob.glob(chosen_dir + "/*.torrent") ] self.dialog = ConfirmationDialog( self, "Add torrents from directory", "Are you sure you want to add %d torrents to Tribler?" % len(self.selected_torrent_files), [('ADD', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)]) self.dialog.button_clicked.connect( self.on_confirm_add_directory_dialog) self.dialog.show() def on_confirm_add_directory_dialog(self, action): if action == 0: for torrent_file in self.selected_torrent_files: escaped_uri = u"file:%s" % pathname2url(torrent_file) self.perform_start_download_request( escaped_uri, self.window().tribler_settings['download_defaults'] ['anonymity_enabled'], self.window().tribler_settings['download_defaults'] ['safeseeding_enabled'], self.tribler_settings['download_defaults']['saveas'], [], 0) if self.dialog: self.dialog.setParent(None) self.dialog = None def on_add_torrent_from_url(self): # Make sure that the window is visible (this action might be triggered from the tray icon) self.raise_window() self.dialog = ConfirmationDialog( self, "Add torrent from URL/magnet link", "Please enter the URL/magnet link in the field below:", [('ADD', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)], show_input=True) self.dialog.dialog_widget.dialog_input.setPlaceholderText( 'URL/magnet link') self.dialog.dialog_widget.dialog_input.setFocus() self.dialog.button_clicked.connect( self.on_torrent_from_url_dialog_done) self.dialog.show() def on_torrent_from_url_dialog_done(self, action): if self.dialog and self.dialog.dialog_widget: uri = self.dialog.dialog_widget.dialog_input.text() # Remove first dialog self.dialog.setParent(None) self.dialog = None if action == 0: self.start_download_from_uri(uri) def on_download_added(self, result): if len(self.pending_uri_requests ) == 0: # Otherwise, we first process the remaining requests. self.window().left_menu_button_downloads.click() else: self.process_uri_request() def on_top_menu_button_click(self): if self.left_menu.isHidden(): self.left_menu.show() else: self.left_menu.hide() def deselect_all_menu_buttons(self, except_select=None): for button in self.menu_buttons: if button == except_select: button.setEnabled(False) continue button.setEnabled(True) if button == self.left_menu_button_search and not self.has_search_results: button.setEnabled(False) button.setChecked(False) def clicked_menu_button_home(self): self.deselect_all_menu_buttons(self.left_menu_button_home) self.stackedWidget.setCurrentIndex(PAGE_HOME) self.navigation_stack = [] self.hide_left_menu_playlist() def clicked_menu_button_search(self): self.deselect_all_menu_buttons(self.left_menu_button_search) self.stackedWidget.setCurrentIndex(PAGE_SEARCH_RESULTS) self.navigation_stack = [] self.hide_left_menu_playlist() def clicked_menu_button_discovered(self): self.deselect_all_menu_buttons(self.left_menu_button_discovered) self.stackedWidget.setCurrentIndex(PAGE_DISCOVERED) self.discovered_page.load_discovered_channels() self.navigation_stack = [] self.hide_left_menu_playlist() def clicked_menu_button_my_channel(self): self.deselect_all_menu_buttons(self.left_menu_button_my_channel) self.stackedWidget.setCurrentIndex(PAGE_EDIT_CHANNEL) self.edit_channel_page.load_my_channel_overview() self.navigation_stack = [] self.hide_left_menu_playlist() def clicked_menu_button_video_player(self): self.deselect_all_menu_buttons(self.left_menu_button_video_player) self.stackedWidget.setCurrentIndex(PAGE_VIDEO_PLAYER) self.navigation_stack = [] self.show_left_menu_playlist() def clicked_menu_button_downloads(self): self.raise_window() self.left_menu_button_downloads.setChecked(True) self.deselect_all_menu_buttons(self.left_menu_button_downloads) self.stackedWidget.setCurrentIndex(PAGE_DOWNLOADS) self.navigation_stack = [] self.hide_left_menu_playlist() def clicked_menu_button_debug(self): if not self.debug_window: self.debug_window = DebugWindow( self.tribler_settings, self.core_manager.events_manager.tribler_version) self.debug_window.show() def clicked_menu_button_subscriptions(self): self.deselect_all_menu_buttons(self.left_menu_button_subscriptions) self.subscribed_channels_page.load_subscribed_channels() self.stackedWidget.setCurrentIndex(PAGE_SUBSCRIBED_CHANNELS) self.navigation_stack = [] self.hide_left_menu_playlist() def hide_left_menu_playlist(self): self.left_menu_seperator.setHidden(True) self.left_menu_playlist_label.setHidden(True) self.left_menu_playlist.setHidden(True) def show_left_menu_playlist(self): self.left_menu_seperator.setHidden(False) self.left_menu_playlist_label.setHidden(False) self.left_menu_playlist.setHidden(False) def on_channel_item_click(self, channel_list_item): list_widget = channel_list_item.listWidget() from TriblerGUI.widgets.channel_list_item import ChannelListItem if isinstance(list_widget.itemWidget(channel_list_item), ChannelListItem): channel_info = channel_list_item.data(Qt.UserRole) self.channel_page.initialize_with_channel(channel_info) self.navigation_stack.append(self.stackedWidget.currentIndex()) self.stackedWidget.setCurrentIndex(PAGE_CHANNEL_DETAILS) def on_playlist_item_click(self, playlist_list_item): list_widget = playlist_list_item.listWidget() from TriblerGUI.widgets.playlist_list_item import PlaylistListItem if isinstance(list_widget.itemWidget(playlist_list_item), PlaylistListItem): playlist_info = playlist_list_item.data(Qt.UserRole) self.playlist_page.initialize_with_playlist(playlist_info) self.navigation_stack.append(self.stackedWidget.currentIndex()) self.stackedWidget.setCurrentIndex(PAGE_PLAYLIST_DETAILS) def on_page_back_clicked(self): try: prev_page = self.navigation_stack.pop() self.stackedWidget.setCurrentIndex(prev_page) if prev_page == PAGE_SEARCH_RESULTS: self.stackedWidget.widget( prev_page).load_search_results_in_list() if prev_page == PAGE_SUBSCRIBED_CHANNELS: self.stackedWidget.widget(prev_page).load_subscribed_channels() if prev_page == PAGE_DISCOVERED: self.stackedWidget.widget(prev_page).load_discovered_channels() except IndexError: logging.exception("Unknown page found in stack") def on_edit_channel_clicked(self): self.stackedWidget.setCurrentIndex(PAGE_EDIT_CHANNEL) self.navigation_stack = [] self.channel_page.on_edit_channel_clicked() def resizeEvent(self, _): # Resize home page cells cell_width = self.home_page_table_view.width( ) / 3 - 3 # We have some padding to the right cell_height = cell_width / 2 + 60 for i in range(0, 3): self.home_page_table_view.setColumnWidth(i, cell_width) self.home_page_table_view.setRowHeight(i, cell_height) self.resize_event.emit() def exit_full_screen(self): self.top_bar.show() self.left_menu.show() self.video_player_page.is_full_screen = False self.showNormal() def close_tribler(self): if not self.core_manager.shutting_down: def show_force_shutdown(): self.loading_text_label.setText( "Tribler is taking longer than expected to shut down. You can force " "Tribler to shutdown by pressing the button below. This might lead " "to data loss.") self.window().force_shutdown_btn.show() if self.tray_icon: self.tray_icon.deleteLater() self.show_loading_screen() self.hide_status_bar() self.loading_text_label.setText("Shutting down...") self.shutdown_timer = QTimer() self.shutdown_timer.timeout.connect(show_force_shutdown) self.shutdown_timer.start(SHUTDOWN_WAITING_PERIOD) self.gui_settings.setValue("pos", self.pos()) self.gui_settings.setValue("size", self.size()) if self.core_manager.use_existing_core: # Don't close the core that we are using QApplication.quit() self.core_manager.stop() self.core_manager.shutting_down = True self.downloads_page.stop_loading_downloads() request_queue.clear() def closeEvent(self, close_event): self.close_tribler() close_event.ignore() def keyReleaseEvent(self, event): if event.key() == Qt.Key_Escape: self.escape_pressed.emit() if self.isFullScreen(): self.exit_full_screen() def clicked_force_shutdown(self): process_checker = ProcessChecker() if process_checker.already_running: core_pid = process_checker.get_pid_from_lock_file() os.kill(int(core_pid), 9) # Stop the Qt application QApplication.quit()
class MainWindow(BaseWindow): __contextName__ = 'MainWindow' windowFocusChanged = pyqtSignal('QWindow*', arguments=['window']) mousePressed = pyqtSignal('QPointF', arguments=['point']) wheel = pyqtSignal('QPointF', arguments=['point']) @registerContext def __init__(self, engine=None, parent=None): super(MainWindow, self).__init__(engine, parent) self._initSystemTray() self._initConnect() def _initConnect(self): from PyQt5.QtWidgets import qApp qApp.focusWindowChanged.connect(self.changeFocusWindow) signalManager.hideShowWindowToggle.connect(self.actionhideShow) def _initSystemTray(self): from PyQt5.QtWidgets import qApp self.systemTray = QSystemTrayIcon(self) self.systemTray.setIcon(qApp.windowIcon()) self.systemTray.show() self.systemTray.activated.connect(self.onSystemTrayIconClicked) def onSystemTrayIconClicked(self, reason): if reason == QSystemTrayIcon.Unknown: pass elif reason == QSystemTrayIcon.Context: menuWorker.systemTrayMenuShowed.emit() elif reason == QSystemTrayIcon.DoubleClick: pass elif reason == QSystemTrayIcon.Trigger: self.setVisible(not self.isVisible()) elif reason == QSystemTrayIcon.MiddleClick: pass else: pass def mousePressEvent(self, event): self.mousePressed.emit(event.pos()) # 鼠标点击事件 if event.button() == Qt.LeftButton: flag = windowManageWorker.windowMode if flag == "Full": x = self.quickItems['mainTitleBar'].x() y = self.quickItems['mainTitleBar'].y() width = self.quickItems['mainTitleBar'].width() height = self.quickItems['mainTitleBar'].height() rect = QRect(x, y, width, height) if rect.contains(event.pos()): self.dragPosition = event.globalPos() - \ self.frameGeometry().topLeft() elif flag == "Simple": x = self.rootObject().x() y = self.rootObject().y() width = self.rootObject().width() height = 40 rect = QRect(x, y, width, height) if rect.contains(event.pos()): self.dragPosition = event.globalPos() - \ self.frameGeometry().topLeft() super(MainWindow, self).mousePressEvent(event) def changeFocusWindow(self, window): self.windowFocusChanged.emit(window) def wheelEvent(self, event): self.wheel.emit(QPointF(event.x(), event.y())) super(MainWindow, self).wheelEvent(event) def keyPressEvent(self, event): from controllers import configWorker, signalManager, mediaPlayer if configWorker.isShortcutEnable: modifier = QKeySequence(event.modifiers()).toString() keyString = QKeySequence(event.key()).toString() shortcut = modifier + keyString print shortcut if shortcut == configWorker.shortcut_preivous: signalManager.previousSong.emit() elif shortcut == configWorker.shortcut_next: signalManager.nextSong.emit() elif shortcut == configWorker.shortcut_volumnIncrease: signalManager.volumnIncrease.emit() elif shortcut == configWorker.shortcut_volumeDecrease: signalManager.volumnDecrease.emit() elif shortcut == configWorker.shortcut_playPause: signalManager.playToggle.emit(not mediaPlayer.playing) elif shortcut == configWorker.shortcut_simpleFullMode: signalManager.simpleFullToggle.emit() elif shortcut == configWorker.shortcut_miniFullMode: signalManager.miniFullToggle.emit() elif shortcut == configWorker.shortcut_hideShowWindow: signalManager.hideShowWindowToggle.emit() elif shortcut == configWorker.shortcut_hideShowDesktopLRC: signalManager.hideShowDesktopLrcToggle.emit() super(MainWindow, self).keyPressEvent(event) def actionhideShow(self): if self.windowState() == Qt.WindowActive: self.setWindowState(Qt.WindowMinimized) elif self.windowState() == Qt.WindowMinimized: self.setWindowState(Qt.WindowActive)
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): if self.check_box.isChecked(): event.ignore() self.hide() self.tray_icon.showMessage("Tray Program", "Application was minimized to Tray", QSystemTrayIcon.Information, 2000)
class MainWindow(BaseWindow): __contextName__ = 'MainWindow' windowFocusChanged = pyqtSignal('QWindow*', arguments=['window']) mousePressed = pyqtSignal('QPointF', arguments=['point']) wheel = pyqtSignal('QPointF', arguments=['point']) @registerContext def __init__(self, engine=None, parent=None): super(MainWindow, self).__init__(engine, parent) self._initSystemTray() self._initConnect() def _initConnect(self): from PyQt5.QtWidgets import qApp qApp.focusWindowChanged.connect(self.changeFocusWindow) signalManager.hideShowWindowToggle.connect(self.actionhideShow) def _initSystemTray(self): from PyQt5.QtWidgets import qApp self.systemTray = QSystemTrayIcon(self) self.systemTray.setIcon(qApp.windowIcon()) self.systemTray.show() self.systemTray.activated.connect(self.onSystemTrayIconClicked) def onSystemTrayIconClicked(self, reason): if reason == QSystemTrayIcon.Unknown: pass elif reason == QSystemTrayIcon.Context: menuWorker.systemTrayMenuShowed.emit() elif reason == QSystemTrayIcon.DoubleClick: pass elif reason == QSystemTrayIcon.Trigger: self.setVisible(not self.isVisible()) elif reason == QSystemTrayIcon.MiddleClick: pass else: pass def mousePressEvent(self, event): self.mousePressed.emit(event.pos()) # 鼠标点击事件 if event.button() == Qt.LeftButton: flag = windowManageWorker.windowMode if flag == "Full": x = self.quickItems['mainTitleBar'].x() y = self.quickItems['mainTitleBar'].y() width = self.quickItems['mainTitleBar'].width() height = self.quickItems['mainTitleBar'].height() rect = QRect(x, y, width, height) if rect.contains(event.pos()): self.dragPosition = event.globalPos() - \ self.frameGeometry().topLeft() elif flag == "Simple": x = self.rootObject().x() y = self.rootObject().y() width = self.rootObject().width() height = 40 rect = QRect(x, y, width, height) if rect.contains(event.pos()): self.dragPosition = event.globalPos() - \ self.frameGeometry().topLeft() super(MainWindow, self).mousePressEvent(event) def changeFocusWindow(self, window): self.windowFocusChanged.emit(window) def wheelEvent(self, event): self.wheel.emit(QPointF(event.x(), event.y())) super(MainWindow, self).wheelEvent(event) def keyPressEvent(self, event): from controllers import configWorker, signalManager, mediaPlayer if configWorker.isShortcutEnable: modifier = QKeySequence(event.modifiers()).toString() keyString = QKeySequence(event.key()).toString() shortcut = modifier + keyString print shortcut if shortcut == configWorker.shortcut_preivous: signalManager.previousSong.emit() elif shortcut == configWorker.shortcut_next: signalManager.nextSong.emit() elif shortcut == configWorker.shortcut_volumnIncrease: signalManager.volumnIncrease.emit() elif shortcut == configWorker.shortcut_volumeDecrease: signalManager.volumnDecrease.emit() elif shortcut == configWorker.shortcut_playPause: signalManager.playToggle.emit(not mediaPlayer.playing) elif shortcut == configWorker.shortcut_simpleFullMode: signalManager.simpleFullToggle.emit() elif shortcut == configWorker.shortcut_miniFullMode: signalManager.miniFullToggle.emit() elif shortcut == configWorker.shortcut_hideShowWindow: signalManager.hideShowWindowToggle.emit() elif shortcut == configWorker.shortcut_hideShowDesktopLRC: signalManager.hideShowDesktopLrcToggle.emit() super(MainWindow, self).keyPressEvent(event) def actionhideShow(self): if self.windowState() == Qt.WindowActive: self.setWindowState(Qt.WindowMinimized) elif self.windowState() == Qt.WindowMinimized: self.setWindowState(Qt.WindowActive)
class App: def __init__(self): print( f"{colors.PINK}{colors.BOLD}Welcome to Spotlightify{colors.RESET}\n\n" ) self.app = QApplication([]) self.app.setQuitOnLastWindowClosed(False) self.theme = default_themes["dark"] self.tray = None self.tray_menu = None self.action_open = None self.action_exit = None self.config = config self.spotlight = None self.spotify = None self.oauth = None self.token_info = None self.listener_thread = Thread(target=listener, daemon=True, args=(self.show_spotlight, )) self.song_queue = None self.image_queue = None self.cache_manager = None self.run() def run(self): print(self.config.username) if not self.config.is_valid(): app = QApplication([]) app.setQuitOnLastWindowClosed(True) auth = AuthUI() # ADD TUTORIAL WEBSITE webbrowser.open("https://alfred-spotify-mini-player.com/setup/", 2) while not self.config.is_valid(): auth.show() app.exec_() if auth.isCanceled: sys.exit() try: print("Starting auth process") self.oauth = self.config.get_oauth() self.token_info = self.oauth.get_access_token(as_dict=True) self.spotify = Spotify(auth=self.token_info["access_token"]) self.init_tray() self.listener_thread.start() self.song_queue = SongQueue() self.image_queue = ImageQueue() self.cache_manager = CacheManager(self.spotify, self.song_queue, self.image_queue) self.spotlight = SpotlightUI(self.spotify, self.song_queue) self.show_spotlight() while True: self.app.exec_() except Exception as ex: print(ex) def init_tray(self): self.tray_menu = QMenu() self.action_open = QAction("Open") self.action_open.triggered.connect(self.show_spotlight) self.tray_menu.addAction(self.action_open) self.action_exit = QAction("Exit") self.action_exit.triggered.connect(App.exit) self.tray_menu.addAction(self.action_exit) self.tray = QSystemTrayIcon() self.tray.setIcon(QIcon(f"{ASSETS_DIR}img{sep}logo_small.png")) self.tray.setVisible(True) self.tray.setToolTip("Spotlightify") self.tray.setContextMenu(self.tray_menu) self.tray.activated.connect( lambda reason: self.show_spotlight(reason=reason)) def show_spotlight(self, **kwargs): def focus_windows(): # Only way to focus UI on Windows mouse = Controller() # mouse position before focus mouse_pos_before = mouse.position # changing the mouse position for click target_pos_x = ui.pos().x() + ui.textbox.pos().x() target_pos_y = ui.pos().y() + ui.textbox.pos().y() mouse.position = (target_pos_x, target_pos_y) mouse.click(Button.left) mouse.position = mouse_pos_before if kwargs and kwargs["reason"] != 3: # if kwargs contains "reason" this has been invoked by the tray icon being clicked # reason = 3 means the icon has been left-clicked, so anything other than a left click # should open the context menu return ui = self.spotlight ui.show() ui.raise_() ui.activateWindow() ui.function_row.refresh(None) # refreshes function row button icons self.token_refresh() if "Windows" in platform(): focus_windows() def token_refresh(self): try: if self.oauth.is_token_expired(token_info=self.token_info): self.token_info = self.oauth.refresh_access_token( self.token_info["refresh_token"]) self.spotify.set_auth(self.token_info["access_token"]) except: print("[WARNING] Could not refresh user API token") @staticmethod def exit(): print(f"\n{colors.PINK}{colors.BOLD}Exiting{colors.RESET}") kill(getpid(), 3) exit(0)
class MailOverseer: def __init__(self, config): self._connection = None self._server = config.get('imap', 'server') self._login = config.get('imap', 'login') self._password = config.get('imap', 'password') self._last_unseen_stats = None self._last_unseen_count = None self._unseen_stats_delta = timedelta(seconds=int( config.get('overseer', 'unseen_stats_delay', fallback=60))) self._unseen_command = config.get('overseer', 'unseen_command', fallback=None) self._mailbox_blacklist = config.get('overseer', 'mailbox_blacklist', fallback=None) if self._mailbox_blacklist and isinstance(self._mailbox_blacklist, str): self._mailbox_blacklist = [ mb.strip() for mb in self._mailbox_blacklist.split(';') ] self._logger = logging.getLogger() self._logger.setLevel(logging.DEBUG) log_handler = logging.StreamHandler(sys.stdout) log_handler.setLevel( getattr(logging, config.get('overseer', 'log_level', fallback='INFO'))) log_handler.setFormatter( logging.Formatter('[MailOverseer] [%(levelname)s] %(message)s')) self._logger.addHandler(log_handler) self._mailboxes = [] # Unix stop signal bindings signal.signal(signal.SIGTERM, self.stop) signal.signal(signal.SIGQUIT, self.stop) signal.signal(signal.SIGINT, self.stop) signal.signal(signal.SIGABRT, self.stop) # Qt components self._app = QApplication([]) # System tray icon menu self._systray_menu = QMenu() self._systray_menu.addAction( QAction('Refresh', self._app, triggered=self._on_refresh_clicked)) self._systray_menu.addSeparator() self._systray_menu.addAction( QAction('Exit', self._app, triggered=self.stop)) # System tray icon tray_icon_path = os.path.join(ICONS_PATH, 'tray.png') self._default_icon = QIcon(tray_icon_path) self._unseen_mails_pixmap = QPixmap(tray_icon_path) self._current_unseen_mails_pixmap = None self._icon_max_unseen_count = config.get('tray', 'icon_max_unseen_count', fallback=99) icon_max_unseen_count_rect = config.get('tray', 'icon_max_unseen_count_rect', fallback='40, 28, 100, 100') self._icon_unseen_count_rect = QRect(*[ int(coord.strip()) for coord in icon_max_unseen_count_rect.split(',') ]) self._icon_unseen_count_font = QFont() self._icon_unseen_count_font_size_1d = int( config.get('tray', 'icon_unseen_count_font_size_1d', fallback=100)) self._icon_unseen_count_font_size_2d = int( config.get('tray', 'icon_unseen_count_font_size_2d', fallback=75)) self._icon_unseen_count_color = QColor( config.get('tray', 'icon_unseen_count_color', fallback='white')) self._icon_unseen_count_bubble_color = QColor( config.get('tray', 'icon_unseen_count_bubble_color', fallback='#f53a86')) self._icon_unseen_count_bubble_border_pen = QPen( QColor( config.get('tray', 'icon_unseen_count_bubble_border_color', fallback='black'))) self._icon_unseen_count_bubble_border_pen.setWidth( int( config.get('tray', 'icon_unseen_count_bubble_border_width', fallback=8))) self._tray_icon = QSystemTrayIcon(self._default_icon) self._tray_icon.setContextMenu(self._systray_menu) self._tray_icon.activated.connect(self._on_tray_icon_activated) self._systray_click_command = config.get('tray', 'on_click_command', fallback=None) # Timer running unseen mail checks self._main_timer = QTimer() self._main_timer.timeout.connect(self._check_unseen_mails) def run(self): self._tray_icon.show() self._main_timer.start(500) return_code = self._app.exec_() self._disconnect() if self._unseen_command: subprocess.run([self._unseen_command, '0']) return return_code def stop(self, *_, **__): """ Received stop request (Qt / Unix Signal / etc.) """ self._logger.info('Stopping...') self._main_timer.stop() self._app.quit() def _disconnect(self): """ Close SMTP connection """ if self.connected: self._connection.logout() self._connection = None def _on_tray_icon_activated(self, activation_reason: int): """ Clicked on the tray icon """ # Activation via simple click if activation_reason != QSystemTrayIcon.Context: # Call external command if defined in the configuration if self._systray_click_command: self._logger.debug('Calling: {}'.format( self._systray_click_command)) subprocess.run(self._systray_click_command.split(' ')) # Refresh mails self._check_unseen_mails(True) def _on_refresh_clicked(self): """ Clicked on refresh button """ self._check_unseen_mails(True) @property def connected(self): return self._connection is not None def _check_unseen_mails(self, force=False): try: if self.connected: now = datetime.now() if (self._last_unseen_stats is None or self._last_unseen_stats < (now - self._unseen_stats_delta) or force): self._last_unseen_stats = now unseen = self._get_total_unseen_count() if unseen != self._last_unseen_count: self._last_unseen_count = unseen self._logger.info( 'New unseen count: {}'.format(unseen)) if self._unseen_command: self._logger.debug('Calling: {}'.format( self._unseen_command)) subprocess.run([self._unseen_command, str(unseen)]) self._tray_icon.setIcon( self._gen_unseen_icon(unseen) if unseen > 0 else self._default_icon) else: self._connect() except imaplib.IMAP4.error: self._logger.exception('IMAP error !') self._disconnect() def _connect(self): """ Initiate IMAP connection """ assert self._connection is None self._logger.info('Connecting to {}...'.format(self._server)) self._connection = imaplib.IMAP4_SSL(self._server) err_code, err_message = self._connection.login(self._login, self._password) if err_code != 'OK': self._logger.error('Failed to connect to {}: {}'.format( self._server, err_message)) self._connection = None return self._on_connection_success() def _on_connection_success(self): """ IMAP connection succeeded """ self._logger.info('Connection succeeded!') self._list_mailboxes() def _get_message_headers(self, msg_num): """ Fetch headers for the required message number """ err_code, raw_msg_headers = self._connection.fetch( msg_num, '(BODY[HEADER])') msg_headers = raw_msg_headers[0][1].decode().split('\r\n') headers = OrderedDict() for header in msg_headers: splitted = header.split(': ', 1) if len(splitted) > 1: headers[splitted[0].strip()] = splitted[1].strip() return headers def _get_total_unseen_count(self): """ Returns the total amount of unseen messages """ total_unseen = 0 for mb in self._mailboxes: mb_name = mb['name'] if mb_name in self._mailbox_blacklist: self._logger.debug( 'Mailbox "{}" is blacklisted'.format(mb_name)) continue unseen = self._get_unseen_count(mb_name) self._logger.debug('Mailbox "{}" has {} unseen mails'.format( mb_name, unseen)) total_unseen += unseen return total_unseen def _get_unseen_count(self, mailbox): """ Returns the amount of unseen messages in the specified mailbox :param mailbox: mailbox to check for unseen messages """ err_code, data = self._connection.status('"{}"'.format(mailbox), '(UNSEEN)') if err_code == 'OK': status_data = data[0].decode().strip() rmatch = re.match(r'.* \(UNSEEN (\d+)\)', status_data) if rmatch: return int(rmatch.group(1)) return 0 def _list_mailboxes(self): err_code, mailboxes = self._connection.list() if err_code != 'OK': self._logger.error('Failed to list mailboxes!') exit(1) mailboxes = [mb.decode() for mb in mailboxes] self._mailboxes = [] for mb in mailboxes: match = re.match(r'\((.*?)\) ".*" (.*)', mb) if match: self._mailboxes.append({ 'name': match.group(2).strip('"'), 'flags': match.group(1) }) def _gen_unseen_icon(self, unseen_count: int): """ Generates unseen mails icon :param unseen_count: amount of uneen mails """ # Pixmap must be kept in memory for later repaint self._current_unseen_mails_pixmap = self._unseen_mails_pixmap.copy() painter = QPainter(self._current_unseen_mails_pixmap) # Fill count bubble painter.setBrush(self._icon_unseen_count_bubble_color) painter.drawEllipse(self._icon_unseen_count_rect) # Draw count bubble border painter.setPen(self._icon_unseen_count_bubble_border_pen) painter.setBrush(Qt.NoBrush) painter.drawEllipse(self._icon_unseen_count_rect) # Write unread count count = str(self._icon_max_unseen_count if unseen_count > self. _icon_max_unseen_count else unseen_count) self._icon_unseen_count_font.setPixelSize( self._icon_unseen_count_font_size_2d if len(count) > 1 else self. _icon_unseen_count_font_size_1d) painter.setFont(self._icon_unseen_count_font) painter.setPen(self._icon_unseen_count_color) painter.drawText(self._icon_unseen_count_rect, Qt.AlignCenter, count) return QIcon(self._current_unseen_mails_pixmap)
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 FramelessWindow(QWidget, Ui_FramelessWindow): subUI = None def __init__(self, title="python", parent=None, icon=''): super(FramelessWindow, self).__init__(parent, Qt.FramelessWindowHint) self.setupUi(self) self._icon = icon # self.setMouseTracking(True) self.setTitle(title) self.icon = icon if icon != "" else ":/button_Ima/git.ico" self.contentLayout = QHBoxLayout(self.windowContent) self.setAttribute(Qt.WA_TranslucentBackground) # 透明 self.windowTitlebar.setAttribute(Qt.WA_StyledBackground, True) self.restoreButton.setVisible(False) # ================== 托盘图标 ==================# self.tray_icon = QIcon(self.icon) # 创建图标 self.tray = QSystemTrayIcon(self) # 创建系统托盘对象 self.tray.setIcon(self.tray_icon) # 设置系统托盘图标 self.MaxAction = QAction(u'最大化 ', self, triggered=lambda: self.setWindowState(Qt.WindowMaximized)) # 添加一级菜单动作选项(最大化主窗口) self.RestoreAction = QAction(u'还原 ', self, triggered=self.show) # 添加一级菜单动作选项(还原主窗口) self.QuitAction = QAction(u'退出 ', self, triggered=self.close) # 添加一级菜单动作选项(退出程序) self.tray_menu = QMenu(QApplication.desktop()) # 创建菜单 self.tray_menu.addAction(self.MaxAction) # 为菜单添加动作 self.tray_menu.addAction(self.RestoreAction) # 为菜单添加动作 self.tray_menu.addAction(self.QuitAction) self.tray.setContextMenu(self.tray_menu) # 设置系统托盘菜单 self.tray.show() # =================== 无 窗 体 拉 伸 ===================↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ # 设置widget鼠标跟踪 self.setMouseTracking(True) # 边框距离 self.SHADOW_WIDTH = 0 # 鼠标左键是否按下 self.isLeftPressDown = False # 拖动时坐标 self.dragPosition = 0 # 枚举参数 self.Numbers = self.enum( UP=0, DOWN=1, LEFT=2, RIGHT=3, LEFTTOP=4, LEFTBOTTOM=5, RIGHTBOTTOM=6, RIGHTTOP=7, NONE=8 ) # 初始鼠标状态 self.dir = self.Numbers.NONE def enum(self, **enums): return type('Enum', (), enums) def region(self, cursorGlobalPoint): # 获取窗体在屏幕上的位置区域, # tl为topleft点, # rb为rightbottom点 rect = self.rect() tl = self.mapToGlobal(rect.topLeft()) rb = self.mapToGlobal(rect.bottomRight()) x = cursorGlobalPoint.x() y = cursorGlobalPoint.y() if (tl.x() + PADDING >= x and tl.x() <= x and tl.y() + PADDING >= y and tl.y() <= y): # 左上角 self.dir = self.Numbers.LEFTTOP self.setCursor(QCursor(Qt.SizeFDiagCursor)) # 设置鼠标形状 elif (x >= rb.x() - PADDING and x <= rb.x() and y >= rb.y() - PADDING and y <= rb.y()): # 右下角 self.dir = self.Numbers.RIGHTBOTTOM self.setCursor(QCursor(Qt.SizeFDiagCursor)) elif (x <= tl.x() + PADDING and x >= tl.x() and y >= rb.y() - PADDING and y <= rb.y()): # 左下角 self.dir = self.Numbers.LEFTBOTTOM self.setCursor(QCursor(Qt.SizeBDiagCursor)) elif (x <= rb.x() and x >= rb.x() - PADDING and y >= tl.y() and y <= tl.y() + PADDING): # 右上角 self.dir = self.Numbers.RIGHTTOP self.setCursor(QCursor(Qt.SizeBDiagCursor)) elif (x <= tl.x() + PADDING and x >= tl.x()): # 左边 self.dir = self.Numbers.LEFT self.setCursor(QCursor(Qt.SizeHorCursor)) elif (x <= rb.x() and x >= rb.x() - PADDING): # 右边 self.dir = self.Numbers.RIGHT self.setCursor(QCursor(Qt.SizeHorCursor)) elif (y >= tl.y() and y <= tl.y() + PADDING): # 上边 self.dir = self.Numbers.UP self.setCursor(QCursor(Qt.SizeVerCursor)) elif (y <= rb.y() and y >= rb.y() - PADDING): # 下边 self.dir = self.Numbers.DOWN self.setCursor(QCursor(Qt.SizeVerCursor)) else: # 默认 self.dir = self.Numbers.NONE self.setCursor(QCursor(Qt.ArrowCursor)) def mouseReleaseEvent(self, event): if (event.button() == Qt.LeftButton): self.isLeftPressDown = False if (self.dir != self.Numbers.NONE): QTimer.singleShot(300, self.releaseMouse) self.setCursor(QCursor(Qt.ArrowCursor)) def mousePressEvent(self, event): if (event.button() == Qt.LeftButton): self.isLeftPressDown = True if (self.dir != self.Numbers.NONE): QTimer.singleShot(300, self.mouseGrabber) else: self.dragPosition = event.globalPos() \ - self.frameGeometry().topLeft() def mouseMoveEvent(self, event): gloPoint = event.globalPos() rect = self.rect() tl = self.mapToGlobal(rect.topLeft()) rb = self.mapToGlobal(rect.bottomRight()) if (not self.isLeftPressDown): self.region(gloPoint) else: if (self.dir != self.Numbers.NONE): rmove = QRect(tl, rb) if (self.dir == self.Numbers.LEFT): if (rb.x() - gloPoint.x() <= self.minimumWidth()): rmove.setX(tl.x()) else: rmove.setX(gloPoint.x()) elif (self.dir == self.Numbers.RIGHT): rmove.setWidth(gloPoint.x() - tl.x()) elif (self.dir == self.Numbers.UP): if (rb.y() - gloPoint.y() <= self.minimumHeight()): rmove.setY(tl.y()) else: rmove.setY(gloPoint.y()) elif (self.dir == self.Numbers.DOWN): rmove.setHeight(gloPoint.y() - tl.y()) elif (self.dir == self.Numbers.LEFTTOP): if (rb.x() - gloPoint.x() <= self.minimumWidth()): rmove.setX(tl.x()) else: rmove.setX(gloPoint.x()) if (rb.y() - gloPoint.y() <= self.minimumHeight()): rmove.setY(tl.y()) else: rmove.setY(gloPoint.y()) elif (self.dir == self.Numbers.RIGHTTOP): rmove.setWidth(gloPoint.x() - tl.x()) rmove.setY(gloPoint.y()) elif (self.dir == self.Numbers.LEFTBOTTOM): rmove.setX(gloPoint.x()) rmove.setHeight(gloPoint.y() - tl.y()) elif (self.dir == self.Numbers.RIGHTBOTTOM): rmove.setWidth(gloPoint.x() - tl.x()) rmove.setHeight(gloPoint.y() - tl.y()) else: pass self.setGeometry(rmove) else: try: self.move(event.globalPos() - self.dragPosition) except: pass event.accept() # =================== 无 窗 体 拉 伸 ===================↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ @pyqtSlot() def on_applicationStateChanged(state): pass @pyqtSlot() def on_windowTitlebar_doubleClicked(self): if (self.windowState() == Qt.WindowNoState): self.on_maximizeButton_clicked() elif (self.windowState() == Qt.WindowMaximized): self.on_restoreButton_clicked() @pyqtSlot() def on_minimizeButton_clicked(self): self.setWindowState(Qt.WindowMinimized) @pyqtSlot() def on_restoreButton_clicked(self): self.restoreButton.setVisible(False) self.maximizeButton.setVisible(True) self.setWindowState(Qt.WindowNoState) @pyqtSlot() def on_maximizeButton_clicked(self): self.restoreButton.setVisible(True) self.maximizeButton.setVisible(False) self.setWindowState(Qt.WindowMaximized) @pyqtSlot() def on_closeButton_clicked(self): self.close() # qApp.quit() try: sip.delete(self.tray) except: pass @property def icon(self): return self._icon @icon.setter def icon(self, value): self._icon = self.setIcon(value) def setIcon(self, icon: str): if isinstance(icon, QIcon): self.titleIconBtn.setIcon(icon) return icon elif isinstance(icon, str): icon_ico = QIcon() icon_ico.addPixmap(QPixmap(icon), QIcon.Normal, QIcon.Off) self.titleIconBtn.setIcon(icon_ico) return icon_ico else: raise Exception("icon need type str or QIcon") def setTitle(self, text): self.titleText.setText(text) def paintEvent(self, e): """titlebar background color """ painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing) # 反锯齿 # 外边框 颜色 # painter.setBrush(QBrush(QColor(85, 170, 255))) hex_color = os.getenv('border_color') painter.setBrush(QBrush(QColor(hex_color))) painter.setPen(Qt.transparent) rect = self.rect() rect.setWidth(rect.width()) rect.setHeight(rect.height()) painter.drawRoundedRect(rect, 4, 4) def setContent(self, w): """ add Window to self.contentLayout :param w:QWidget :return: """ self.contentLayout.setContentsMargins(0, 0, 0, 0) self.contentLayout.setSpacing(0) self.contentLayout.addWidget(w) self.windowContent.setLayout(self.contentLayout) self.subUI = w self.subUI.origin_enterEvent = self.subUI.enterEvent self.subUI.enterEvent = self.m_enterEvent # def enterEvent(self, event): # "have bug " # return super(QWidget, self.subUI).enterEvent(event) def m_enterEvent(self, e): """ redirect to MainWindow's enterEvent 重定向到 MainWindow's enterEvent """ self.dir = self.Numbers.NONE self.setCursor(QCursor(Qt.ArrowCursor)) return self.subUI.origin_enterEvent(e) def closeEvent(self, event): """ redirect to MainWindow's closeEvent 重定向到 MainWindow's closeEvent """ self.setWindowFlags(Qt.Widget) self.tray.setVisible(False) sip.delete(self.tray) return super(QWidget, self.subUI).closeEvent(event)
class ElectrumGui(PrintError): @profiler def __init__(self, config, daemon, plugins): set_language(config.get('language', get_default_language())) # Uncomment this call to verify objects are being properly # GC-ed when windows are closed #network.add_jobs([DebugMem([Abstract_Wallet, SPV, Synchronizer, # ElectrumWindow], interval=5)]) QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads) if hasattr(QtCore.Qt, "AA_ShareOpenGLContexts"): QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_ShareOpenGLContexts) if hasattr(QGuiApplication, 'setDesktopFileName'): QGuiApplication.setDesktopFileName('electrum.desktop') self.gui_thread = threading.current_thread() self.config = config self.daemon = daemon self.plugins = plugins self.windows = [] self.efilter = OpenFileEventFilter(self.windows) self.app = QElectrumApplication(sys.argv) self.app.installEventFilter(self.efilter) self.app.setWindowIcon(read_QIcon("electrum.png")) # timer self.timer = QTimer(self.app) self.timer.setSingleShot(False) self.timer.setInterval(500) # msec self.nd = None self.network_updated_signal_obj = QNetworkUpdatedSignalObject() self._num_wizards_in_progress = 0 self._num_wizards_lock = threading.Lock() # init tray self.dark_icon = self.config.get("dark_icon", False) self.tray = QSystemTrayIcon(self.tray_icon(), None) self.tray.setToolTip('Electrum') self.tray.activated.connect(self.tray_activated) self.build_tray_menu() self.tray.show() self.app.new_window_signal.connect(self.start_new_window) self.set_dark_theme_if_needed() run_hook('init_qt', self) def set_dark_theme_if_needed(self): use_dark_theme = self.config.get('qt_gui_color_theme', 'default') == 'dark' if use_dark_theme: try: import qdarkstyle self.app.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5()) except BaseException as e: use_dark_theme = False self.print_error('Error setting dark theme: {}'.format(repr(e))) # Even if we ourselves don't set the dark theme, # the OS/window manager/etc might set *a dark theme*. # Hence, try to choose colors accordingly: ColorScheme.update_from_widget(QWidget(), force_dark=use_dark_theme) def build_tray_menu(self): # Avoid immediate GC of old menu when window closed via its action if self.tray.contextMenu() is None: m = QMenu() self.tray.setContextMenu(m) else: m = self.tray.contextMenu() m.clear() for window in self.windows: submenu = m.addMenu(window.wallet.basename()) submenu.addAction(_("Show/Hide"), window.show_or_hide) submenu.addAction(_("Close"), window.close) m.addAction(_("Dark/Light"), self.toggle_tray_icon) m.addSeparator() m.addAction(_("Exit Electrum"), self.close) def tray_icon(self): if self.dark_icon: return read_QIcon('electrum_dark_icon.png') else: return read_QIcon('electrum_light_icon.png') def toggle_tray_icon(self): self.dark_icon = not self.dark_icon self.config.set_key("dark_icon", self.dark_icon, True) self.tray.setIcon(self.tray_icon()) def tray_activated(self, reason): if reason == QSystemTrayIcon.DoubleClick: if all([w.is_hidden() for w in self.windows]): for w in self.windows: w.bring_to_top() else: for w in self.windows: w.hide() def close(self): for window in self.windows: window.close() def new_window(self, path, uri=None): # Use a signal as can be called from daemon thread self.app.new_window_signal.emit(path, uri) def show_network_dialog(self, parent): if not self.daemon.network: parent.show_warning(_('You are using Electrum in offline mode; restart Electrum if you want to get connected'), title=_('Offline')) return if self.nd: self.nd.on_update() self.nd.show() self.nd.raise_() return self.nd = NetworkDialog(self.daemon.network, self.config, self.network_updated_signal_obj) self.nd.show() def _create_window_for_wallet(self, wallet): w = ElectrumWindow(self, wallet) self.windows.append(w) self.build_tray_menu() # FIXME: Remove in favour of the load_wallet hook run_hook('on_new_window', w) w.warn_if_watching_only() return w def count_wizards_in_progress(func): def wrapper(self: 'ElectrumGui', *args, **kwargs): with self._num_wizards_lock: self._num_wizards_in_progress += 1 try: return func(self, *args, **kwargs) finally: with self._num_wizards_lock: self._num_wizards_in_progress -= 1 return wrapper @count_wizards_in_progress def start_new_window(self, path, uri, *, app_is_starting=False): '''Raises the window for the wallet if it is open. Otherwise opens the wallet and creates a new window for it''' wallet = None try: wallet = self.daemon.load_wallet(path, None) except BaseException as e: traceback.print_exc(file=sys.stdout) QMessageBox.warning(None, _('Error'), _('Cannot load wallet') + ' (1):\n' + str(e)) # if app is starting, still let wizard to appear if not app_is_starting: return if not wallet: wallet = self._start_wizard_to_select_or_create_wallet(path) if not wallet: return # create or raise window try: for window in self.windows: if window.wallet.storage.path == wallet.storage.path: break else: window = self._create_window_for_wallet(wallet) except BaseException as e: traceback.print_exc(file=sys.stdout) QMessageBox.warning(None, _('Error'), _('Cannot create window for wallet') + ':\n' + str(e)) if app_is_starting: wallet_dir = os.path.dirname(path) path = os.path.join(wallet_dir, get_new_wallet_name(wallet_dir)) self.start_new_window(path, uri) return if uri: window.pay_to_URI(uri) window.bring_to_top() window.setWindowState(window.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive) window.activateWindow() return window def _start_wizard_to_select_or_create_wallet(self, path) -> Optional[Abstract_Wallet]: wizard = InstallWizard(self.config, self.app, self.plugins) try: path, storage = wizard.select_storage(path, self.daemon.get_wallet) # storage is None if file does not exist if storage is None: wizard.path = path # needed by trustedcoin plugin wizard.run('new') storage = wizard.create_storage(path) else: wizard.run_upgrades(storage) except (UserCancelled, GoBack): return except WalletAlreadyOpenInMemory as e: return e.wallet except (WalletFileException, BitcoinException) as e: traceback.print_exc(file=sys.stderr) QMessageBox.warning(None, _('Error'), _('Cannot load wallet') + ' (2):\n' + str(e)) return finally: wizard.terminate() # return if wallet creation is not complete if storage is None or storage.get_action(): return wallet = Wallet(storage) wallet.start_network(self.daemon.network) self.daemon.add_wallet(wallet) return wallet def close_window(self, window): if window in self.windows: self.windows.remove(window) self.build_tray_menu() # save wallet path of last open window if not self.windows: self.config.save_last_wallet(window.wallet) run_hook('on_close_window', window) self.daemon.stop_wallet(window.wallet.storage.path) def init_network(self): # Show network dialog if config does not exist if self.daemon.network: if self.config.get('auto_connect') is None: wizard = InstallWizard(self.config, self.app, self.plugins) wizard.init_network(self.daemon.network) wizard.terminate() def main(self): try: self.init_network() except UserCancelled: return except GoBack: return except BaseException as e: traceback.print_exc(file=sys.stdout) return self.timer.start() self.config.open_last_wallet() path = self.config.get_wallet_path() if not self.start_new_window(path, self.config.get('url'), app_is_starting=True): return signal.signal(signal.SIGINT, lambda *args: self.app.quit()) def quit_after_last_window(): # keep daemon running after close if self.config.get('daemon'): return # check if a wizard is in progress with self._num_wizards_lock: if self._num_wizards_in_progress > 0 or len(self.windows) > 0: return self.app.quit() self.app.setQuitOnLastWindowClosed(False) # so _we_ can decide whether to quit self.app.lastWindowClosed.connect(quit_after_last_window) def clean_up(): # Shut down the timer cleanly self.timer.stop() # clipboard persistence. see http://www.mail-archive.com/[email protected]/msg17328.html event = QtCore.QEvent(QtCore.QEvent.Clipboard) self.app.sendEvent(self.app.clipboard(), event) self.tray.hide() self.app.aboutToQuit.connect(clean_up) # main loop self.app.exec_() # on some platforms the exec_ call may not return, so use clean_up() def stop(self): self.print_error('closing GUI') self.app.quit()
class MainWindow(QMainWindow, Ui_MainWindow): spider = None threads = None startTime = 0 emailCount = 0 thread_load_email = None thread_check_network = None tray = None dlgSetting = None settings = { "chkMinToTray":True, "chkStart":False, "chkEnd":False, "timeStart":None, "timeEnd":None, } timeStartDate = None timeEndDate = None def __init__(self, parent=None): super(MainWindow, self).__init__(parent) self.setupUi(self) self.spider = Spider() self.spider.signal.connect(self.log) self.spider.init() self.threads = [] def changeEvent(self, e): if e.type() == QEvent.WindowStateChange: if self.isMinimized() and isWindows(): #print("窗口最小化") #print(self.settings) if self.settings["chkMinToTray"]: self.hide() elif self.isMaximized(): #print("窗口最大化") pass elif self.isFullScreen(): #print("全屏显示") pass elif self.isActiveWindow(): #print("活动窗口") pass elif e.type()==QEvent.ActivationChange: #self.repaint() pass def closeEvent(self, event): reply = QMessageBox.question(self, '提示', "是否要退出程序吗?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: self.exitSystem() sys.exit() else: event.ignore() def addContextMenu(self): self.addEmailContextMenu() self.addLogContextMenu() def addEmailContextMenu(self): self.txt_email.setContextMenuPolicy(Qt.CustomContextMenu) self.txt_email.customContextMenuRequested.connect(self.showEmailPopupMenu) self.emailContextMenu = QMenu(self) self.clearEmailAction = self.emailContextMenu.addAction('清空数据') self.clearEmailAction.triggered.connect(lambda: self.doMenuEvent('clear')) self.backupEmailAction = self.emailContextMenu.addAction('数据备份') self.backupEmailAction.triggered.connect(lambda: self.doMenuEvent('backup')) self.exportEmailAction = self.emailContextMenu.addAction('导出数据') self.exportEmailAction.triggered.connect(lambda: self.doMenuEvent('export')) self.refreshEmailAction = self.emailContextMenu.addAction('刷新数据') self.refreshEmailAction.triggered.connect(lambda: self.doMenuEvent('reload')) def addLogContextMenu(self): self.txt_log.setContextMenuPolicy(Qt.CustomContextMenu) self.txt_log.customContextMenuRequested.connect(self.showLogPopupMenu) self.logContextMenu = QMenu(self) self.clearLogAction = self.logContextMenu.addAction('清空数据') self.clearLogAction.triggered.connect(lambda: self.doMenuEvent('clearLog')) self.exportLogAction = self.logContextMenu.addAction('导出数据') self.exportLogAction.triggered.connect(lambda: self.doMenuEvent('exportLog')) def showEmailPopupMenu(self, pos): self.emailContextMenu.exec_(QCursor.pos()) def showLogPopupMenu(self, pos): self.logContextMenu.exec_(QCursor.pos()) def doMenuEvent(self, action): if action == 'reload': self.loadEmail() elif action == 'clear': reply = QMessageBox.question(self, '提示', "您确定要清空所有已保存的Email文件吗,建议您先备份数据?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: self.spider.emptyEmail() self.loadEmail() elif action == 'clearLog': self.txt_log.clear() elif action == 'backup': self.spider.backupEmail() elif action == 'export': f = QFileDialog.getSaveFileName(self,"保存文件", None ,"Text files (*.txt);;All files(*.*)") self.spider.exportEmail(f[0], self.txt_email.toPlainText()) elif action == 'exportLog': f = QFileDialog.getSaveFileName(self,"保存文件", None ,"Text files (*.txt);;All files(*.*)") self.spider.exportLog(f[0], self.txt_log.toPlainText()) def addSystemTray(self): self.tray = QSystemTrayIcon() #self.icon = QIcon(self.spider.logoIconPath) self.icon = MyIcon.getLogoIcon() self.tray.setIcon(self.icon) self.tray.activated.connect(self.clickTray) self.tray.messageClicked.connect(self.clickTray) self.tray_menu = QMenu(QApplication.desktop()) self.RestoreAction = QAction('显示', self, triggered=self.restoreAction) self.SettingAction = QAction('设置', self, triggered=self.settingAction) self.QuitAction = QAction('退出', self, triggered=self.exitAction) self.tray_menu.addAction(self.RestoreAction) self.tray_menu.addAction(self.SettingAction) self.tray_menu.addAction(self.QuitAction) self.tray.setContextMenu(self.tray_menu) self.tray.show() def settingAction(self): self.dlgSetting = DlgSetting(self) self.dlgSetting.show() def exitAction(self): reply = QMessageBox.question(self, '提示', "是否要退出程序吗?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: self.exitSystem() sys.exit() def clickTray(self, reason): if reason != QSystemTrayIcon.DoubleClick: return self.restoreAction() def restoreAction(self): if self.isMaximized(): self.showMaximized() elif self.isFullScreen(): self.showFullScreen() else: self.showNormal() self.activateWindow() #scrollbar self.txt_email.verticalScrollBar().setValue(self.txt_email.verticalScrollBar().maximum()) self.txt_log.verticalScrollBar().setValue(self.txt_log.verticalScrollBar().maximum()) @pyqtSlot() def windowIconChanged(self, icon): print('changed.') @pyqtSlot() def on_btn_search_link_clicked(self): if len(self.threads) <= 0: self.startTime = time.time() self.btn_start.setDisabled(True) self.btn_resume.setDisabled(True) self.btn_stop.setDisabled(False) t = threading.Thread(target=self.spider.fetchSearchPageTask,args=()) self.threads.append(t) self.threads[0].setDaemon(True) self.threads[0].start() else: self.log({'str':'已经在有其他进程在运行了,请稍后运行!','extra':None}) @pyqtSlot() def on_btn_saveKeyword_clicked(self): if len(self.threads)>0: self.log({'str':'已经在有其他进程在运行了,请稍后运行!','extra':None}) return if self.txt_keyword.toPlainText().strip() == '': QMessageBox.critical(self, '提示', "请输入关键词!", QMessageBox.Ok, QMessageBox.Ok) return #check if self.spider.workInterupted(): reply = QMessageBox.question(self, '提示', "任务尚未完成,您确定要重新设置吗?建议您继续运行完成后重新设置!", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) allow = reply == QMessageBox.Yes else: allow = True if not allow: return self.spider.saveConfig({ "isSearchList": 1 if self.chk_searchList.isChecked() else 0, "searchPageSize": self.txt_pageSize.text(), "workQueueSize": self.txt_workSize.text(), "searchListDelay": self.txt_searchDelay.text(), "searchPageDelay": self.txt_pageDelay.text(), "resultMatchPatternType":self.combo_pattern.currentIndex(), "maxThreadNum":self.txt_threadNum.text(), "searchSource":self.combo_source.currentIndex() }) if self.spider.saveKeyword(self.txt_keyword.toPlainText()) and self.spider.saveKeywordFlag(self.txt_keyword_flag.toPlainText()): self.spider.saveResultMatchPattern(self.txt_pattern.toPlainText()) self.loadSettingData() #start self.btn_start.setDisabled(True) self.btn_resume.setDisabled(True) self.btn_stop.setDisabled(False) self.btn_saveKeyword.setDisabled(True) self.startTime = time.time() self.progressBar.setValue(0) #thread self.threads.clear() t = threading.Thread(target=self.spider.createSearchUrlTask,args=()) self.threads.append(t) self.threads[0].setDaemon(True) self.threads[0].start() @pyqtSlot() def on_btn_start_clicked(self): if not self.spider.isNetworkConnected(): QMessageBox.critical(self, '提示', "网络故障,请稍后重试!", QMessageBox.Ok, QMessageBox.Ok) return if len(self.threads) <= 0: if self.spider.getWorkConfig() == None: QMessageBox.critical(self, '提示', "请先保存设置项!", QMessageBox.Ok, QMessageBox.Ok) return #check if self.spider.workInterupted(): reply = QMessageBox.question(self, '提示', "任务尚未完成,您确定要重新运行吗?建议您继续运行!", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) allow = reply == QMessageBox.Yes else: allow = True if not allow: return self.startTime = time.time() self.progressBar.setValue(0) self.btn_start.setDisabled(True) self.btn_resume.setDisabled(True) self.btn_stop.setDisabled(False) self.btn_saveKeyword.setDisabled(True) self.threads.clear() t = threading.Thread(target=self.spider.fetchSearchListTask,args=(True,False)) self.threads.append(t) self.threads[0].setDaemon(True) self.threads[0].start() else: self.log({'str':'已经在有其他进程在运行了,请稍后运行!','extra':None}) @pyqtSlot() def on_btn_resume_clicked(self): if not self.spider.isNetworkConnected(): QMessageBox.critical(self, '提示', "网络故障,请稍后重试!", QMessageBox.Ok, QMessageBox.Ok) return if self.spider.getWorkConfig() == None: QMessageBox.critical(self, '提示', "请先保存设置项!", QMessageBox.Ok, QMessageBox.Ok) return self.resumeAllTask() def resumeAllTask(self, status=-1): if not self.spider.isNetworkConnected(): self.log({'str':'网络故障,请稍后重试!','extra':None}) return if len(self.threads) <= 0: if status != -2: self.startTime = time.time() self.progressBar.setValue(0) self.btn_start.setDisabled(True) self.btn_resume.setDisabled(True) self.btn_stop.setDisabled(False) self.btn_saveKeyword.setDisabled(True) self.threads.clear() t = threading.Thread(target=self.spider.fetchSearchListTask,args=(True,True)) self.threads.append(t) self.threads[0].setDaemon(True) self.threads[0].start() else: self.log({'str':'已经在有其他进程在运行了,请稍后运行!','extra':None}) @pyqtSlot() def on_btn_stop_clicked(self): self.stopAllTask() def stopAllTask(self, status=-1): self.log({'str':'开始停止所有任务...','extra':None}) self.spider.stopCreateSearchUrlTask() self.spider.stopSearchListTask(status) self.spider.stopSearchPageTask(status) self.threads.clear() self.btn_start.setDisabled(False) self.btn_resume.setDisabled(False) self.btn_stop.setDisabled(True) self.btn_saveKeyword.setDisabled(False) if status != -2: self.startTime = 0 def loadSettingData(self): keyword = self.spider.getKeyword() keywordFlag = self.spider.getKeywordFlag() self.txt_keyword.setPlainText(keyword) self.txt_keyword_flag.setPlainText(keywordFlag) self.txt_pattern.setPlainText(self.spider.getResultMatchPattern()) self.title_keyword.setText('搜索关键词(%s)' % (format(len(keyword.split('\n')),'0,') if keyword!='' else 0)) self.title_keywordFlag.setText('附加关键词(%s)' % (format(len(keywordFlag.split('\n')),'0,') if keywordFlag!='' else 0)) #load config self.config = self.spider.getConfig() self.chk_searchList.setChecked(1 if self.config['isSearchList']=='1' else 0) self.txt_pageSize.setText(self.config['searchPageSize']) self.txt_workSize.setText(self.config['workQueueSize']) self.txt_searchDelay.setText(self.config['searchListDelay']) self.txt_pageDelay.setText(self.config['searchPageDelay']) self.txt_pageDelay.setText(self.config['searchPageDelay']) self.txt_threadNum.setText(self.config['maxThreadNum']) self.combo_pattern.setCurrentIndex(int(self.config['resultMatchPatternType'])) if len(self.config['searchSourceList']) > 1: self.combo_source.clear() for i in self.config['searchSourceList']: self.combo_source.addItem(i) self.combo_source.setCurrentIndex(int(self.config['searchSource'])) else: self.combo_source.setHidden(True) self.progressBar.setValue(self.spider.getWorkProgress()) if self.config['debug'] == '1' and self.config['isSearchList'] == '1': self.btn_search_link.setHidden(False) else: self.btn_search_link.setHidden(True) if self.spider.getKeyword() == '': self.btn_start.setDisabled(True) self.btn_resume.setDisabled(True) def loadEmail(self): if not self.thread_load_email or not self.thread_load_email.isAlive(): self.thread_load_email = threading.Thread(target=self.spider.loadEmails,args=()) self.thread_load_email.setDaemon(True) self.thread_load_email.start() self.log({'str':'正在从文件中载入Email...','extra':None}) else: self.log({'str':'Email线程正在运行,请等待!','extra':None}) def setTimer(self): if self.startTime>0: self.lbl_timer.setText(self.convertTime(time.time() - self.startTime)) #other setting if self.settings['chkStart']: startTime = time.strptime(time.strftime('%Y-%m-%d') + ' ' + self.settings['timeStart'], '%Y-%m-%d %H:%M:%S') if time.localtime() >= startTime and self.timeStartDate != time.strftime('%Y-%m-%d'): self.timeStartDate = time.strftime('%Y-%m-%d') self.resumeAllTask(-1) if self.settings['chkEnd']: endTime = time.strptime(time.strftime('%Y-%m-%d') + ' ' + self.settings['timeEnd'], '%Y-%m-%d %H:%M:%S') if time.localtime() >= endTime and self.timeEndDate != time.strftime('%Y-%m-%d'): self.timeEndDate = time.strftime('%Y-%m-%d') self.stopAllTask(-1) def convertTime(self, raw_time): hour = int(raw_time // 3600) minute = int((raw_time % 3600) // 60) second = int(raw_time % 60) return '{:0>2d}:{:0>2d}:{:0>2d}'.format(hour, minute, second) def clearSystem(self): log = self.txt_log.toPlainText() c = log.count('\n') + 1 maxLine = self.spider.autoSaveLogSize if c > maxLine: if self.spider.saveLog(log): self.txt_log.clear() del log self.spider.saveSystemData() self.loadEmail() def exitSystem(self): if self.tray: self.tray.hide() self.spider.exitSystem() def exitAll(self): if self.tray: self.tray.hide() sys.exit() def log(self, o): self.txt_log.appendPlainText('['+time.strftime("%H:%M:%S", time.localtime()) + ']' + o['str']) if o['extra']: if o['extra'][0] == 'exit': QMessageBox.critical(self, '提示', o['str'], QMessageBox.Ok, QMessageBox.Ok) sys.exit() if o['extra'][0] == 'data-loaded': self.emailCount = len(o['extra'][1]) self.txt_email.setPlainText("".join(o['extra'][1])) self.lbl_email.setText("已载入 %s 个Email" % format(self.emailCount,'0,')) #self.txt_email.verticalScrollBar().setValue(self.txt_email.verticalScrollBar().maximum()) elif o['extra'][0] == 'data-update': self.emailCount += len(o['extra'][1]) self.txt_email.appendPlainText("\n".join(o['extra'][1])) self.lbl_email.setText("已获取 %s 个Email" % format(self.emailCount,'0,')) #self.txt_email.verticalScrollBar().setValue(self.txt_email.verticalScrollBar().maximum()) elif o['extra'][0] == 'createurl-end': self.btn_start.setDisabled(False) self.btn_resume.setDisabled(False) self.btn_stop.setDisabled(False) self.btn_saveKeyword.setDisabled(False) self.threads.clear() self.startTime = 0 elif o['extra'][0] == 'link-end': self.btn_start.setDisabled(False) self.btn_resume.setDisabled(False) self.btn_stop.setDisabled(False) self.btn_saveKeyword.setDisabled(False) self.threads.clear() if not self.chk_searchList.isChecked() and not self.spider.taskInterrupted(): self.startTime = 0 elif o['extra'][0] == 'work-end': self.btn_start.setDisabled(False) self.btn_resume.setDisabled(False) self.btn_stop.setDisabled(False) self.btn_saveKeyword.setDisabled(False) self.threads.clear() if not self.spider.taskInterrupted(): self.startTime = 0 elif o['extra'][0] == 'check-end' and o['extra'][1]['allowUpgrade']: reply = QMessageBox.question(self, '提示', "发现新版本,您确认要更新吗?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: mainScript = os.path.basename(sys.argv[0])[0:os.path.basename(sys.argv[0]).rfind('.')] self.spider.runScriptFile(self.spider.updaterFilePath, ' -s' + mainScript) self.exitAll() elif o['extra'][0] == 'network-stop': self.stopAllTask(-2) elif o['extra'][0] == 'network-resume': self.resumeAllTask(-2) #progress if o['extra'][0] in ['link-end','work-end','link-progress','work-progress','createurl-progress']: o['extra'][1]>self.progressBar.value() and self.progressBar.setValue(o['extra'][1]) #scrollbar #self.txt_log.verticalScrollBar().setValue(self.txt_log.verticalScrollBar().maximum()) def loadData(self): self.btn_stop.setDisabled(True) self.txt_log.setReadOnly(True) self.txt_log.clear() self.txt_email.clear() self.txt_keyword.clear() self.txt_keyword_flag.clear() self.txt_pattern.clear() self.loadEmail() self.timer = QTimer() self.timer.timeout.connect(self.setTimer) self.timer.start(1000) self.log_timer = QTimer() self.log_timer.timeout.connect(self.clearSystem) self.log_timer.start(self.spider.clearSystemInterval) self.network_timer = QTimer() self.network_timer.timeout.connect(self.checkNetworkStatus) self.network_timer.start(self.spider.networkConnectionCheckInterval) #self.startTime = time.time() self.loadSettingData() #check upgrade t1 = threading.Thread(target=self.spider.checkUpgrade,args=()) t1.setDaemon(True) t1.start() if self.config['debug'] == '1': self.usage_timer = QTimer() self.usage_timer.timeout.connect(self.loadUsage) self.usage_timer.start(self.spider.systemMonitorInterval) self.loadUsage() #spitter splitter = QSplitter(self) splitter.addWidget(self.groupBox) splitter.addWidget(self.groupBox_2) splitter.addWidget(self.groupBox_3) splitter.setContentsMargins(9,9,9,9) splitter.setOrientation(Qt.Vertical) self.setCentralWidget(splitter) def loadUsage(self): self.statusBar().showMessage(self.spider.getUsageInfo()) def checkNetworkStatus(self): if not self.thread_check_network or not self.thread_check_network.isAlive(): self.thread_check_network = threading.Thread(target=self.spider.checkNetworkStatus,args=()) self.thread_check_network.setDaemon(True) self.thread_check_network.start() #self.log({'str':'正在启动检查网络连接...','extra':None}) else: #self.log({'str':'检查网络连接线程正在运行,请等待!','extra':None}) pass
class MyApp(QtWidgets.QMainWindow): mouseLeaveTimer=0 def __init__(self): # Ui_MainWindow.__init__(self) #自己有__init__函数时,不会默认调用基类的__init__函数 # 因为这里重写了__init__将基类的覆盖掉了,故需要主动调用之 # QtWidgets.QMainWindow.__init__(self) # super(MyApp,self).__init__() #上面两句的作用是相同的,下面这句是python3的新写法 super().__init__() # Get the Screen size self.screenWidth=QDesktopWidget().availableGeometry().width() self.screenHeight=QDesktopWidget().availableGeometry().height() #初始化字体 font=QFont('黑体') font.setPointSize(12) app.setFont(font) # ColorSetting self.bgColor=QColor(66,66,77,88) # # self.setupUi(self) self.initUI() #用来控制半透明的bg面板自动消失 self.timer=QTimer() self.timer.start(30) self.setGeometry(0,30,self.screenWidth,self.screenHeight//3) #Flagsq self.IsMouseHover=False self.MouseOver=False self.Locked=False self.Hidden=False self.isDrag=False self.isResize=False #变量初始化 GLOBAL.WINDOWWIDTH=self.width() GLOBAL.WINDOWHEIGHT=self.height() self.bullets=[] self.dragPos=QPoint(22,22) self.savedName='' # self.screenBuffer=QBitmap(GLOBAL.WINDOWWIDTH,GLOBAL.WINDOWHEIGHT) # self.bufferPainter=QPainter(self.screenBuffer) # self.picture=QPicture() # 建立connection和slot的回调连接 self.createConnections() # 连接到nodejs建立的服务器 self.connect2Server() def initUI(self): #构建托盘 self.trayIcon=QSystemTrayIcon(self) self.trayIcon.setIcon(QtGui.QIcon("tmpIcon.ico")) self.trayIcon.show() self.trayIcon.setToolTip('BulletGo') # 构建托盘菜单 action_quit=QAction('退出',self) action_quit.triggered.connect(self.exitApp) action_switchLock=QAction('锁定/解锁(F6)',self) action_switchLock.triggered.connect(self.switchLock) action_showHide=QAction('显示/隐藏(F7)',self) action_showHide.triggered.connect(lambda:self.switchVisible(self)) action_Settings=QAction('设置',self) action_Settings.triggered.connect(lambda:self.switchVisible(self.settingWindow)) trayIconMenu=QtWidgets.QMenu(self) trayIconMenu.addAction(action_switchLock) trayIconMenu.addAction(action_showHide) trayIconMenu.addSeparator() trayIconMenu.addAction(action_Settings) trayIconMenu.addAction(action_quit) #设定快捷键 QtWidgets.QShortcut(QtGui.QKeySequence(\ QtCore.Qt.Key_F7),self,\ (lambda:self.switchVisible(self.settingWindow))) QtWidgets.QShortcut(QtGui.QKeySequence(\ QtCore.Qt.Key_F6),self,\ (self.switchLock)) self.trayIcon.setContextMenu(trayIconMenu) # 保障不按下鼠标也追踪mouseMove事件 self.setMouseTracking(True) self.setMinimumSize(600,260) self.setWindowTitle("BulletGo") sizeGrip=QtWidgets.QSizeGrip(self) self.setWindowFlags(Qt.FramelessWindowHint\ |Qt.WindowStaysOnTopHint|Qt.Window|\ Qt.X11BypassWindowManagerHint) #Plan A self.setAttribute(Qt.WA_TranslucentBackground,True) #这一句是给Mac系统用的,防止它绘制(很黯淡的)背景 self.setAutoFillBackground(False) QSizeGrip(self).setVisible(True) sizeGrip.setVisible(True) #Plan B 失败 # palette=QPalette() # color=QColor(190, 230, 250) # color.setAlphaF(0.6) # palette.setBrush(self.backgroundRole(), color) # self.setPalette(palette) # self.setAutoFillBackground(True) # self.setBackgroundRole(QPalette.Window) #创建房间的Button和 输入框 self.roomName=QPlainTextEdit() self.roomName.setPlaceholderText('请输入房间名') # self.roomName.resize(50,20) # self.roomName.move(0,0) # self.roomName.setBackgroundVisible(False) self.createBtn=QPushButton("创建/进入") self.hideBtn=QPushButton('隐藏本设置窗口') self.hideBtn.clicked.connect(self.joinRoom) # lambda:self.switchVisible(self.settingWindow)) # self.createBtn.resize(50,20) # self.move(0,100) # self.d settingLayout=QVBoxLayout() hLayout=QHBoxLayout() settingLayout.addWidget(self.roomName) hLayout.addWidget(self.hideBtn) hLayout.addWidget(self.createBtn) self.settingWindow=QWidget() # self.hideBtn=setShortcut(QtGui.QKeySequence('Ctrl+B')) settingLayout.addLayout(hLayout) self.settingWindow.setLayout(settingLayout) # Qt.Tool的作用是 不在任务栏显示 self.settingWindow.setWindowFlags(Qt.FramelessWindowHint|Qt.Tool\ |Qt.X11BypassWindowManagerHint|Qt.Popup) self.roomName.show() self.createBtn.show() self.settingWindow.resize(160,26) self.settingWindow.show() # self.btnFire=QPushButton("Fire",self) # self.btnFire.resize(60,60) # self.btnFire.move(100,30) # self.btnFire.show() # self.btnLock=QPushButton("Lock",self) # self.btnLock.resize(40,40) # self.btnLock.move(self.screenWidth/2,30) # self.btnLock.setFlat(True) # self.btnLock.setIcon(QtGui.QIcon("tmpIcon.png")) # self.btnLock.show() # self.danmakuEditText=QPlainTextEdit(self) # self.danmakuEditText.resize(200,100) # self.danmakuEditText.move(100,100) # self.danmakuEditText.setBackgroundVisible(False) # self.danmakuEditText.show() def joinRoom(self): name=self.roomName.toPlainText() self.socketio.emit('join',name) def connect2Server(self): self.socketio=SocketIO('115.159.102.76/bulletgo_client',80,LoggingNamespace) self.registerEvents() # 开新线程监听服务器发来的消息,否则主线程被阻塞 _thread.start_new_thread(self.socketio.wait,()) def registerEvents(self): self.socketio.on('create_rsp',lambda rsp:self.handleIncomeBullet(rsp)) self.socketio.on('bullet',lambda msg:self.handleIncomeBullet(msg)) self.socketio.on('control_msg',lambda msg:print\ ('---control message--- : '+msg)) self.socketio.on('sys_notification',lambda msg:print\ ('---system notification--- : '+msg)) def handleIncomeBullet(self,bulletMsg): textsAndInfo=self.preProcessText(bulletMsg) if(len(textsAndInfo)>1): self.fireABullet(textsAndInfo[0],self.genQColorFromStr(textsAndInfo[1])) def createRoom_Nodejs(self,name): self.socketio.emit('create_room',name) def fireBtn(self): txt=self.danmakuEditText.toPlainText() tmpbullet=Bullet(txt,GLOBAL.ORANGE,random.randrange(9,16,2)) self.bullets.append(tmpbullet) tmpbullet.prepare() # print(len(self.bullets)) # testStr="line1\nline2\nline3" # textsAndInfo=self.preProcessText(testStr) # print(len(textsAndInfo)) # print def fireABullet(self,txt,color=GLOBAL.ORANGE): tmpbullet=Bullet(txt,color,random.randrange(12,22,2)) self.bullets.append(tmpbullet) tmpbullet.prepare() def createConnections(self): self.timer.timeout.connect(self.update) # self.btnFire.clicked.connect(self.fireBtn) self.createBtn.clicked.connect(\ lambda:self.createRoom_Nodejs\ (self.roomName.toPlainText())) # self.btnLock.clicked.connect(self.switchLock) # self.btnLock.clicked.connect(self.pullMsg) self.trayIcon.activated.connect(self.trayClick) def switchVisible(self,handle): if(handle.isHidden()): handle.activateWindow() handle.setHidden(not handle.isHidden()) def trayClick(self,reason): #单击事件还没设计好 # if(reason==QSystemTrayIcon.Trigger): # self.switchVisible(self) if(reason==QSystemTrayIcon.DoubleClick): self.switchVisible(self.settingWindow) def switchLock(self): self.Locked=not self.Locked '''这个神奇的用法, 在js中也可用''' '''博客搞好后, 这个要单独写个文章''' def genQColorFromStr(self,color): # print(color) return{ 'white':GLOBAL.WHITE, 'green':GLOBAL.GREEN, 'red':GLOBAL.RED, 'pink':GLOBAL.PINK, 'purple':GLOBAL.PURPLE, 'darkblue':GLOBAL.DARKBLUE, 'blue':GLOBAL.BLUE, 'yellow':GLOBAL.YELLOW, 'cyan':GLOBAL.CYAN, 'orange':GLOBAL.ORANGE, '':GLOBAL.ORANGE }[color] def preProcessText(self,string): return string.split('`<') '''---[deprecated]---''' def realPullMsg(self): url='http://danmaku.applinzi.com/message.php' r = requests.post(url,data=self.savedName) r.encoding='utf-8' #预处理收到的字符串 # print(r.text) # r.te textsAndInfo=self.preProcessText(r.text) i=0 # print(textsAndInfo) # print(r.text) if(len(textsAndInfo)>1): while(i<len(textsAndInfo)-1): # print(len(textsAndInfo)) # print('ddddd') # print(i) self.fireABullet(textsAndInfo[i],self.genQColorFromStr(textsAndInfo[i+1])) i+=2 '''---[deprecated]---''' def pullMsg(self): _thread.start_new_thread(self.realPullMsg,()) '''---[deprecated]---''' def createRoom(self): #编码问题实在天坑!!! self.savedName=self.roomName.toPlainText().encode('utf-8')#保存自己的房间号 postData=self.roomName.toPlainText().encode('utf-8') r = requests.post('http://danmaku.applinzi.com/createroom.php',data=postData) r.encoding='utf-8' self.fireABullet(r.text) # print(r.encoding) if(len(r.text)==7): # 开始自动获取服务器上的消息内容 self.pullTimer=QTimer() self.pullTimer.start(2000) self.pullTimer.timeout.connect(self.pullMsg) # print(r.content) # print(r.text) def closeEvent(self,e): e.accept() def mouseReleaseEvent(self,e): if(e.button()==Qt.LeftButton): self.isDrag=False self.isResize=False def mousePressEvent(self,e): if e.button()==Qt.LeftButton: self.LDown=True # self.dragPos=e.globalPos()-self.frameGeometry().topLeft() self.dragPos=e.pos()#效果同上,鼠标相对窗口左上角的位置 if(GLOBAL.WINDOWWIDTH-e.pos().x()<16\ and GLOBAL.WINDOWHEIGHT-e.pos().y()<16): self.topLeft=self.frameGeometry().topLeft() self.isResize=True else: if(not self.Locked): self.isDrag=True # else: # if e.button()==Qt.RightButton: # self.exitApp() e.accept() def mouseMoveEvent(self,e): if(GLOBAL.WINDOWWIDTH-e.pos().x()<16\ and GLOBAL.WINDOWHEIGHT-e.pos().y()<16): #更改鼠标样式 self.setCursor(Qt.SizeFDiagCursor) else: self.setCursor(Qt.ArrowCursor) #如果是Resize,改变窗口大小 if(self.isResize): tmp=e.globalPos()-self.topLeft self.move(self.topLeft) self.resize(tmp.x(),tmp.y()) if (self.isDrag): self.move(e.globalPos()-self.dragPos) e.accept(); def enterEvent(self,e): self.MouseOver=True self.IsMouseHover=True return super(MyApp,self).enterEvent(e) def setMouseHoverFalse(self): # if(not self.MouseOver): self.IsMouseHover=self.MouseOver def leaveEvent(self,e): QTimer.singleShot(800,self.setMouseHoverFalse) self.MouseOver=False return super(MyApp,self).leaveEvent(e) def resizeEvent(self,e): GLOBAL.WINDOWWIDTH=self.width() GLOBAL.WINDOWHEIGHT=self.height() # self.screenBuffer=QBitmap(GLOBAL.WINDOWWIDTH,GLOBAL.WINDOWHEIGHT) # self.bufferPainter=QPainter(self.screenBuffer) # print('resized') # self.repaint() e.accept() def paintEvent(self,e): # Get the Painter painter=QPainter(self) font=QFont('黑体',GLOBAL.BULLETFONTSIZE,QFont.Bold) painter.setFont(font) #Draw a semi-Transparent rect whose size is the same with this window if(self.IsMouseHover and (not self.Locked)): painter.fillRect(0,0,GLOBAL.WINDOWWIDTH,GLOBAL.WINDOWHEIGHT\ ,self.bgColor) # painter.setBackground(QBrush(QColor(123,222,123,122))) #画所有bullet for b in self.bullets: b.draw(painter) for b in self.bullets: if(b.IsExpired): self.bullets.remove(b) # painter.drawPicture(0,0,self.picture) # painter.drawText(30,100,"Hello this is a PyQt5 App我也会说中文") return super(MyApp,self).paintEvent(e) def exitApp(self): self.trayIcon.hide() sys.exit()
@jit def maxc(datalist): temp = max(datalist) if temp <= 0: return round(0, 2) else: return round(temp, 2) if __name__ == '__main__': app = QApplication(sys.argv) md = MainCode() md.setWindowTitle("Queue Analyzer") md.show() tray = QSystemTrayIcon(md) tray.setIcon(QIcon(r"resources/ico.png")) tray.show() tray.showMessage("Message", "Queue Analyzer is Running", icon=0) md.setWindowIcon(QIcon(r"resources/ico.png")) temp1 = QPixmap(r"resources/InitialTemp1.png") md.ED_Graph_Label.setPixmap(temp1.scaled(480, 360)) temp2 = QPixmap(r"resources/InitialTemp2.png") md.Hospital_Graph_Label.setPixmap(temp2.scaled(480, 360)) temp3 = QPixmap(r"resources/InitialTemp3.png") md.ICU_Graph_Label.setPixmap(temp3.scaled(480, 360)) sys.exit(app.exec_())
class MarksTimeTracker(QMainWindow, Ui_MainWindow): runningEvent = None def __init__(self, parent=None): super(MarksTimeTracker, self).__init__(parent) self.setupUi(self) self.tabWidget.tabBar().hide() self.setupStatusIcon() # config self.config_path = os.path.join(os.path.expanduser('~'), '.config', 'markstimetracker') dir_util.mkpath(self.config_path) self.readConfig() # Setup DB engine = create_engine('sqlite:///' + os.path.join(self.config_path, 'markstimetracker.db')) init_db(engine) self.db = sessionmaker(bind=engine)() self.updateTaskList() self.updateTasksComboBox() self.checkForRunningTask() # Timers timer = QTimer(self) timer.timeout.connect(self.updateTimeSpent) timer.start(1000) self.idleTimeTimer = QTimer() self.idleTimeTimer.timeout.connect(self.detectIdleTime) self.checkIdleTime() self.remindTimer = QTimer() self.remindTimer.timeout.connect(self.remindTracking) self.checkRemind() self.redmineSyncTimer = QTimer() self.redmineSyncTimer.timeout.connect(self.doRedmineSync) self.checkRedmineSync() # Events self.startButton.clicked.connect(self.toggleEventButton) self.eventsPeriodComboBox.currentIndexChanged.connect(self.eventsPeriodChanged) self.editDurationSpinBox.valueChanged.connect(self.updateEditStartEndTime) self.editStartDateTimeEdit.dateTimeChanged.connect(self.updateDurationSpinBoxEndTime) self.editEndDateTimeEdit.dateTimeChanged.connect(self.updateDurationSpinBox) self.editButtonBox.accepted.connect(self.saveEvent) self.editButtonBox.rejected.connect(lambda: self.tabWidget.setCurrentIndex(TAB_MAIN)) self.settingsButtonBox.accepted.connect(self.saveSettings) self.settingsButtonBox.rejected.connect(lambda: self.tabWidget.setCurrentIndex(TAB_MAIN)) self.settingsPushButton.clicked.connect( lambda: self.tabWidget.setCurrentIndex(TAB_SETTINGS)) self.redmineSyncPushButton.clicked.connect(lambda: self.doRedmineSync(check=False)) self.addEventPushButton.clicked.connect(self.addEvent) self.setupDbus() def setupStatusIcon(self): icon = QIcon() icon.addPixmap(QPixmap(":/clock.svg"), QIcon.Normal, QIcon.Off) self.statusIcon = QSystemTrayIcon(self) self.statusIcon.setIcon(icon) self.statusIcon.activated.connect(lambda: self.hide() if self.isVisible() else self.show()) self.statusIcon.setToolTip("Mark's Time Tracker") self.statusIcon.show() def setupDbus(self): dbus_loop = DBusQtMainLoop(set_as_default=True) self.bus = dbus.SessionBus(mainloop=dbus_loop) signals = [('org.freedesktop.ScreenSaver', '/org/freedesktop/ScreenSaver', 'ActiveChanged'), ('com.canonical.Unity', '/com/canonical/Unity/Session', 'Locked')] for org, path, event in signals: screensaver = self.bus.get_object(org, path) screensaver.connect_to_signal(event, self.checkLockScreen) def updateTasksComboBox(self): self.tasksComboBox.clear() self.editTaskListComboBox.clear() self.tasksComboBox.addItem('') self.tasksComboBox.lineEdit().setPlaceholderText("What are you going to do?") for task in self.db.query(Task).all(): if task.active: self.tasksComboBox.addItem(task.description) self.editTaskListComboBox.addItem(task.description) def updateTimeSpent(self): if self.runningEvent: spent_time = self.runningEvent.spent_time m, s = divmod(spent_time, 60) h, m = divmod(m, 60) self.timeLabel.setText("{h:02d}:{m:02d}:{s:02d}".format(h=h, m=m, s=s)) period = self.eventsPeriodComboBox.currentText() start, end = self.getStartEndForPeriod(period) total = Event.get_spent_time_period(self.db, start, end) self.totalTimeLabel.setText("{}h".format(total)) def getStartEndForPeriod(self, period): if period == "Today": start = datetime.datetime.now().replace(hour=0, minute=0) end = start + relativedelta.relativedelta(days=1) elif period == "Yesterday": end = datetime.datetime.now().replace(hour=0, minute=0) start = end - relativedelta.relativedelta(days=1) elif period == "This week": today = datetime.datetime.now().replace(hour=0, minute=0) start = today - relativedelta.relativedelta(days=today.weekday()) end = today + relativedelta.relativedelta(days=6 - today.weekday()) else: raise Exception("Don't know this period {}".format(period)) return start, end def updateTaskList(self): while self.timeEntriesLayout.count() > 0: self.timeEntriesLayout.takeAt(0).widget().deleteLater() period = self.eventsPeriodComboBox.currentText() start, end = self.getStartEndForPeriod(period) events = self.db.query(Event).filter(Event.start.between(start, end))\ .order_by(Event.start.desc()) for event in events: if not event.end: continue widget = EventWidget(event.id, event.task.description, event.spent_time, parent=self) widget.clicked.connect(self.eventClicked) widget.show() self.timeEntriesLayout.addWidget(widget) def updateEditStartEndTime(self): hours = self.editDurationSpinBox.value() startTime = self.editStartDateTimeEdit.dateTime().toPyDateTime() newEndTime = startTime + relativedelta.relativedelta(hours=hours) self.editEndDateTimeEdit.setDateTime(newEndTime) def updateDurationSpinBox(self): seconds = float((self.editEndDateTimeEdit.dateTime().toPyDateTime() - self.editStartDateTimeEdit.dateTime().toPyDateTime()).seconds) hours = seconds / 3600 self.editDurationSpinBox.setValue(hours) def updateDurationSpinBoxEndTime(self): self.updateDurationSpinBox() self.updateEditStartEndTime() def checkForRunningTask(self): self.runningEvent = self.db.query(Event).filter(Event.end == None).first() if self.runningEvent: self.tasksComboBox.setCurrentIndex( [self.tasksComboBox.itemText(x) for x in range(self.tasksComboBox.count())] .index(self.runningEvent.task.description)) self.startButton.setText("Stop") self.tasksComboBox.setEnabled(False) def toggleEventButton(self): if self.runningEvent: self.stopEvent() else: self.startEvent() def eventsPeriodChanged(self): self.updateTaskList() def eventClicked(self, event_id): event = self.db.query(Event).get(event_id) self.editTaskListComboBox.setCurrentIndex( [self.editTaskListComboBox.itemText(x) for x in range(self.editTaskListComboBox.count())] .index(event.task.description)) self.editDurationSpinBox.setValue(float(event.spent_time) / 3600) self.editStartDateTimeEdit.setDateTime(event.start_date) self.editEndDateTimeEdit.setDateTime(event.end_date) self.tabWidget.setCurrentIndex(TAB_EDIT_EVENT) self.editingEvent = event def startEvent(self, event=None): if not event: event = self.tasksComboBox.currentText() self.tasksComboBox.setEnabled(False) self.startButton.setText("Stop") if not event: return if re.match(r'\d+ - .+', event): tracker_id, name = re.findall(r'(\d+) - (.+)', event)[0] else: tracker_id = None name = event # Update DB task = Task.get_or_create(self.db, task_id=tracker_id, name=name, parent=None) if self.runningEvent: self.runningEvent.end = datetime.datetime.now() self.runningEvent = Event(task_id=task.task_id, comment="", start=datetime.datetime.now()) self.db.add(self.runningEvent) self.db.commit() self.tasksComboBox.lineEdit().setText(self.runningEvent.task.description) self.checkForRunningTask() def addEvent(self): self.editDurationSpinBox.setValue(1) self.editStartDateTimeEdit.setDateTime(datetime.datetime.now()) self.editEndDateTimeEdit.setDateTime(datetime.datetime.now() + relativedelta.relativedelta(hours=1)) self.tabWidget.setCurrentIndex(TAB_EDIT_EVENT) self.editingEvent = Event() self.db.add(self.editingEvent) def stopEvent(self): self.tasksComboBox.setEnabled(True) self.runningEvent.end = datetime.datetime.now() self.db.commit() self.runningEvent = None self.updateTaskList() self.startButton.setText("Start") self.timeLabel.setText("00:00:00") self.updateTasksComboBox() def saveEvent(self): self.editingEvent.task_id = self.editTaskListComboBox.currentText().split(' - ')[0] self.editingEvent.start = self.editStartDateTimeEdit.dateTime().toPyDateTime() self.editingEvent.end = self.editEndDateTimeEdit.dateTime().toPyDateTime() self.db.commit() self.tabWidget.setCurrentIndex(TAB_MAIN) self.updateTaskList() def saveSettings(self): self.config = {'enable_detect_idle_time': self.detectIdleTimecheckBox.checkState(), 'detect_idle_time': self.detectIdleTimeSpinBox.value(), 'enable_remind': self.remindCheckBox.checkState(), 'remind_time': self.remindSpinBox.value(), 'stop_on_lock_screen': self.stopLockScreencheckBox.checkState(), 'enabled_redmine_sync': self.syncRedmineCheckBox.checkState(), 'redmine_sync_time': self.redmineSyncTimeSpinBox.value(), 'redmine_apikey': self.redmineApikeyLineEdit.text(), 'redmine_url': self.redmineUrlLineEdit.text(), 'redmine_user': self.redmineUserLineEdit.text()} self.writeConfig() def readConfig(self): config_file_path = os.path.join(self.config_path, 'config.json') if os.path.exists(config_file_path): with open(config_file_path, 'r') as f: self.config = json.loads(f.read()) else: self.config = {} self.detectIdleTimecheckBox.setCheckState(self.config.get('enable_detect_idle_time', True)) self.detectIdleTimeSpinBox.setValue(self.config.get('detect_idle_time')) self.remindCheckBox.setCheckState(self.config.get('enable_remind', True)) self.remindSpinBox.setValue(self.config.get('remind_time')) self.stopLockScreencheckBox.setCheckState(self.config.get('stop_on_lock_screen', True)) self.syncRedmineCheckBox.setCheckState(self.config.get('enabled_redmine_sync')) self.redmineSyncTimeSpinBox.setValue(self.config.get('redmine_sync_time')) self.redmineApikeyLineEdit.setText(self.config.get('redmine_apikey')) self.redmineUrlLineEdit.setText(self.config.get('redmine_url')) self.redmineUserLineEdit.setText(self.config.get('redmine_user')) def writeConfig(self): with open(os.path.join(self.config_path, 'config.json'), 'w') as f: f.write(json.dumps(self.config)) self.tabWidget.setCurrentIndex(TAB_MAIN) self.checkIdleTime() self.checkRemind() self.checkRedmineSync() def checkIdleTime(self): self.idleTimeTimer.stop() if self.config.get("enable_detect_idle_time", True): self.idleTimeTimer.start(self.config.get("detect_idle_time", 5) * 60000) def detectIdleTime(self): # do something self.checkIdleTime() def checkRemind(self): self.remindTimer.stop() if self.config.get("enable_remind", True): self.remindTimer.start(self.config.get("remind_time", 5) * 60000) def remindTracking(self): # do something self.checkRemind() def checkRedmineSync(self): self.redmineSyncTimer.stop() if self.config.get("enabled_redmine_sync"): self.redmineSyncTimer.start(self.config.get("redmine_sync_time", 5) * 60000) self.redmineSyncPushButton.setVisible(self.config.get("enabled_redmine_sync", False)) def doRedmineSync(self, check=True): logging.info("Doing redmine sync") thread = RedmineSyncThread(self.config, 'sqlite:///' + os.path.join(self.config_path, 'markstimetracker.db')) def updateTaskWidgets(): self.updateTaskList() self.updateTasksComboBox() self.checkForRunningTask() thread.finished.connect(updateTaskWidgets) thread.start() self.checkRedmineSync() def checkLockScreen(self, is_locked=True): if is_locked and self.config.get("stop_on_lock_screen"): self.stopEvent()
class Window(QDialog): def __init__(self): super(Window, self).__init__() self.runButton = QPushButton("Run") self.runButton.clicked.connect(self.confirm_btn) self.runButton.setEnabled(False) self.saveButton = QPushButton("Save Config") self.saveButton.clicked.connect(self.saveConfig) self.createFormGroupBox() self.getConfig() self.createSysTrayEntry() self.tray.activated.connect(self.iconActivated) self.setWindowTitle("JIRA-Outlook Adapter") self.tray.show() self.showIconCheckBox.toggled.connect(self.tray.setVisible) self.showPwCheckBox.stateChanged.connect( lambda: self.pwToggle(self.showPwCheckBox)) mainLayout = QVBoxLayout() mainLayout.addWidget(self.formGroupBox) mainLayout.addWidget(self.runButton) mainLayout.addStretch() mainLayout.addWidget(self.saveButton) self.setLayout(mainLayout) def formCheck(self): if len(self.jiraID.text()) > 0 and len(self.jiraUsername.text( )) > 0 and len(self.jiraPassword.text()) > 0 and len( self.jiraPassword.text()) > 0 and len( self.boardName.text()) > 0 and len( self.boardID.text()) and len(self.jiraLink.text()) > 0: self.runButton.setEnabled(True) else: self.runButton.setEnabled(False) def getConfig(self): configFile = Path('adapter_config.ini') if configFile.exists(): config = SafeConfigParser() config.read('adapter_config.ini') try: self.jiraID.setText(config.get('jiraout', 'jiraid')) self.jiraUsername.setText(config.get('jiraout', 'jirausername')) self.jiraPassword.setText(config.get('jiraout', 'jirapassword')) self.boardName.setText(config.get('jiraout', 'boardname')) self.boardID.setText(config.get('jiraout', 'boardid')) self.jiraLink.setText(config.get('jiraout', 'jiralink')) except NoSectionError: print("Section Error in the config file") except NoOptionError: print("Option Error in the config file") def pwToggle(self, showPwCheckBox): if showPwCheckBox.isChecked() == True: self.jiraPassword.setEchoMode(QLineEdit.Normal) else: self.jiraPassword.setEchoMode(QLineEdit.Password) @pyqtSlot() def saveConfig(self): if len(self.jiraID.text()) == 0 or len( self.jiraUsername.text()) == 0 or len( self.jiraPassword.text()) == 0 or len( self.jiraPassword.text()) == 0 or len( self.boardName.text()) == 0 or len( self.boardID.text()) == 0 or len( self.jiraLink.text()) == 0: error_dialog = QErrorMessage(self) error_dialog.showMessage('Please enter all the textfields.') else: configFile = open("adapter_config.ini", "w") configFile.truncate() configFile.close() config = SafeConfigParser() config.read('adapter_config.ini') config.add_section('jiraout') config.set('jiraout', 'jiraid', self.jiraID.text()) config.set('jiraout', 'jirausername', self.jiraUsername.text()) config.set('jiraout', 'jirapassword', self.jiraPassword.text()) config.set('jiraout', 'boardname', self.boardName.text()) config.set('jiraout', 'boardid', self.boardID.text()) config.set('jiraout', 'jiralink', self.jiraLink.text()) with open('adapter_config.ini', 'w') as f: config.write(f) def setVisible(self, visible): self.minimizeAction.setEnabled(visible) self.maximizeAction.setEnabled(not self.isMaximized()) self.restoreAction.setEnabled(self.isMaximized() or not visible) super(Window, self).setVisible(visible) def iconActivated(self, reason): if reason in (QSystemTrayIcon.Trigger, QSystemTrayIcon.DoubleClick): self.show() def createSysTrayEntry(self): # Create the menu self.minimizeAction = QAction("Minimize", self, triggered=self.hide) self.maximizeAction = QAction("Maximize", self, triggered=self.showMaximized) self.restoreAction = QAction("Restore", self, triggered=self.showNormal) self.quitAction = QAction("Quit", self, triggered=QApplication.instance().quit) self.menu = QMenu() self.menu.triggered[QAction].connect(self.processtrigger) self.menu.addAction(self.minimizeAction) self.menu.addAction(self.maximizeAction) self.menu.addAction(self.restoreAction) self.menu.addSeparator() self.menu.addAction(self.quitAction) # Create the tray self.tray = QSystemTrayIcon(self) self.tray.setContextMenu(self.menu) self.icon = QIcon(':/images/jira.png') self.tray.setIcon(self.icon) self.setWindowIcon(self.icon) self.tray.setVisible(True) def createFormGroupBox(self): self.formGroupBox = QGroupBox("JIRA Details") layout = QFormLayout() self.jiraID = QLineEdit() self.jiraUsername = QLineEdit() self.jiraPassword = QLineEdit() self.boardName = QLineEdit() self.boardID = QLineEdit() self.jiraLink = QLineEdit() self.jiraID.textChanged.connect(self.formCheck) self.jiraUsername.textChanged.connect(self.formCheck) self.jiraPassword.textChanged.connect(self.formCheck) self.boardName.textChanged.connect(self.formCheck) self.boardID.textChanged.connect(self.formCheck) self.jiraLink.textChanged.connect(self.formCheck) hbox1 = QHBoxLayout() self.oneTime = QRadioButton("One-time") self.scheduled = QRadioButton("Scheduled") self.scheduled.setChecked(True) hbox1.addWidget(self.oneTime) hbox1.addStretch() hbox1.addWidget(self.scheduled) hbox2 = QHBoxLayout() self.showPopups = QCheckBox("JIRA-Card pop-ups") self.showPopups.setChecked(True) self.showIconCheckBox = QCheckBox("Show icon") self.showIconCheckBox.setChecked(True) self.showPwCheckBox = QCheckBox("Show password") self.jiraPassword.setEchoMode(QLineEdit.Password) hbox2.addWidget(self.showPopups) hbox2.addStretch() hbox2.addWidget(self.showIconCheckBox) layout.addRow(QLabel("JIRA-ID(LAN-ID):"), self.jiraID) layout.addRow(QLabel("JIRA-Username:"******"JIRA-Password:"******"Board name:"), self.boardName) layout.addRow(QLabel("Board ID:"), self.boardID) layout.addRow(QLabel("Jira Link:"), self.jiraLink) layout.addRow(hbox1) layout.addRow(hbox2) self.formGroupBox.setLayout(layout) def processtrigger(self, q): print(q.text() + " is triggered") if q.text() == "Quit": self.tray.hide() QApplication.instance().quit sys.exit() @pyqtSlot() def confirm_btn(self): if self.oneTime.isChecked() == True: print("One-Time") PyJiraOut.syncTasksToJira(self.jiraID.text(), self.jiraUsername.text(), self.jiraPassword.text(), self.boardName.text(), self.boardID.text(), self.jiraLink.text()) if self.scheduled.isChecked() == True: print("Scheduled") while True: PyJiraOut.syncTasksToJira(self.jiraID.text(), self.jiraUsername.text(), self.jiraPassword.text(), self.boardName.text(), self.boardID.text(), self.jiraLink.text()) loop = QEventLoop() QTimer.singleShot(9000000, loop.quit) loop.exec_() print("Success")
class TrayIcon(QObject): def __init__(self): super().__init__() self._supported = QSystemTrayIcon.isSystemTrayAvailable() self._context_menu = None self._enabled = False self._trayicon = None self._state_icons = {} for state in ( 'disconnected', 'disabled', 'enabled', ): icon = QIcon(':/state-%s.svg' % state) if hasattr(icon, 'setIsMask'): icon.setIsMask(True) self._state_icons[state] = icon self._machine = None self._machine_state = 'disconnected' self._is_running = False self._update_state() def set_menu(self, menu): self._context_menu = menu if self._enabled: self._trayicon.setContextMenu(menu) def log(self, level, message): if self._enabled: if level <= log.INFO: icon = QSystemTrayIcon.Information timeout = 10 elif level <= log.WARNING: icon = QSystemTrayIcon.Warning timeout = 15 else: icon = QSystemTrayIcon.Critical timeout = 25 self._trayicon.showMessage(__software_name__.capitalize(), message, icon, timeout * 1000) else: if level <= log.INFO: icon = QMessageBox.Information elif level <= log.WARNING: icon = QMessageBox.Warning else: icon = QMessageBox.Critical msgbox = QMessageBox() msgbox.setText(message) msgbox.setIcon(icon) msgbox.exec_() def is_supported(self): return self._supported def enable(self): if not self._supported: return self._trayicon = QSystemTrayIcon() # On OS X, the context menu is activated with either mouse buttons, # and activation messages are still sent, so ignore those... if not sys.platform.startswith('darwin'): self._trayicon.activated.connect(self._on_activated) if self._context_menu is not None: self._trayicon.setContextMenu(self._context_menu) self._enabled = True self._update_state() self._trayicon.show() def disable(self): if not self._enabled: return self._trayicon.hide() self._trayicon = None self._enabled = False def is_enabled(self): return self._enabled def update_machine_state(self, machine, state): self._machine = machine self._machine_state = state self._update_state() def update_output(self, enabled): self._is_running = enabled self._update_state() clicked = pyqtSignal() def _update_state(self): if self._machine_state not in ('initializing', 'connected'): state = 'disconnected' else: state = 'enabled' if self._is_running else 'disabled' icon = self._state_icons[state] if not self._enabled: return machine_state = _('{machine} is {state}').format( machine=_(self._machine), state=_(self._machine_state), ) if self._is_running: output_state = _('output is enabled') else: output_state = _('output is disabled') self._trayicon.setIcon(icon) self._trayicon.setToolTip( 'Plover:\n- %s\n- %s' % (output_state, machine_state) ) def _on_activated(self, reason): if reason == QSystemTrayIcon.Trigger: self.clicked.emit()
class mainForm(QWidget, Ui_MainForm): def __init__(self): super(mainForm, self).__init__() self.setupUi(self) # 隐藏任务栏显示,窗口置顶 self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint | QtCore.Qt.WindowMinimizeButtonHint | QtCore.Qt.WindowCloseButtonHint) # 固定大小 self.setFixedSize(self.width(), self.height()) # 设置系统托盘 self.tp = QSystemTrayIcon(self) self.tp.setIcon(QIcon('./res/imgs/icon.jpg')) # 托盘图标 self.tp.setToolTip('截图工具') # 提示语 self.tp.activated.connect(self.tpActivated) # 激活时调用函数 # 操作actions self.action_always_top = QAction('始终置顶', self, triggered=self.setAlwaysTop, checkable=True) self.action_always_top.setChecked(True) self.action_quit = QAction('退出', self, triggered=self.quit) # 目录 self.tpMenu = QMenu() # 绑定actions self.tpMenu.addAction(self.action_quit) # 设置目录 self.tp.setContextMenu(self.tpMenu) self.tp.show() self.scene = None # 子窗口 self.screenCapForm = None # 禁用滚动条 self.gv_display.setVerticalScrollBarPolicy( QtCore.Qt.ScrollBarAlwaysOff) self.gv_display.setHorizontalScrollBarPolicy( QtCore.Qt.ScrollBarAlwaysOff) def tpActivated(self, reason): if (reason == QSystemTrayIcon.DoubleClick): # TODO: 双击截图操作 self.hide() time.sleep(0.2) self.screenCapForm = screenCapForm(self) self.screenCapForm.showFullScreen() def setAlwaysTop(self): self.action_always_top.setChecked( not self.action_always_top.isChecked()) def quit(self): self.tp.hide() exit(0) # 关闭时隐藏托盘 def closeEvent(self, QCloseEvent): self.tp.hide() # 设置大小 def setSize(self, width, height): self.setFixedSize(width, height) self.gv_display.setFixedSize(width, height)
class Invoice(QMainWindow): '''Runs the main window of the invoice development Calls the table to be used for parts and labor from table_widget.py ''' invoice_count = 0 total_parts_ = 0 labor_supplies_ = 0 recent_open = False start_flag = False current_job = str labor_ = 0 parts_ = 0 supplies = 0 freight_ = 0 subtotal = 0 taxed = 0 totals = 0 tax = 0 partial = 0 finance = 0 new_total = 0 open_list = [] printed_list = {} def __init__(self): '''Initialize the window and get pertinent information read in: Set the window size Set the picture to be a BEI logo Read in the standard labor rates ''' super().__init__() self.size_policy = QSizePolicy.Expanding self.font = QFont() self.font.setPointSize(12) self.showMaximized() self.setWindowIcon(QIcon('BEI_Logo.png')) # backimage=QImage('BEI_Logo.png') self.setWindowTitle('Burl Equipment Inc. Invoices Beta') self.tray = QSystemTrayIcon(self) self.tray.setIcon(QIcon('BEI_Logo.png')) self.show() self.menu_bar() self.statusbar = QStatusBar() self.setStatusBar(self.statusbar) #this is the first time start up section, should only run the very #first time self.base_directory = str( Path(os.path.join(os.environ['USERPROFILE'], 'BEI_Invoices'))) base_entries = os.listdir(os.environ['USERPROFILE']) if 'BEI_Invoices' not in base_entries: initi.First_Run(self.base_directory) def menu_bar(self): '''Create the menu bar for the main window will include Name: Shortcut: Function called: File: New CTRL+N new_invoice_begin Open CTRL+O existing_invoice_open Save CTRL+S print_invoice Quit ALT save_invoice Print CTRL+P +F4 exit_system Edit: Change Labor Rates labor_rates View: View Totals view_totals View Labor Breakdown labor_breakdown Help: View Current Cheat Sheet cheat_sheet Add New Task to Cheat Sheet add_cheat_task ''' self.menuFile = self.menuBar().addMenu("&File") self.actionNew = QAction('&New', self) self.actionNew.setShortcut('Ctrl+N') self.actionNew.triggered.connect(self.new_invoice_begin) self.actionOpen = QAction("&Open", self) self.actionOpen.setShortcut('Ctrl+O') self.actionOpen.triggered.connect(self.existing_invoice_open) self.actionSave = QAction('&Save', self) self.actionSave.setShortcut('Ctrl+S') self.actionSave.setDisabled(True) self.actionSave.triggered.connect(self.save_invoice) # self.actionImport=QAction('&Import Old Job',self) # self.actionImport.triggered.connect(self.old_job) # self.actionImport.setShortcut('Ctrl+I') self.actionPrint = QAction('&Print', self) self.actionPrint.setShortcut('Ctrl+P') self.actionPrint.setDisabled(True) self.actionPrint.triggered.connect(self.print_invoice) self.printMenu = QMenu('Print Envelopes', self) self.actionEnvelope = QAction('&Print All Billed Customer Envelopes', self) self.actionEnvelope.triggered.connect(self.envelop_write) self.actionEnvelope.setShortcut('Ctrl+E') self.actionEnvelope.setDisabled(True) self.actionEnvelope1 = QAction( '&Print Single Billed Customer Envelope', self) self.actionEnvelope1.triggered.connect(self.envelop_write1) self.actionEnvelope1.setShortcut('Ctrl+R') self.actionBilledEnvelopes = QAction('&Print Check Envelope', self) self.actionBilledEnvelopes.triggered.connect(self.billing_envelopes) self.actionBilledEnvelopes.setShortcut('Ctrl+C') self.printMenu.addActions([ self.actionEnvelope, self.actionEnvelope1, self.actionBilledEnvelopes ]) self.actionQuit = QAction('&Exit', self) self.actionQuit.triggered.connect(self.closing) self.actionQuit.setShortcut('Alt+F4') self.menuFile.addActions([ self.actionNew, self.actionOpen, self.actionSave, self.actionPrint ]) self.menuFile.addMenu(self.printMenu) self.menuFile.addAction(self.actionQuit) self.menuEdit = self.menuBar().addMenu('&Edit') self.menuEdit_Change_In = QMenu('Change Basic Invoice Information', self) self.menuEdit_Change_Sy = QMenu('Change Operating Data', self) self.actionLaborRates = QAction('&Change Standard Labor Rates', self) self.actionLaborRates.triggered.connect(self.labor_rates) self.actionAddTechnician = QAction('&Add Technician', self) self.actionAddTechnician.triggered.connect(self.add_tech) self.actionChangeDate = QAction('&Change Invoice Date', self) self.actionChangeDate.triggered.connect(self.date_change) self.actionChangeCustomerAddress = QAction('&Change Customer Address', self) self.actionChangeCustomerAddress.triggered.connect(self.change_address) self.actionBasicInfo = QAction('&Change Basic Information', self) self.actionBasicInfo.triggered.connect(self.change_basic_info) self.actionBasicInfo.setDisabled(True) self.menuEdit_Change_In.addActions([self.actionBasicInfo]) self.menuEdit_Change_Sy.addActions([ self.actionLaborRates, self.actionAddTechnician, self.actionChangeDate, self.actionChangeCustomerAddress ]) self.menuEdit.addMenu(self.menuEdit_Change_In) self.menuEdit.addMenu(self.menuEdit_Change_Sy) self.menuView = self.menuBar().addMenu('&View') self.actionViewLaborBreakdown = QAction('&View Labor Breakdown', self) self.actionViewLaborBreakdown.setDisabled(True) self.actionViewLaborBreakdown.triggered.connect(self.breakdown) self.actionViewAllWindows = QAction('&View All Windows', self) self.actionViewAllWindows.setDisabled(True) self.actionViewAllWindows.triggered.connect(self.view_windows) self.actionViewCutomer = QAction('&View Customer Invoice', self) self.actionViewCutomer.triggered.connect(self.view_customer) self.actionViewCutomer = QAction('&View Customer Invoice', self) self.actionViewCutomer.triggered.connect(self.view_customer) self.actionViewCutomer.setEnabled(False) self.actionViewCompany = QAction('&View Company Invoice', self) self.actionViewCompany.triggered.connect(self.view_company) self.actionViewCompany.setEnabled(False) self.menuView.addActions([ self.actionViewLaborBreakdown, self.actionViewAllWindows, self.actionViewCutomer, self.actionViewCompany ]) self.actionJobNumbers = QAction('&More Job Numbers', self) self.actionJobNumbers.triggered.connect(self.new_job_nums) self.menuJobNumbers = self.menuBar().addMenu('Job Numbers') self.menuJobNumbers.addAction(self.actionJobNumbers) self.menuPayment = self.menuBar().addMenu('&Finance/Payments') self.actionPartialPayment = QAction('&Partial Payment', self) self.actionPartialPayment.triggered.connect(self.partial_payment) self.actionPartialPayment.setDisabled(True) self.actionFinanceCharges = QAction('&Add Finance Charges', self) self.actionFinanceCharges.triggered.connect(self.finance_charges) self.actionFinanceCharges.setDisabled(True) self.menuPayment.addActions( [self.actionPartialPayment, self.actionFinanceCharges]) self.menuHelp = self.menuBar().addMenu('&Help') self.actionViewCheatSheet = QAction('&View Cheat Sheet', self) self.actionViewCheatSheet.triggered.connect(self.cheat_sheet) self.actionNewCheat = QAction('&Add New Item to Cheat Sheet', self) self.actionNewCheat.triggered.connect(self.add_cheat_task) self.actionUpdate = QAction('&Update Application') self.actionUpdate.triggered.connect(self.updater) self.menuHelp.addActions([ self.actionViewCheatSheet, self.actionNewCheat, self.actionUpdate ]) def new_invoice_begin(self): '''Entering basic information: Job Number: Machine: Customer Name: ''' try: self.docked.close() self.docked2.close() self.totals_table.close() self.save_invoice() self.new_window = New_Invoice(12, self.base_directory) # self.new_window.basic_information() self.new_window.start.clicked.connect(self.job_num_insertion) self.new_window.customer_address_line_2.returnPressed.connect( self.new_window.information_) self.new_window.customer_address_line_2.returnPressed.connect( self.job_num_insertion) except: self.new_window = New_Invoice(12, self.base_directory) # self.new_window.basic_information() self.new_window.start.clicked.connect(self.job_num_insertion) self.new_window.customer_address_line_2.returnPressed.connect( self.new_window.information_) self.new_window.customer_address_line_2.returnPressed.connect( self.job_num_insertion) def job_num_insertion(self): '''Call the table with the job number given in the new invoice ''' self.reset_data() if not self.recent_open: self.recent_invoices() self.tax = self.new_window.tax self.customer = self.new_window.customer.replace('#', '') self.machine_text = self.new_window.machine_ self.current_job = self.new_window.job_num if self.current_job not in self.open_list: self.open_list.append(self.current_job) self.recently_opened_invoice.appendRow( QStandardItem(str(self.current_job))) self.invoice_count += 1 #make the folder for this invoice to be saved in self.job_dire = os.path.join( os.path.join(self.base_directory, 'Saved_Invoices'), self.current_job) try: os.mkdir(self.job_dire) #save the basic information location = os.path.join( os.path.join(self.base_directory, 'Saved_Invoices'), self.current_job) self.table(self.new_window.job_num) basic = os.path.join(location, 'Basic_Info.csv') f = open(basic, 'w') f.write(str(self.current_job) + '\n') f.write(self.new_window.customer + '\n') f.write(self.new_window.machine_ + '\n') f.write('{},{}\n'.format(str(self.new_window.tax), self.new_window.tax_code)) f.write(self.new_window.line1 + '\n') f.write(self.new_window.line2 + '\n') f.close() except: buttonReply = QMessageBox.question( self, 'Confirm New Machine', 'Job Number {} already exist.\nDo you want to overwrite it?'. format(self.new_window.job_num), QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if buttonReply == QMessageBox.Yes: self.table(self.new_window.job_num) location = os.path.join( os.path.join(self.base_directory, 'Saved_Invoices'), self.current_job) basic = os.path.join(location, 'Basic_Info.csv') f = open(basic, 'w') f.write(str(self.current_job) + '\n') f.write(self.new_window.customer + '\n') f.write(self.new_window.machine_ + '\n') f.write('{},{}\n'.format(str(self.new_window.tax), self.new_window.tax_code)) f.write(self.new_window.line1 + '\n') f.write(self.new_window.line2 + '\n') f.close() elif buttonReply == QMessageBox.No: self.current_job = str(self.current_job) self.read_in_data() def existing_invoice_open(self): '''Open an existing invoice ''' try: self.docked.close() self.docked2.close() self.save_invoice() except: True if not self.recent_open: self.recent_invoices() #get the saved invoices loc = os.path.join(self.base_directory, 'Saved_Invoices') saved_jobs = os.listdir(loc) self.existing = QWidget() self.existing.setWindowIcon(QIcon('BEI_Logo.png')) self.existing.setWindowTitle('Open Existing Invoice') self.open = QPushButton('Open', self) self.open.setFont(self.font) self.open.setSizePolicy(self.size_policy, self.size_policy) self.open.clicked.connect(self.reader) self.job_to_open = QLineEdit(self) self.job_to_open.setFont(self.font) self.job_to_open.setSizePolicy(self.size_policy, self.size_policy) self.job_to_open.setCompleter(QCompleter(saved_jobs)) # self.job_to_open.returnPressed.connect(self.reader) layout = QVBoxLayout() layout.addWidget(self.job_to_open) layout.addWidget(self.open) self.existing.setLayout(layout) self.existing.setGeometry(400, 400, 300, 100) self.existing.show() def reader(self): self.current_job = self.job_to_open.text() self.read_in_data() self.existing.close() def table(self, num): '''Setup the table for use with a new invoice ''' self.start_flag = True self.actionPrint.setEnabled(True) self.actionSave.setEnabled(True) self.actionViewLaborBreakdown.setEnabled(True) self.actionBasicInfo.setEnabled(True) self.actionViewAllWindows.setEnabled(True) self.actionViewCutomer.setEnabled(True) self.actionViewCompany.setEnabled(True) self.docked = QMdiSubWindow() self.docked.setWindowTitle('Invoice {}'.format(num)) self.num = num self.tabs = QTabWidget(self) self.parts = Parts_Tabs(num) self.tabs.addTab(self.parts, 'Parts') self.labor = Labor_Tabs(num) self.tabs.addTab(self.labor, 'Labor') self.docked.setWidget(self.tabs) self.parts.total.connect(self.calculate_totals) self.labor.labor_total.connect(self.calculate_totals) cust_display = QWidget(self) self.cust_label = QLabel('Customer: {}'.format(self.customer), self) self.cust_label.setFont(self.font) self.machine_label = QLabel('Machine: {}'.format(self.machine_text), self) self.machine_label.setFont(self.font) lay = QHBoxLayout() lay.addWidget(self.cust_label) lay.addWidget(self.machine_label) cust_display.setLayout(lay) #design and insert the totals table self.totals_table = Table(7, 2) self.totals_table.tableWidget.setItem(0, 0, QTableWidgetItem('Parts:')) self.totals_table.tableWidget.setItem(1, 0, QTableWidgetItem('Labor:')) self.totals_table.tableWidget.setItem(2, 0, QTableWidgetItem('Supplies:')) self.totals_table.tableWidget.setItem(3, 0, QTableWidgetItem('Freight:')) self.totals_table.tableWidget.setItem(4, 0, QTableWidgetItem('Subtotal:')) self.totals_table.tableWidget.setItem( 5, 0, QTableWidgetItem('Tax: {:.2f}%'.format(self.tax * 100))) self.totals_table.tableWidget.setItem(6, 0, QTableWidgetItem('Total:')) #set up the comments section self.comments = QTextEdit(self) self.comments.setFont(self.font) self.comments.setSizePolicy(self.size_policy, self.size_policy) self.comments.setText('Comments:\n') self.additional_docking = QWidget(self) layout = QVBoxLayout(self) layout.addWidget(cust_display) layout.addWidget(self.totals_table) layout.addWidget(self.comments) self.additional_docking.setLayout(layout) self.docked2 = QMdiSubWindow() self.docked2.setWidget(self.additional_docking) self.docked2.setWindowTitle('Information') self.mdi = QMdiArea() self.mdi.addSubWindow(self.docked2) self.mdi.addSubWindow(self.docked) self.mdi.tileSubWindows() self.setCentralWidget(self.mdi) # self.window_saved=self.saveState(1) def recent_invoices(self): '''Show a list of recently opened invoices ''' self.recent_open = True self.recent = QDockWidget('Recently opened invoices', self) self.recently_opened_invoice = QStandardItemModel() self.invoices_open = QListView(self) self.invoices_open.setFont(self.font) self.invoices_open.setSizePolicy(self.size_policy, self.size_policy) self.invoices_open.setModel(self.recently_opened_invoice) self.invoices_open.setEditTriggers(QAbstractItemView.NoEditTriggers) self.invoices_open.doubleClicked[QModelIndex].connect(self.recall) self.notes = QTextEdit(self) self.notes.setFont(self.font) self.notes.setSizePolicy(self.size_policy, self.size_policy) self.notes.setText('Notes:\n') self.running_info = QWidget(self) layout = QVBoxLayout(self) layout.addWidget(self.invoices_open) layout.addWidget(self.notes) self.running_info.setLayout(layout) self.recent.setWidget(self.running_info) self.addDockWidget(Qt.LeftDockWidgetArea, self.recent) def recall(self, index): item = self.recently_opened_invoice.itemFromIndex(index) job_number = item.text() self.docked.close() self.docked2.close() self.save_invoice() self.current_job = job_number self.read_in_data() def save_invoice(self, printing=False): '''Save both the parts and labor tables ''' if self.current_job == str: pass else: location = os.path.join( os.path.join(self.base_directory, 'Saved_Invoices'), self.current_job) parts_file = os.path.join(location, 'Parts.csv') #first read and write the parts information f = open(parts_file, 'w') row = [] for i in range(100): try: for j in range(8): if j == 0: if self.parts.parts_table.tableWidget.item( i, j).text() != '*': val = float( self.parts.parts_table.tableWidget.item( i, j).text()) elif self.parts.parts_table.tableWidget.item( i, j).text() == '*': val = self.parts.parts_table.tableWidget.item( i, j).text() row.append(val) else: try: val = self.parts.parts_table.tableWidget.item( i, j).text() row.append(val) except: row.append('') if '\n' in row[-1]: row[-1] = row[-1].split(sep='\n')[0] row[2] = row[2].replace(',', '.') f.write('{},{},{},{},{},{},{},{}\n'.format(*row)) row = [] except: break f.close() #save the total table total_location = os.path.join(location, 'Totals.csv') h = open(total_location, 'w') t_row = [ self.parts_, self.labor_, self.supplies, self.freight_, self.subtotal, self.taxed, self.totals ] for i in t_row: try: float(i) h.write('{:.2f}\n'.format(i)) except: h.write('0') h.close() #save the comments comments_location = os.path.join(location, 'Comments.csv') v = open(comments_location, 'w') v.write(self.comments.toPlainText()) v.close() #finally save the labor information #get the number of techs showing count = self.labor.counts for l in range(count): labor_location = os.path.join(location, 'tech{}.csv'.format(l)) o = open(labor_location, 'w') #get the data from the labor class tech_labor = self.labor.read_data_out(l) for k in range(len(tech_labor)): if '\n' in list(tech_labor[k][-1]): tech_labor[k][-1] = float(tech_labor[k][-1]) o.write('{},{},{},{},{},{},{},{}\n'.format(*tech_labor[k])) o.close() self.statusbar.showMessage('Invoice {} saved'.format(self.current_job), 5000) envelop_writer = EWriter(self.base_directory, self.current_job) envelop_writer.generate_latex() acrobat = 'Acrobat.exe' in (p.name() for p in psutil.process_iter()) reader = 'AcroRd32.exe' in (p.name() for p in psutil.process_iter()) if acrobat: lis = ['taskkill', '/F', '/IM', 'Acrobat.exe', '/T'] subprocess.call(lis) if reader: os.system('taskkill /F /IM "AcroRd32.exe" /T') if printing == False: comp_cust = Saver(self, self.base_directory, self.current_job) comp_cust.out.connect(self.failure) comp_cust.start() def failure(self, value): if value == 1: QMessageBox.information(self, 'Save Failure', 'Closing PDF and trying again', QMessageBox.Ok) # self.save_invoice() def add_tech(self): '''Adding a technician to the company: Changes to make: Add to the tabs Add standard labor rates Change stuff in the base invoice, not sure how this is going to work yet ''' text, okPressed = QInputDialog.getText(self, "Tech Name", "Tech name:", QLineEdit.Normal, "") if okPressed and text != '': regular, okPressed1 = QInputDialog.getDouble( self, "Regular Rate", "Regular Hourly Rate: $", 80, 0, 150, 2) if okPressed1: overtime, okPressed2 = QInputDialog.getDouble( self, "Overtime Rate", "Overtime Hourly Rate: $", 80, 0, 150, 2) if okPressed2: directory = str( Path( os.path.join( os.path.join(os.environ['USERPROFILE'], 'BEI_Invoices')), 'Basic_Information_Totals')) tech_data = open( str(Path(os.path.join(directory, 'Labor_Rates.csv'))), 'a') tech_data.write('{},{},{}\n'.format( text, regular, overtime)) tech_data.close() QMessageBox.information( self, 'Updated', 'Application must be restarted to apply these changes', QMessageBox.Ok) def read_in_data(self): self.actionPartialPayment.setEnabled(True) self.actionFinanceCharges.setEnabled(True) self.invoice_count += 1 if self.current_job not in self.open_list: self.open_list.append(self.current_job) self.recently_opened_invoice.appendRow( QStandardItem(self.current_job)) #open the basic information and read the tax percentage location = os.path.join( os.path.join(self.base_directory, 'Saved_Invoices'), '{}'.format(self.current_job)) e = open(os.path.join(location, 'Basic_Info.csv'), 'r') basic = e.readlines() e.close() self.customer, self.machine_text = basic[1].replace( '\n', ''), basic[2].replace('\n', '') self.tax = float(basic[3].split(sep=',')[0]) self.table(self.current_job) self.machine_label.setText('Machine: {}'.format(self.machine_text)) self.cust_label.setText('Customer: {}'.format(self.customer)) #read in the parts data from the file and hand it off the the parts_tab #class to be placed in the table location = os.path.join( os.path.join(self.base_directory, 'Saved_Invoices'), '{}'.format(self.current_job)) parts_location = os.path.join(location, 'Parts.csv') p_d = open(parts_location, 'r') p_data = p_d.readlines() p_d.close() parts_data = [p_data[i].split(sep=',') for i in range(len(p_data))] self.parts.read_in_data(parts_data) #read in the totals information totals_information = os.path.join(location, 'Totals.csv') t_d = open(totals_information, 'r') t_data = t_d.readlines() t_d.close() totals = [float(i) for i in t_data] #reset all the s self.reset_data() #try to read in payments and finance cahrges if they exist try: par_d = open(os.path.join(location, 'Payments.csv'), 'r') self.partial = float(par_d.readlines()[0]) par_d.close() except: self.partial = 0 try: fin_d = open(os.path.join(location, 'Finance.csv'), 'r') self.finance = float(fin_d.readlines()[0]) fin_d.close() except: self.finance = 0 self.parts_, self.labor_, self.supplies, self.freight_, self.subtotal, self.taxed, self.totals = totals #set the values into the totals table # self.totals=self.totals+self.finance-self.partial for i in range(len(totals)): self.totals_table.tableWidget.setItem( i, 1, QTableWidgetItem('${:,.2f}'.format(totals[i]))) #read and put the comments in place comments_location = os.path.join(location, 'Comments.csv') c_data = open(comments_location, 'r') com_data = c_data.readlines() c_data.close() combi = '' for i in com_data: combi += str(i) self.comments.setText(combi) #read in the labor data #determine the number of tech there are tech_num = 0 dir_ = os.listdir(location) for i in range(len(dir_)): if 'tech' in dir_[i]: tech_num += 1 for l in range(tech_num): loca = os.path.join(location, 'tech{}.csv'.format(l)) l_data = open(loca, 'r') lab_data = l_data.readlines() l_data.close() labor_data = [o.split(sep=',') for o in lab_data] self.labor.read_in_data(l, labor_data) def reset_data(self): self.parts_, self.labor_, self.supplies, self.freight_, self.subtotal, self.taxed, self.totals = [ 0, 0, 0, 0, 0, 0, 0 ] self.partial, self.finance = 0, 0 def labor_rates(self): '''Change the labor rates ''' self.changes = Labor_Rates(self.base_directory, self.font, self.size_policy) self.changes.show() def update_parts_total(self): self.parts_calculator() # self.parts_=round(self.parts.parts_total,2) # self.freight_=round(self.parts.freight_total,2) self.totals_table.tableWidget.setItem( 0, 1, QTableWidgetItem('${:,.2f}'.format(self.parts_))) self.totals_table.tableWidget.setItem( 3, 1, QTableWidgetItem('${:,.2f}'.format(self.freight_))) self.total_parts_ = self.parts_ + self.freight_ def parts_calculator(self): self.parts_ = 0 self.freight_ = 0 # self.parts.parts_sumation() # self.parts_=self.parts.parts_total # self.freight_=self.parts.freight_total+1 for i in range(100): try: self.parts_ += float( self.parts.parts_table.tableWidget.item(i, 5).text()) try: self.freight_ += float( self.parts.parts_table.tableWidget.item(i, 6).text()) except: self.freight_ += 0 except: True def update_labor(self): total_labor = 0 for i in range(self.labor.counts): total_labor += self.labor.find_tech_total(i) self.totals_table.tableWidget.setItem( 1, 1, QTableWidgetItem('${:,.2f}'.format(round(total_labor, 2)))) self.totals_table.tableWidget.setItem( 2, 1, QTableWidgetItem('${:,.2f}'.format(round(total_labor * 0.05, 2)))) self.supplies = round(total_labor * 0.05, 2) self.labor_ = total_labor self.labor_supplies_ = round(self.labor_, 2) + self.supplies def calculate_totals(self): '''Calculate the totals for the totals table and display it ''' self.update_labor() # self.parts.parts_sumation() self.update_parts_total() self.subtotal = self.labor_supplies_ + self.total_parts_ self.totals_table.tableWidget.setItem( 4, 1, QTableWidgetItem('${:,.2f}'.format(self.subtotal))) self.taxed = self.tax * self.subtotal self.totals_table.tableWidget.setItem( 5, 1, QTableWidgetItem('${:,.2f}'.format(self.taxed))) self.totals = self.subtotal + self.taxed + self.finance - self.partial self.totals_table.tableWidget.setItem( 6, 1, QTableWidgetItem('${:,.2f}'.format(self.totals))) def print_invoice(self): ''' Print the customer and company invoices ''' #make sure the invoice is saved self.save_invoice(printing=True) self.printed_list[self.current_job] = [ self.customer, self.machine_text ] try: pdf2.PDF_Builder(self.current_job, self.base_directory, 'Company').print_tex() pdf2.PDF_Builder(self.current_job, self.base_directory, 'Customer').print_tex() except: QMessageBox.information(self, 'Print Failure', 'Close file and try again', QMessageBox.Ok) #first check and see if the Envelopes directory has this #months print list envelope_date = EP(self.base_directory, self.customer, self.current_job) self.envelope_date = envelope_date.dater() self.actionEnvelope.setEnabled(True) self.actionEnvelope1.setEnabled(True) def closing(self): # self.save_invoice() self.close() def breakdown(self): '''view the labor break down ''' #open the labor rates to get the names loc = os.path.join(self.base_directory, 'Basic_Information_Totals') file_loc = os.path.join(loc, 'Labor_Rates.csv') f = open(file_loc, 'r') f_data = f.readlines() f.close() names = [] for i in range(len(f_data)): names.append(f_data[i].split(sep=',')[0]) #get the totals from the labor page individauls = self.labor.find_tech_individual() #combine the two lists into a single string combined = '' for i in range(len(individauls)): combined += '{}: ${:,.2f}\n'.format(names[i], individauls[i]) QMessageBox.information(self, 'Labor Breakdown', combined, QMessageBox.Ok) def date_change(self): '''change the data on the invoices for the month ''' self.date_changed = QWidget() self.date_changed.setWindowTitle('Change Invoice Date') self.date_changed.setWindowIcon(QIcon('BEI_Logo.png')) self.line = QLineEdit() self.line.setFont(self.font) self.line.setSizePolicy(self.size_policy, self.size_policy) self.save_date = QPushButton('Save Date') self.save_date.setFont(self.font) self.save_date.setSizePolicy(self.size_policy, self.size_policy) self.save_date.clicked.connect(self.saved_date) layout = QVBoxLayout() layout.addWidget(self.line) layout.addWidget(self.save_date) self.date_changed.setLayout(layout) d_location = os.path.join(self.base_directory, 'Basic_Information_Totals') self.date_location = os.path.join(d_location, 'Invoice_Date.txt') y = open(self.date_location, 'r') date = y.readlines() y.close() self.line.setText(date[0]) self.date_changed.show() def saved_date(self): ''' Save the new date ''' y = open(self.date_location, 'w') y.write(self.line.text()) y.close() self.date_changed.close() def new_job_nums(self): '''Run the class to create more job numbers ''' self.n_jobs = Job_Numbers() def cheat_sheet(self): '''Open the cheat sheet for viewing ''' self.chea = Read_Cheat_Sheet(self.font, self.size_policy, self.base_directory) def add_cheat_task(self): self.cheat = Write_Cheat_Sheet(self.font, self.size_policy, self.base_directory) def partial_payment(self): self.a = Partial_Payments(self.font, self.size_policy) self.a.add.clicked.connect(self.proce) def proce(self): try: self.a.process() self.this_payment = self.a.amount try: location = os.path.join( os.path.join(self.base_directory, 'Saved_Invoices'), '{}'.format(self.current_job)) payments = os.path.join(location, 'Payments.csv') f = open(payments, 'r') value = float(f.readlines()[0]) f.close() self.this_payment += value except: self.this_payment = self.this_payment self.comments.append( '''Partial payment of ${:,.2f} on {}, leaves a remaining balance of ${:,.2f}''' .format(self.a.amount, self.a.date, self.totals - self.a.amount)) f = open(payments, 'w') f.write(str(self.this_payment)) self.partial = self.this_payment self.calculate_totals() f.close() except: pass def finance_charges(self): self.charg = Finance_Charges(self.font, self.size_policy) self.charg.add.clicked.connect(self.fin_process) def fin_process(self): self.charg.process() self.finance += self.charg.amount self.comments.append( 'Finance Charge of ${:,.2f} applied on {}, kindly remit payment immediately.' .format(self.charg.amount, self.charg.date)) location = os.path.join( os.path.join(self.base_directory, 'Saved_Invoices'), '{}'.format(self.current_job)) fin_loc = os.path.join(location, 'Finance.csv') f = open(fin_loc, 'w') f.write(str(self.finance)) f.close() self.calculate_totals() def change_basic_info(self): #first read in the current status of the basic info location = os.path.join( os.path.join(self.base_directory, 'Saved_Invoices'), '{}'.format(self.current_job)) e = open(os.path.join(location, 'Basic_Info.csv'), 'r') basic = e.readlines() e.close() self.update_info = QWidget() self.update_info.setWindowTitle('Update Basic Information') self.update_info.setWindowIcon(QIcon('BEI_Logo.png')) mach = QLabel('Machine', self) mach.setFont(self.font) mach.setSizePolicy(self.size_policy, self.size_policy) tax = QLabel('Tax [%]', self) tax.setFont(self.font) tax.setSizePolicy(self.size_policy, self.size_policy) self.machine = QLineEdit(self) self.machine.setFont(self.font) self.machine.setSizePolicy(self.size_policy, self.size_policy) self.machine.setText(basic[2]) self.tax_value = QLineEdit(self) self.tax_value.setFont(self.font) self.tax_value.setSizePolicy(self.size_policy, self.size_policy) self.tax_value.setText( str(round(float(basic[3].split(sep=',')[0]) * 100, 2))) update = QPushButton('Update', self) update.setFont(self.font) update.setSizePolicy(self.size_policy, self.size_policy) update.clicked.connect(self.update_basic_values) layout = QGridLayout() layout.addWidget(mach, 0, 0) layout.addWidget(self.machine, 0, 1) layout.addWidget(tax, 1, 0) layout.addWidget(self.tax_value, 1, 1) layout.addWidget(update, 2, 0) self.update_info.setLayout(layout) self.update_info.show() def update_basic_values(self): self.update_info.close() location = os.path.join( os.path.join(self.base_directory, 'Saved_Invoices'), '{}'.format(self.current_job)) e = open(os.path.join(location, 'Basic_Info.csv'), 'r') basic = e.readlines() e.close() flag = False #change the information in basic[2] and basic[3] to match the new values if basic[2].split(sep='\n')[0] != self.machine.text(): old = basic[2].split(sep='\n')[0].replace(' ', '_') cust = basic[1].split(sep='\n')[0].replace(' ', '_') file_name = basic[0].split(sep='\n')[0] + '.pdf' flag = True basic[2] = self.machine.text() self.machine_text = basic[2] self.machine_label.setText('Machine: {}'.format(self.machine_text)) if float(basic[3].split( sep=',')[0]) != float(self.tax_value.text()) / 100: try: tax_code, ok = QInputDialog.getText( self, 'Update Tax Code', 'Tax Code: ', QLineEdit.Normal, basic[3].split(sep=',')[1].split(sep='\n')[0]) except: tax_code, ok = QInputDialog.getText(self, 'Update Tax Code', 'Tax Code: ', QLineEdit.Normal, "") basic[3] = '{},{}'.format( float(self.tax_value.text()) / 100, tax_code) self.tax = float(self.tax_value.text()) / 100 f = open(os.path.join(location, 'Basic_Info.csv'), 'w') for i in range(len(basic)): if '\n' not in basic[i]: f.write('{}\n'.format(basic[i])) else: f.write('{}'.format(basic[i])) f.close() #change the percent shown in the total value self.totals_table.tableWidget.setItem( 5, 0, QTableWidgetItem('Tax: {:.2f}%'.format(self.tax * 100))) #next update the totals self.calculate_totals() self.save_invoice() time.sleep(3) if flag: #depending on if the machine has been updated, get rid of the previous #version of the file and start change it to the new location location = os.path.join(os.path.expanduser('~/Desktop'), 'BEI_Invoices') old_location_cust_ = os.path.join( os.path.join(location, 'Customer'), cust) old_location_cust = os.path.join(old_location_cust_, old) old_final_cust = os.path.join(old_location_cust, file_name) old_location_comp_ = os.path.join( os.path.join(location, 'Company'), cust) old_location_comp = os.path.join(old_location_comp_, old) len_old = len(os.listdir(old_location_comp)) old_final_comp = os.path.join(old_location_comp, file_name) if len_old == 1: shutil.rmtree(old_location_comp) shutil.rmtree(old_location_cust) else: os.unlink(old_final_comp) os.unlink(old_final_cust) def view_windows(self): '''Used to re-initialize the totals and main window''' self.save_invoice() self.read_in_data() def closeEvent(self, event): if self.start_flag: self.save_invoice(printing=True) flag = self.save_no_threading() if flag == 0: reply = QMessageBox.question( self, 'Close Window', 'Do you want to close the application?', QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: event.accept() else: event.ignore() else: event.ignore() else: event.accept() def updater(self): QMessageBox.information(self, 'Restart Required', 'Run BEI_Updater and Restart program', QMessageBox.Ok) self.close() def change_address(self): self.edi = EDI(self.font, self.size_policy, self.base_directory) def view_customer(self): flag = self.save_no_threading() if flag == 0: location = os.path.join(os.path.expanduser('~/Desktop'), 'BEI_Invoices') location = os.path.join(location, 'Customer') cust_location = os.path.join(location, self.customer.replace(' ', '_')) machine_location = os.path.join( cust_location, self.machine_text.replace(' ', '_')) job_location = os.path.join(machine_location, '{}.pdf'.format( self.current_job)).replace('&', '^&') print(job_location) subprocess.Popen(job_location, shell=True) else: pass def view_company(self): flag = self.save_no_threading() if flag == 0: location = os.path.join(os.path.expanduser('~/Desktop'), 'BEI_Invoices') location = os.path.join(location, 'Company') cust_location = os.path.join(location, self.customer.replace(' ', '_')) machine_location = os.path.join( cust_location, self.machine_text.replace(' ', '_')) job_location = os.path.join(machine_location, '{}.pdf'.format( self.current_job)).replace('&', '^&') subprocess.Popen(job_location, shell=True) else: pass def save_no_threading(self): self.save_invoice(printing=True) try: pdf2.PDF_Builder(self.current_job, self.base_directory, 'Company') pdf2.PDF_Builder(self.current_job, self.base_directory, 'Customer') return 0 except: QMessageBox.information(self, 'Opening Failure', 'Close file and try again', QMessageBox.Ok) time.sleep(1) return 1 def envelop_write(self): #navigate to the envelope folder loc = os.path.join( os.path.join(self.base_directory, 'Customer_Envelopes'), self.envelope_date + '.txt') f = open(loc, 'r') data = f.readlines() f.close() job_numbers = [] for i in data: job_numbers.append(i.split()[1]) QMessageBox.information( self, 'Envelope Printing', 'Load {} invoices into printer before clicking OK'.format( len(job_numbers)), QMessageBox.Ok) base = os.path.join(self.base_directory, 'Saved_Invoices') for i in range(len(job_numbers)): enve_loc = os.path.join(os.path.join(base, job_numbers[i]), 'envelope.pdf') os.startfile(enve_loc, 'print') def envelop_write1(self): #get the job number to print num, ok = QInputDialog.getText(self, 'Single Customer Envelope', 'Job Number to print:', QLineEdit.Normal, '') if num != '' and ok: writer = EWriter(self.base_directory, num) writer.generate_latex() QMessageBox.information( self, 'Envelope Printing', 'Load 1 invoices into printer before clicking OK', QMessageBox.Ok) writer.print_pdf() def billing_envelopes(self): self.billing_ = CE(self.base_directory)
# 关闭所有窗口,也不关闭应用程序 QApplication.setQuitOnLastWindowClosed(False) from PyQt5 import QtWidgets # QWidget窗口是PyQt5中所有用户界口对象的基本类.我们使用了QWidget默认的构造器.默认的构造器没有父类.一个没有父类的窗口被称为一个window. w = MainWindow("https://wx.qq.com/?&lang=zh") # show()方法将窗口显示在屏幕上.一个窗口是先在内存中被创建,然后显示在屏幕上的. w.show() # from PyQt5.QtWidgets import QSystemTrayIcon # from PyQt5.QtGui import QIcon # 在系统托盘处显示图标 tp = QSystemTrayIcon(w) cur_path = os.path.dirname(os.path.realpath(__file__)) config_path = os.path.join(cur_path, 'timg.jpeg') tp.setIcon(QIcon(config_path)) # 设置系统托盘图标的菜单 a1 = QAction('&显示(Show)', triggered=w.show) def quitApp(): QCoreApplication.instance().quit() # 在应用程序全部关闭后,TrayIcon其实还不会自动消失, # 直到你的鼠标移动到上面去后,才会消失, # 这是个问题,(如同你terminate一些带TrayIcon的应用程序时出现的状况), # 这种问题的解决我是通过在程序退出前将其setVisible(False)来完成的。 tp.setVisible(False)
class BlueApp: def __init__(self, argv): self.app = QApplication(argv) self.mainWindow = QtWidgets.QMainWindow() self.mainWindow.closeEvent = self.onClose self.ui = blueMainUi.Ui_MainWindow() self.ui.setupUi(self.mainWindow) #settings and statistics self.defaultSettings = dict(image_detect=True, text_detect=True, game_type=0, pipeRank=0, goal = 10, startDate = time.time(), avata = "res/icon.png", name = "窜天猴") self.defaultStat = dict(stat = [ [0, 0, 0, 0] for i in range(7)], achivement = 0, lastWater = 0, lastFertilize = 0, cleanHours = 0, cleanMinutes = 0) self.achivementsList = ["大淫魔", "从头开始", "欲火渐盛", "钢筋铁骨", "渐入佳境","心无杂念"] self.achivementsStd = [0, 24, 24 * 3, 24 * 7, 24 * 31, 27 * 365] self.loadSettings() self.validateSettings() self.loadStatistics() #setup the visibles self.setupUi2() self.setupWidget1() self.setupWidget2() self.setupWidget3() self.setupWidget4() self.refreshStatistics() #setup porn_detector self.devMode = False self.timeToExit = False self.pornDectector = porn_detector_final.PornDetector() self.pornDetected = False self.detectThread = threading.Thread(target = self.detectPorn) self.detectThread.start() self.alarm = False #first alarm, then take action #setup timer self.cleanMinute = 0 self.MinuteTimer = QTimer() self.MinuteTimer.setInterval(1000 * 60) self.MinuteTimer.timeout.connect(self.addCleanMinute) self.MinuteTimer.start() #lauch self.mainWindow.show() self.connections() def addCleanMinute(self): self.stat["cleanMinutes"] += 1 if self.stat["cleanMinutes"] == 60: self.self.stat["cleanMinutes"] = 0 self.stat["cleanHours"] += 1 self.saveStatistics() self.refreshStatistics() self.checkLevel() def call_zqz(self): if self.settings["game_type"] == 0: os.system("python2 easy_maze/PyMaze.py") else : os.system("python2 esay_game/play.py") QtWidgets.QMessageBox.information(None, "bluer", "你有10s的时间关掉黄黄的东西。") time.sleep(10) def detectPorn(self): while(True): if "PORN_DETECTED" == self.pornDectector.porn_detector(self.settings["image_detect"], self.settings["text_detect"], self.devMode): if self.alarm: self.call_zqz() self.stat["cleanHours"] -= 24 if self.stat["cleanHours"] < 0: self.stat["cleanHours"] = 0 l = self.stat["stat"][time.localtime(time.time())[6]] h = time.localtime(time.time())[3] if h >= 0 and h < 6: l[0] = 1 elif h >= 6 and h < 12: l[1] = 1 elif h >= 12 and h < 18: l[2] = 1; else: l[3] = 1; else: self.alarm = True else: self.alarm = True self.saveStatistics() self.refreshStatistics() time.sleep(10) def onClose(self, event): self.mainWindow.hide() event.ignore() def onTrayClicked(self, event): if event == QSystemTrayIcon.Trigger or event == QSystemTrayIcon.DoubleClick: self.mainWindow.show() def saveSettings(self, event): QtWidgets.QMessageBox.Question = QIcon("res/logo-tray.png") if self.settings["goal"] != self.ui.spin_goal.value(): ret = QtWidgets.QMessageBox.question(self.mainWindow, "Blue", "确定要将目标改为" + str(self.ui.spin_goal.value()) + "天吗?\n" "此操作会重置当前任务的进度。") if ret != QtWidgets.QMessageBox.No: self.settings["goal"] = self.ui.spin_goal.value() self.saveStatistics() self.refreshStatistics() QtWidgets.QMessageBox.information(None, "Blue", "新目标设置为" + str(self.settings["goal"]) + "天") else: QtWidgets.QMessageBox.information(None, "Blue", "目标没有被重置") try: sfile = open(PATH_TO_SETTINGS, "w") json.dump(self.settings, sfile) sfile.close() except Exception: return QtWidgets.QMessageBox.information(None, "Blue", "设置已保存:D") self.refreshStatistics() def checkLevel(self): for i in range(5, -1, -1): if self.stat["cleanHours"] >= self.achivementsStd[i] and self.stat["achivement"] < i: QtWidgets.QMessageBox.information(None, "Blue", "等级提升为Lv. " + str(i) + " :" + self.achivementsList[i]) self.stat["achivement"] = i self.saveStatistics() break def saveStatistics(self): json.dump(self.stat, open(PATH_TO_STATISTICS, "w")) def refreshStatistics(self): days = time.localtime(time.time()) delta = self.settings["goal"] - self.stat["cleanHours"] if delta == 0: QtWidgets.QMessageBox.information(None, "Blue", "目标达成!!!\n请设置新的目标!!!") self.slideClicked3(None) self.ui.lb_days.setText(str(delta)) self.ui.lb_goal.setText(str(self.settings["goal"])) self.ui.lb_achv.setText(self.achivementsList[self.stat["achivement"]]) self.ui.lb_lv.setText("Lv. " + str(self.stat["achivement"])) self.ui.lb_growth.setText(str(self.stat["cleanHours"] // 24)) #setup the water and ferilization if days[7] == time.localtime(self.stat["lastWater"])[7]: self.ui.lb_jiaoshui.setPixmap(QPixmap("res/ack.png")) self.watered = True else: self.watered = False if days[7] == time.localtime(self.stat["lastFertilize"])[7]: self.ui.lb_shifei.setPixmap(QPixmap("res/ack.png")) self.fertilized = True else: self.fertilized = False #setup the calendar pixmapA = QPixmap("res/lu.png") pixmapB = QPixmap("res/blue.png") h = days[3] if h >= 0 and h < 6: r = 0 elif h >= 6 and h < 12: r = 1 elif h >= 12 and h < 18: r = 2 else: r = 3 for i in range(days[6]): for j in range(4): if self.stat["stat"][i][j] == 0: self.statLabels[i][j].setPixmap(pixmapA) else: self.statLabels[i][j].setPixmap(pixmapB) day = days[6] for j in range(r): if self.stat["stat"][day][j] == 0: self.statLabels[day][j].setPixmap(pixmapA) else: self.statLabels[day][j].setPixmap(pixmapB) #setup the wall for i in range(6): self.achivIcons[i].setPixmap(QPixmap("res/" + str(i) * 2)) for i in range(self.stat["achivement"] + 1): self.achivIcons[i].setPixmap(QPixmap("res/" + str(i))) def loadSettings(self): try: sfile = open(PATH_TO_SETTINGS, "r") self.settings = json.load(sfile) sfile.close() except: self.settings = self.defaultSettings self.saveSettings(None) def validateSettings(self): for keys in self.defaultSettings: try: self.settings[keys] except: self.settings[keys] = self.defaultSettings[keys] def loadStatistics(self): try: sfile = open(PATH_TO_STATISTICS, "r") self.stat = json.load(sfile) except: self.stat = self.defaultStat for keys in self.defaultStat: try: self.stat[keys] except: self.stat[keys] = self.defaultStat[keys] self.saveStatistics() def refreshInfo(self): #setup avata pixmap = QPixmap() pixmap.load("res/avata_mask") pixmap.scaled(115, 115) self.ui.lb_avata.setMask(pixmap.mask()) pixmap.load(self.settings["avata"]) self.ui.lb_avata.setPixmap(pixmap) # self.ui.lb_avata2.setMask(pixmap.mask()) # pixmap.load(self.settings["avata"]) # self.ui.lb_avata2.setPixmap(pixmap) #setup the name self.ui.lb_welcomname.setText(self.settings["name"]) self.ui.lb_nick.setText(self.settings["name"]) def appExit(self, event): if self.devMode == False: QtWidgets.QMessageBox.information(None, "bluer", "开发者模式开启") self.devMode = True else: QtWidgets.QMessageBox.information(None, "bluer", "开发者模式关闭") self.devMode = False def avataEdit(self, event): openDlg = QtWidgets.QFontDialog() openDlg.open() def setupUi2(self): #setup event handling self.sideButtons = [self.ui.lb1, self.ui.lb2, self.ui.lb3, self.ui.lb4] self.ui.lb_exit.mousePressEvent = self.appExit self.setupAnimes() self.setupSideButtons() self.refreshInfo() #setup tray self.icon = QIcon("res/logo-tray.png") self.trayIcon = QSystemTrayIcon() self.trayIcon.setIcon(self.icon) self.trayIcon.activated.connect(self.onTrayClicked) self.trayIcon.show() #setup the info edit self.ui.lb_avata.mousePressEvent = self.avataEdit def setupAnimes(self): self.shiftAnime1 = QPropertyAnimation() self.shiftAnime1.setTargetObject(self.ui.widget1) self.shiftAnime1.setPropertyName("geometry".encode()) self.shiftAnime1.setDuration(400) self.shiftAnime1.setStartValue(QRect(177, 29, 0, 571)) self.shiftAnime1.setEndValue(QRect(177, 29, 623, 571)) self.shiftAnime2 = QPropertyAnimation() self.shiftAnime2.setTargetObject(self.ui.widget2) self.shiftAnime2.setPropertyName("geometry".encode()) self.shiftAnime2.setDuration(400) self.shiftAnime2.setStartValue(QRect(800, 29, 0, 571)) self.shiftAnime2.setEndValue(QRect(177, 29, 623, 571)) self.shiftAnime3 = QPropertyAnimation() self.shiftAnime3.setTargetObject(self.ui.widget3) self.shiftAnime3.setPropertyName("geometry".encode()) self.shiftAnime3.setDuration(400) self.shiftAnime3.setStartValue(QRect(800, 29, 623, 571)) self.shiftAnime3.setEndValue(QRect(177, 29, 623, 571)) self.shiftAnime4 = QPropertyAnimation() self.shiftAnime4.setTargetObject(self.ui.widget4) self.shiftAnime4.setPropertyName("geometry".encode()) self.shiftAnime4.setDuration(400) self.shiftAnime4.setStartValue(QRect(800, 29, 623, 571)) self.shiftAnime4.setEndValue(QRect(177, 29, 623, 571)) self.selectedWidget = self.ui.widget1 def setSlideMid(self, bt): if self.selectedSideButton != bt: bt.setStyleSheet(slide_bt_mid) def setSlideUp(self, bt): if self.selectedSideButton != bt: bt.setStyleSheet(slide_bt_up) def setSlideDown(self, bt): self.selectedSideButton.setStyleSheet(slide_bt_up) self.selectedSideButton = bt bt.setStyleSheet(slide_bt_down) def slideEnter1(self, event): self.setSlideMid(self.ui.lb1) def slideEnter2(self, event): self.setSlideMid(self.ui.lb2) def slideEnter3(self, event): self.setSlideMid(self.ui.lb3) def slideEnter4(self, event): self.setSlideMid(self.ui.lb4) def slideLeave1(self, event): self.setSlideUp(self.ui.lb1) def slideLeave2(self, event): self.setSlideUp(self.ui.lb2) def slideLeave3(self, event): self.setSlideUp(self.ui.lb3) def slideLeave4(self, event): self.setSlideUp(self.ui.lb4) def slideBack(self, event): self.setSlideDown(self.ui.lb1) self.ui.widget1.raise_() self.shiftAnime1.start() self.selectedWidget = self.ui.widget1 def slideClicked1(self, event): self.setSlideDown(self.ui.lb1) if self.selectedWidget != self.ui.widget1: self.ui.widget1.raise_() self.shiftAnime1.start() self.selectedWidget = self.ui.widget1 def slideClicked2(self, event): self.setSlideDown(self.ui.lb2) if self.selectedWidget != self.ui.widget2: self.ui.widget2.raise_() self.shiftAnime2.start() self.selectedWidget = self.ui.widget2 def slideClicked3(self, event): self.setSlideDown(self.ui.lb3) if self.selectedWidget != self.ui.widget3: self.ui.widget3.raise_() self.shiftAnime3.start() self.selectedWidget = self.ui.widget3 def jiaoshuiCheck(self, event): pixmap = QPixmap() pixmap.load("res/ack.png") self.ui.lb_jiaoshui.setPixmap(pixmap) self.stat["lastWater"] = time.time() self.saveStatistics() def shifeiCheck(self, event): pixmap = QPixmap() pixmap.load("res/ack.png") self.ui.lb_shifei.setPixmap(pixmap) self.stat["lastFertilize"] = time.time() self.saveStatistics() def slideClicked4(self, event): self.setSlideDown(self.ui.lb4) if self.selectedWidget != self.ui.widget4: self.ui.widget4.raise_() self.shiftAnime4.start() self.selectedWidget = self.ui.widget4 def setupWidget1(self): #setup the tree movie = QMovie() movie.setFileName("res/tree.gif") self.ui.lb_tree.setMovie(movie) self.ui.lb_tree_big.setMovie(movie) movie.start() #setup the statistics self.ui.gridLayout.setHorizontalSpacing(60) self.ui.gridLayout.setVerticalSpacing(10) self.ui.gridLayout.setGeometry(QRect(0, 51, 291, 224)) self.ui.gridLayout.setAlignment(QtCore.Qt.AlignCenter) self.statLabels = [] for i in range(7): self.statLabels.append([]) for j in range(4): self.statLabels[i].append(QLabel()) self.statLabels[i][j].setScaledContents(True) self.statLabels[i][j].setAutoFillBackground(False) self.statLabels[i][j].setAlignment(QtCore.Qt.AlignCenter) self.ui.gridLayout.addWidget(self.statLabels[i][j], i, j, 1, 1) def setupWidget2(self): self.ui.lb_jiaoshui.mousePressEvent = self.jiaoshuiCheck self.ui.lb_shifei.mousePressEvent = self.shifeiCheck def setupWidget3(self): self.ui.check_maze.mousePressEvent = self.mazeCliked self.ui.check_paper.mousePressEvent = self.paperCliked self.ui.check_pic.mousePressEvent = self.picCliked self.ui.check_text.mousePressEvent = self.textClicked self.ui.lb_save.mousePressEvent = self.saveSettings self.ui.spin_goal.setValue(self.settings["goal"]) if self.settings["game_type"] == 0: self.mazeCliked(None) else: self.paperCliked(None) self.picCliked(None) self.picCliked(None) self.textClicked(None) self.textClicked(None) def setupWidget4(self): self.achivIcons = [self.ui.lb_a0, self.ui.lb_a1, self.ui.lb_a2, self.ui.lb_a3, self.ui.lb_a4, self.ui.lb_a5] for i in range(6): self.achivIcons[i].setPixmap(QPixmap("res/" + str(i) * 2)) def mazeCliked(self, event): pixmap = QPixmap() pixmap.load("res/checked.png") self.ui.check_maze.setPixmap(pixmap) pixmap.load("res/unchecked.png") self.ui.check_paper.setPixmap(pixmap) self.settings["game_type"] = 0 def paperCliked(self, event): pixmap = QPixmap() pixmap.load("res/checked.png") self.ui.check_paper.setPixmap(pixmap) pixmap.load("res/unchecked.png") self.ui.check_maze.setPixmap(pixmap) self.settings["game_type"] = 1 def picCliked(self, event): pixmap = QPixmap() pixmap.load("res/checked.png") self.ui.check_pic.setPixmap(pixmap) pixmap.load("res/unchecked.png") self.ui.check_text.setPixmap(pixmap) self.settings["pic_detect"] = 1 self.settings["text_detect"] = 0 def textClicked(self, event): pixmap = QPixmap() pixmap.load("res/checked.png") self.ui.check_text.setPixmap(pixmap) pixmap.load("res/unchecked.png") self.ui.check_pic.setPixmap(pixmap) self.settings["pic_detect"] = 1 self.settings["text_detect"] = 1 def setupSideButtons(self): self.ui.lb1.enterEvent = self.slideEnter1 self.ui.lb1.leaveEvent = self.slideLeave1 self.ui.lb1.mousePressEvent = self.slideClicked1 self.ui.lb2.enterEvent = self.slideEnter2 self.ui.lb2.leaveEvent = self.slideLeave2 self.ui.lb2.mousePressEvent = self.slideClicked2 self.ui.lb3.enterEvent = self.slideEnter3 self.ui.lb3.leaveEvent = self.slideLeave3 self.ui.lb3.mousePressEvent = self.slideClicked3 self.ui.lb4.enterEvent = self.slideEnter4 self.ui.lb4.leaveEvent = self.slideLeave4 self.ui.lb4.mousePressEvent = self.slideClicked4 self.ui.lb4.enterEvent = self.slideEnter4 self.ui.lb4.leaveEvent = self.slideLeave4 self.ui.lb4.mousePressEvent = self.slideClicked4 self.ui.lb_back2.mousePressEvent = self.slideBack self.ui.lb_back3.mousePressEvent = self.slideBack self.ui.lb_back4.mousePressEvent = self.slideBack for lb in self.sideButtons: lb.setStyleSheet(slide_bt_up) self.selectedSideButton = self.ui.lb1 self.slideClicked1(None) def connections(self): pass
class BlenderLauncher(QMainWindow, BaseWindow, Ui_MainWindow): show_signal = pyqtSignal() close_signal = pyqtSignal() def __init__(self, app): super().__init__() self.setupUi(self) # Server self.server = QLocalServer() self.server.listen("blender-launcher-server") self.server.newConnection.connect(self.new_connection) # Global scope self.app = app self.favorite = None self.status = "None" self.app_state = AppState.IDLE self.cashed_builds = [] self.notification_pool = [] self.windows = [self] self.manager = PoolManager(200) self.timer = None # Setup window self.setWindowTitle("Blender Launcher") self.app.setWindowIcon( QIcon(taskbar_icon_paths[get_taskbar_icon_color()])) # Setup font QFontDatabase.addApplicationFont( ":/resources/fonts/OpenSans-SemiBold.ttf") self.font = QFont("Open Sans SemiBold", 10) self.font.setHintingPreference(QFont.PreferNoHinting) self.app.setFont(self.font) # Setup style file = QFile(":/resources/styles/global.qss") file.open(QFile.ReadOnly | QFile.Text) self.style_sheet = QTextStream(file).readAll() self.app.setStyleSheet(self.style_sheet) # Check library folder if is_library_folder_valid() is False: self.dlg = DialogWindow( self, title="Information", text="First, choose where Blender\nbuilds will be stored", accept_text="Continue", cancel_text=None, icon=DialogIcon.INFO) self.dlg.accepted.connect(self.set_library_folder) else: self.draw() def set_library_folder(self): library_folder = Path.cwd().as_posix() new_library_folder = QFileDialog.getExistingDirectory( self, "Select Library Folder", library_folder, options=QFileDialog.DontUseNativeDialog | QFileDialog.ShowDirsOnly) if new_library_folder: set_library_folder(new_library_folder) self.draw() def draw(self): self.HeaderLayout = QHBoxLayout() self.HeaderLayout.setContentsMargins(1, 1, 1, 0) self.HeaderLayout.setSpacing(0) self.CentralLayout.addLayout(self.HeaderLayout) self.SettingsButton = \ QPushButton(QIcon(":resources/icons/settings.svg"), "") self.SettingsButton.setIconSize(QSize(20, 20)) self.SettingsButton.setFixedSize(36, 32) self.WikiButton = \ QPushButton(QIcon(":resources/icons/wiki.svg"), "") self.WikiButton.setIconSize(QSize(20, 20)) self.WikiButton.setFixedSize(36, 32) self.MinimizeButton = \ QPushButton(QIcon(":resources/icons/minimize.svg"), "") self.MinimizeButton.setIconSize(QSize(20, 20)) self.MinimizeButton.setFixedSize(36, 32) self.CloseButton = \ QPushButton(QIcon(":resources/icons/close.svg"), "") self.CloseButton.setIconSize(QSize(20, 20)) self.CloseButton.setFixedSize(36, 32) self.HeaderLabel = QLabel("Blender Launcher") self.HeaderLabel.setAlignment(Qt.AlignCenter) self.HeaderLayout.addWidget(self.SettingsButton, 0, Qt.AlignLeft) self.HeaderLayout.addWidget(self.WikiButton, 0, Qt.AlignLeft) self.HeaderLayout.addWidget(self.HeaderLabel, 1) self.HeaderLayout.addWidget(self.MinimizeButton, 0, Qt.AlignRight) self.HeaderLayout.addWidget(self.CloseButton, 0, Qt.AlignRight) self.SettingsButton.setProperty("HeaderButton", True) self.WikiButton.setProperty("HeaderButton", True) self.MinimizeButton.setProperty("HeaderButton", True) self.CloseButton.setProperty("HeaderButton", True) self.CloseButton.setProperty("CloseButton", True) # Tab layout self.TabWidget = QTabWidget() self.CentralLayout.addWidget(self.TabWidget) self.LibraryTab = QWidget() self.LibraryTabLayout = QVBoxLayout() self.LibraryTabLayout.setContentsMargins(0, 0, 0, 0) self.LibraryTab.setLayout(self.LibraryTabLayout) self.TabWidget.addTab(self.LibraryTab, "Library") self.DownloadsTab = QWidget() self.DownloadsTabLayout = QVBoxLayout() self.DownloadsTabLayout.setContentsMargins(0, 0, 0, 0) self.DownloadsTab.setLayout(self.DownloadsTabLayout) self.TabWidget.addTab(self.DownloadsTab, "Downloads") self.LibraryToolBox = BaseToolBoxWidget(self) self.LibraryStableListWidget = \ self.LibraryToolBox.add_list_widget( "Stable Releases", "Nothing to show yet") self.LibraryDailyListWidget = \ self.LibraryToolBox.add_list_widget( "Daily Builds", "Nothing to show yet") self.LibraryExperimentalListWidget = \ self.LibraryToolBox.add_list_widget( "Experimental Branches", "Nothing to show yet") self.LibraryCustomListWidget = \ self.LibraryToolBox.add_list_widget( "Custom Builds", "Nothing to show yet") self.LibraryTab.layout().addWidget(self.LibraryToolBox) self.DownloadsToolBox = BaseToolBoxWidget(self) self.DownloadsStableListWidget = \ self.DownloadsToolBox.add_list_widget( "Stable Releases", "No new builds available", False) self.DownloadsDailyListWidget = \ self.DownloadsToolBox.add_list_widget( "Daily Builds", "No new builds available") self.DownloadsExperimentalListWidget = \ self.DownloadsToolBox.add_list_widget( "Experimental Branches", "No new builds available") self.DownloadsTab.layout().addWidget(self.DownloadsToolBox) self.LibraryToolBox.setCurrentIndex(get_default_library_page()) # Connect buttons self.SettingsButton.clicked.connect(self.show_settings_window) self.WikiButton.clicked.connect(lambda: webbrowser.open( "https://github.com/DotBow/Blender-Launcher/wiki")) self.MinimizeButton.clicked.connect(self.showMinimized) self.CloseButton.clicked.connect(self.close) self.StatusBar.setFont(self.font) self.statusbarLabel = QLabel() self.statusbarLabel.setIndent(8) self.NewVersionButton = QPushButton() self.NewVersionButton.hide() self.NewVersionButton.clicked.connect(lambda: webbrowser.open( "https://github.com/DotBow/Blender-Launcher/releases/latest")) self.statusbarVersion = QLabel(self.app.applicationVersion()) self.StatusBar.addPermanentWidget(self.statusbarLabel, 1) self.StatusBar.addPermanentWidget(self.NewVersionButton) self.StatusBar.addPermanentWidget(self.statusbarVersion) # Draw library self.draw_library() # Setup tray icon context Menu quit_action = QAction("Quit", self) quit_action.triggered.connect(self.quit) hide_action = QAction("Hide", self) hide_action.triggered.connect(self.close) show_action = QAction("Show", self) show_action.triggered.connect(self._show) launch_favorite_action = QAction( QIcon(":resources/icons/favorite.svg"), "Blender", self) launch_favorite_action.triggered.connect(self.launch_favorite) tray_menu = QMenu() tray_menu.setFont(self.font) tray_menu.addAction(launch_favorite_action) tray_menu.addAction(show_action) tray_menu.addAction(hide_action) tray_menu.addAction(quit_action) # Setup tray icon self.tray_icon = QSystemTrayIcon(self) self.tray_icon.setIcon( QIcon(taskbar_icon_paths[get_taskbar_icon_color()])) self.tray_icon.setToolTip("Blender Launcher") self.tray_icon.activated.connect(self.tray_icon_activated) self.tray_icon.setContextMenu(tray_menu) self.tray_icon.messageClicked.connect(self._show) self.tray_icon.show() # Forse style update self.style().unpolish(self.app) self.style().polish(self.app) # Show window if get_launch_minimized_to_tray() is False: self._show() def _show(self): self.activateWindow() self.show() self.set_status() self.show_signal.emit() def show_message(self, message, value=None): if value not in self.notification_pool: if value is not None: self.notification_pool.append(value) self.tray_icon.showMessage( "Blender Launcher", message, QIcon(taskbar_icon_paths[get_taskbar_icon_color()]), 10000) def launch_favorite(self): try: self.favorite.launch() except Exception: self.dlg = DialogWindow(self, text="Favorite build not found!", accept_text="OK", cancel_text=None) def tray_icon_activated(self, reason): if reason == QSystemTrayIcon.Trigger: self._show() elif reason == QSystemTrayIcon.MiddleClick: self.launch_favorite() def quit(self): download_widgets = [] download_widgets.extend(self.DownloadsStableListWidget.items()) download_widgets.extend(self.DownloadsDailyListWidget.items()) download_widgets.extend(self.DownloadsExperimentalListWidget.items()) for widget in download_widgets: if widget.state == DownloadState.DOWNLOADING: self.dlg = DialogWindow(self, title="Warning", text="Download task in progress!<br>\ Are you sure you want to quit?", accept_text="Yes", cancel_text="No", icon=DialogIcon.WARNING) self.dlg.accepted.connect(self.destroy) return self.destroy() def destroy(self): if self.timer is not None: self.timer.cancel() self.tray_icon.hide() self.app.quit() def draw_library(self, clear=False): self.set_status("Reading local builds") if clear: self.timer.cancel() self.scraper.quit() self.DownloadsStableListWidget.clear() self.DownloadsDailyListWidget.clear() self.DownloadsExperimentalListWidget.clear() self.favorite = None self.LibraryStableListWidget.clear() self.LibraryDailyListWidget.clear() self.LibraryExperimentalListWidget.clear() self.library_drawer = LibraryDrawer(self) self.library_drawer.build_found.connect(self.draw_to_library) self.library_drawer.finished.connect(self.draw_downloads) self.library_drawer.start() def draw_downloads(self): for page in self.DownloadsToolBox.pages: page.set_info_label_text("Checking for new builds") self.app_state = AppState.CHECKINGBUILDS self.set_status("Checking for new builds") self.scraper = Scraper(self, self.manager) self.scraper.links.connect(self.draw_new_builds) self.scraper.new_bl_version.connect(self.set_version) self.scraper.error.connect(self.connection_error) self.scraper.start() def connection_error(self): set_locale() utcnow = strftime(('%H:%M'), localtime()) self.set_status("Connection Error at " + utcnow) self.app_state = AppState.IDLE self.timer = threading.Timer(600.0, self.draw_downloads) self.timer.start() def draw_new_builds(self, builds): self.cashed_builds.clear() self.cashed_builds.extend(builds) library_widgets = [] download_widgets = [] library_widgets.extend(self.LibraryStableListWidget.items()) library_widgets.extend(self.LibraryDailyListWidget.items()) library_widgets.extend(self.LibraryExperimentalListWidget.items()) download_widgets.extend(self.DownloadsStableListWidget.items()) download_widgets.extend(self.DownloadsDailyListWidget.items()) download_widgets.extend(self.DownloadsExperimentalListWidget.items()) for widget in download_widgets: if widget.build_info in builds: builds.remove(widget.build_info) elif widget.state != DownloadState.DOWNLOADING: widget.destroy() for widget in library_widgets: if widget.build_info in builds: builds.remove(widget.build_info) for build_info in builds: self.draw_to_downloads(build_info) set_locale() utcnow = strftime(('%H:%M'), localtime()) self.set_status("Last check at " + utcnow) self.app_state = AppState.IDLE for page in self.DownloadsToolBox.pages: page.set_info_label_text("No new builds available") self.timer = threading.Timer(600.0, self.draw_downloads) self.timer.start() def draw_from_cashed(self, build_info): if self.app_state == AppState.IDLE: if build_info in self.cashed_builds: i = self.cashed_builds.index(build_info) self.draw_to_downloads(self.cashed_builds[i]) def draw_to_downloads(self, build_info): branch = build_info.branch if branch == 'stable': list_widget = self.DownloadsStableListWidget elif branch == 'daily': list_widget = self.DownloadsDailyListWidget else: list_widget = self.DownloadsExperimentalListWidget item = BaseListWidgetItem(build_info.commit_time) widget = DownloadWidget(self, list_widget, item, build_info) list_widget.add_item(item, widget) def draw_to_library(self, path): category = Path(path).parent.name if category == 'stable': list_widget = self.LibraryStableListWidget elif category == 'daily': list_widget = self.LibraryDailyListWidget elif category == 'experimental': list_widget = self.LibraryExperimentalListWidget elif category == 'custom': list_widget = self.LibraryCustomListWidget else: return item = BaseListWidgetItem() widget = LibraryWidget(self, item, path, list_widget) list_widget.insert_item(item, widget) def set_status(self, status=None): if status is not None: self.status = status self.statusbarLabel.setText("Status: {0}".format(self.status)) def set_version(self, latest_tag): current_tag = self.app.applicationVersion() latest_ver = re.sub(r'\D', '', latest_tag) current_ver = re.sub(r'\D', '', current_tag) if int(latest_ver) > int(current_ver): if latest_tag not in self.notification_pool: self.NewVersionButton.setText( "New version {0} is available".format( latest_tag.replace('v', ''))) self.NewVersionButton.show() self.show_message( "New version of Blender Launcher is available!", latest_tag) def show_settings_window(self): self.settings_window = SettingsWindow(self) def clear_temp(self): temp_folder = Path(get_library_folder()) / ".temp" self.remover = Remover(temp_folder) self.remover.start() def closeEvent(self, event): event.ignore() self.hide() self.close_signal.emit() def new_connection(self): self._show()
class TriblerWindow(QMainWindow): resize_event = pyqtSignal() escape_pressed = pyqtSignal() tribler_crashed = pyqtSignal(str) received_search_completions = pyqtSignal(object) def on_exception(self, *exc_info): if self.exception_handler_called: # We only show one feedback dialog, even when there are two consecutive exceptions. return self.exception_handler_called = True exception_text = "".join(traceback.format_exception(*exc_info)) logging.error(exception_text) self.tribler_crashed.emit(exception_text) self.delete_tray_icon() # Stop the download loop self.downloads_page.stop_loading_downloads() # Add info about whether we are stopping Tribler or not os.environ['TRIBLER_SHUTTING_DOWN'] = str(self.core_manager.shutting_down) if not self.core_manager.shutting_down: self.core_manager.stop(stop_app_on_shutdown=False) self.setHidden(True) if self.debug_window: self.debug_window.setHidden(True) dialog = FeedbackDialog(self, exception_text, self.core_manager.events_manager.tribler_version, self.start_time) dialog.show() def __init__(self, core_args=None, core_env=None, api_port=None): QMainWindow.__init__(self) QCoreApplication.setOrganizationDomain("nl") QCoreApplication.setOrganizationName("TUDelft") QCoreApplication.setApplicationName("Tribler") QCoreApplication.setAttribute(Qt.AA_UseHighDpiPixmaps) self.gui_settings = QSettings() api_port = api_port or int(get_gui_setting(self.gui_settings, "api_port", DEFAULT_API_PORT)) dispatcher.update_worker_settings(port=api_port) self.navigation_stack = [] self.tribler_started = False self.tribler_settings = None self.debug_window = None self.core_manager = CoreManager(api_port) self.pending_requests = {} self.pending_uri_requests = [] self.download_uri = None self.dialog = None self.new_version_dialog = None self.start_download_dialog_active = False self.request_mgr = None self.search_request_mgr = None self.search_suggestion_mgr = None self.selected_torrent_files = [] self.vlc_available = True self.has_search_results = False self.last_search_query = None self.last_search_time = None self.start_time = time.time() self.exception_handler_called = False self.token_refresh_timer = None self.shutdown_timer = None self.add_torrent_url_dialog_active = False sys.excepthook = self.on_exception uic.loadUi(get_ui_file_path('mainwindow.ui'), self) TriblerRequestManager.window = self self.tribler_status_bar.hide() self.token_balance_widget.mouseReleaseEvent = self.on_token_balance_click def on_state_update(new_state): self.loading_text_label.setText(new_state) self.core_manager.core_state_update.connect(on_state_update) self.magnet_handler = MagnetHandler(self.window) QDesktopServices.setUrlHandler("magnet", self.magnet_handler, "on_open_magnet_link") self.debug_pane_shortcut = QShortcut(QKeySequence("Ctrl+d"), self) self.debug_pane_shortcut.activated.connect(self.clicked_menu_button_debug) self.import_torrent_shortcut = QShortcut(QKeySequence("Ctrl+o"), self) self.import_torrent_shortcut.activated.connect(self.on_add_torrent_browse_file) self.add_torrent_url_shortcut = QShortcut(QKeySequence("Ctrl+i"), self) self.add_torrent_url_shortcut.activated.connect(self.on_add_torrent_from_url) # Remove the focus rect on OS X for widget in self.findChildren(QLineEdit) + self.findChildren(QListWidget) + self.findChildren(QTreeWidget): widget.setAttribute(Qt.WA_MacShowFocusRect, 0) self.menu_buttons = [self.left_menu_button_home, self.left_menu_button_search, self.left_menu_button_my_channel, self.left_menu_button_subscriptions, self.left_menu_button_video_player, self.left_menu_button_downloads, self.left_menu_button_discovered] self.video_player_page.initialize_player() self.search_results_page.initialize_search_results_page(self.gui_settings) self.settings_page.initialize_settings_page() self.subscribed_channels_page.initialize() self.edit_channel_page.initialize_edit_channel_page(self.gui_settings) self.downloads_page.initialize_downloads_page() self.home_page.initialize_home_page() self.loading_page.initialize_loading_page() self.discovering_page.initialize_discovering_page() self.discovered_page.initialize_discovered_page(self.gui_settings) self.channel_page.initialize_channel_page(self.gui_settings) self.trust_page.initialize_trust_page() self.token_mining_page.initialize_token_mining_page() self.stackedWidget.setCurrentIndex(PAGE_LOADING) # Create the system tray icon if QSystemTrayIcon.isSystemTrayAvailable(): self.tray_icon = QSystemTrayIcon() use_monochrome_icon = get_gui_setting(self.gui_settings, "use_monochrome_icon", False, is_bool=True) self.update_tray_icon(use_monochrome_icon) # Create the tray icon menu menu = self.create_add_torrent_menu() show_downloads_action = QAction('Show downloads', self) show_downloads_action.triggered.connect(self.clicked_menu_button_downloads) token_balance_action = QAction('Show token balance', self) token_balance_action.triggered.connect(lambda: self.on_token_balance_click(None)) quit_action = QAction('Quit Tribler', self) quit_action.triggered.connect(self.close_tribler) menu.addSeparator() menu.addAction(show_downloads_action) menu.addAction(token_balance_action) menu.addSeparator() menu.addAction(quit_action) self.tray_icon.setContextMenu(menu) else: self.tray_icon = None self.hide_left_menu_playlist() self.left_menu_button_debug.setHidden(True) self.top_menu_button.setHidden(True) self.left_menu.setHidden(True) self.token_balance_widget.setHidden(True) self.settings_button.setHidden(True) self.add_torrent_button.setHidden(True) self.top_search_bar.setHidden(True) # Set various icons self.top_menu_button.setIcon(QIcon(get_image_path('menu.png'))) self.search_completion_model = QStringListModel() completer = QCompleter() completer.setModel(self.search_completion_model) completer.setCompletionMode(QCompleter.UnfilteredPopupCompletion) self.item_delegate = QStyledItemDelegate() completer.popup().setItemDelegate(self.item_delegate) completer.popup().setStyleSheet(""" QListView { background-color: #404040; } QListView::item { color: #D0D0D0; padding-top: 5px; padding-bottom: 5px; } QListView::item:hover { background-color: #707070; } """) self.top_search_bar.setCompleter(completer) # Toggle debug if developer mode is enabled self.window().left_menu_button_debug.setHidden( not get_gui_setting(self.gui_settings, "debug", False, is_bool=True)) # Start Tribler self.core_manager.start(core_args=core_args, core_env=core_env) self.core_manager.events_manager.torrent_finished.connect(self.on_torrent_finished) self.core_manager.events_manager.new_version_available.connect(self.on_new_version_available) self.core_manager.events_manager.tribler_started.connect(self.on_tribler_started) self.core_manager.events_manager.events_started.connect(self.on_events_started) self.core_manager.events_manager.low_storage_signal.connect(self.on_low_storage) self.core_manager.events_manager.credit_mining_signal.connect(self.on_credit_mining_error) self.core_manager.events_manager.tribler_shutdown_signal.connect(self.on_tribler_shutdown_state_update) self.core_manager.events_manager.upgrader_tick.connect( lambda text: self.show_status_bar("Upgrading Tribler database: " + text)) self.core_manager.events_manager.upgrader_finished.connect( lambda _: self.hide_status_bar()) self.core_manager.events_manager.received_search_result.connect( self.search_results_page.received_search_result) # Install signal handler for ctrl+c events def sigint_handler(*_): self.close_tribler() signal.signal(signal.SIGINT, sigint_handler) self.installEventFilter(self.video_player_page) # Resize the window according to the settings center = QApplication.desktop().availableGeometry(self).center() pos = self.gui_settings.value("pos", QPoint(center.x() - self.width() * 0.5, center.y() - self.height() * 0.5)) size = self.gui_settings.value("size", self.size()) self.move(pos) self.resize(size) self.show() def update_tray_icon(self, use_monochrome_icon): if not QSystemTrayIcon.isSystemTrayAvailable() or not self.tray_icon: return if use_monochrome_icon: self.tray_icon.setIcon(QIcon(QPixmap(get_image_path('monochrome_tribler.png')))) else: self.tray_icon.setIcon(QIcon(QPixmap(get_image_path('tribler.png')))) self.tray_icon.show() def delete_tray_icon(self): if self.tray_icon: try: self.tray_icon.deleteLater() except RuntimeError: # The tray icon might have already been removed when unloading Qt. # This is due to the C code actually being asynchronous. logging.debug("Tray icon already removed, no further deletion necessary.") self.tray_icon = None def on_low_storage(self): """ Dealing with low storage space available. First stop the downloads and the core manager and ask user to user to make free space. :return: """ self.downloads_page.stop_loading_downloads() self.core_manager.stop(False) close_dialog = ConfirmationDialog(self.window(), "<b>CRITICAL ERROR</b>", "You are running low on disk space (<100MB). Please make sure to have " "sufficient free space available and restart Tribler again.", [("Close Tribler", BUTTON_TYPE_NORMAL)]) close_dialog.button_clicked.connect(lambda _: self.close_tribler()) close_dialog.show() def on_torrent_finished(self, torrent_info): self.tray_show_message("Download finished", "Download of %s has finished." % torrent_info["name"]) def show_loading_screen(self): self.top_menu_button.setHidden(True) self.left_menu.setHidden(True) self.token_balance_widget.setHidden(True) self.settings_button.setHidden(True) self.add_torrent_button.setHidden(True) self.top_search_bar.setHidden(True) self.stackedWidget.setCurrentIndex(PAGE_LOADING) def tray_set_tooltip(self, message): """ Set a tooltip message for the tray icon, if possible. :param message: the message to display on hover """ if self.tray_icon: try: self.tray_icon.setToolTip(message) except RuntimeError as e: logging.error("Failed to set tray tooltip: %s", str(e)) def tray_show_message(self, title, message): """ Show a message at the tray icon, if possible. :param title: the title of the message :param message: the message to display """ if self.tray_icon: try: self.tray_icon.showMessage(title, message) except RuntimeError as e: logging.error("Failed to set tray message: %s", str(e)) def on_tribler_started(self): self.tribler_started = True self.top_menu_button.setHidden(False) self.left_menu.setHidden(False) self.token_balance_widget.setHidden(False) self.settings_button.setHidden(False) self.add_torrent_button.setHidden(False) self.top_search_bar.setHidden(False) # fetch the settings, needed for the video player port self.request_mgr = TriblerRequestManager() self.fetch_settings() self.downloads_page.start_loading_downloads() self.home_page.load_popular_torrents() if not self.gui_settings.value("first_discover", False) and not self.core_manager.use_existing_core: self.window().gui_settings.setValue("first_discover", True) self.discovering_page.is_discovering = True self.stackedWidget.setCurrentIndex(PAGE_DISCOVERING) else: self.clicked_menu_button_home() self.setAcceptDrops(True) def on_events_started(self, json_dict): self.setWindowTitle("Tribler %s" % json_dict["version"]) def show_status_bar(self, message): self.tribler_status_bar_label.setText(message) self.tribler_status_bar.show() def hide_status_bar(self): self.tribler_status_bar.hide() def process_uri_request(self): """ Process a URI request if we have one in the queue. """ if len(self.pending_uri_requests) == 0: return uri = self.pending_uri_requests.pop() if uri.startswith('file') or uri.startswith('magnet'): self.start_download_from_uri(uri) def perform_start_download_request(self, uri, anon_download, safe_seeding, destination, selected_files, total_files=0, callback=None): # Check if destination directory is writable is_writable, error = is_dir_writable(destination) if not is_writable: gui_error_message = "Insufficient write permissions to <i>%s</i> directory. Please add proper " \ "write permissions on the directory and add the torrent again. %s" \ % (destination, error) ConfirmationDialog.show_message(self.window(), "Download error <i>%s</i>" % uri, gui_error_message, "OK") return selected_files_list = [] if len(selected_files) != total_files: # Not all files included selected_files_list = [filename for filename in selected_files] anon_hops = int(self.tribler_settings['download_defaults']['number_hops']) if anon_download else 0 safe_seeding = 1 if safe_seeding else 0 post_data = { "uri": uri, "anon_hops": anon_hops, "safe_seeding": safe_seeding, "destination": destination, "selected_files": selected_files_list } request_mgr = TriblerRequestManager() request_mgr.perform_request("downloads", callback if callback else self.on_download_added, method='PUT', data=post_data) # Save the download location to the GUI settings current_settings = get_gui_setting(self.gui_settings, "recent_download_locations", "") recent_locations = current_settings.split(",") if len(current_settings) > 0 else [] if isinstance(destination, six.text_type): destination = destination.encode('utf-8') encoded_destination = hexlify(destination) if encoded_destination in recent_locations: recent_locations.remove(encoded_destination) recent_locations.insert(0, encoded_destination) if len(recent_locations) > 5: recent_locations = recent_locations[:5] self.gui_settings.setValue("recent_download_locations", ','.join(recent_locations)) def on_new_version_available(self, version): if version == str(self.gui_settings.value('last_reported_version')): return self.new_version_dialog = ConfirmationDialog(self, "New version available", "Version %s of Tribler is available.Do you want to visit the " "website to download the newest version?" % version, [('IGNORE', BUTTON_TYPE_NORMAL), ('LATER', BUTTON_TYPE_NORMAL), ('OK', BUTTON_TYPE_NORMAL)]) self.new_version_dialog.button_clicked.connect(lambda action: self.on_new_version_dialog_done(version, action)) self.new_version_dialog.show() def on_new_version_dialog_done(self, version, action): if action == 0: # ignore self.gui_settings.setValue("last_reported_version", version) elif action == 2: # ok import webbrowser webbrowser.open("https://tribler.org") if self.new_version_dialog: self.new_version_dialog.close_dialog() self.new_version_dialog = None def on_search_text_change(self, text): self.search_suggestion_mgr = TriblerRequestManager() self.search_suggestion_mgr.perform_request( "search/completions", self.on_received_search_completions, url_params={'q': sanitize_for_fts(text)}) def on_received_search_completions(self, completions): if completions is None: return self.received_search_completions.emit(completions) self.search_completion_model.setStringList(completions["completions"]) def fetch_settings(self): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("settings", self.received_settings, capture_errors=False) def received_settings(self, settings): if not settings: return # If we cannot receive the settings, stop Tribler with an option to send the crash report. if 'error' in settings: raise RuntimeError(TriblerRequestManager.get_message_from_error(settings)) self.tribler_settings = settings['settings'] # Set the video server port self.video_player_page.video_player_port = settings["ports"]["video_server~port"] # Disable various components based on the settings if not self.tribler_settings['video_server']['enabled']: self.left_menu_button_video_player.setHidden(True) self.downloads_creditmining_button.setHidden(not self.tribler_settings["credit_mining"]["enabled"]) self.downloads_all_button.click() # process pending file requests (i.e. someone clicked a torrent file when Tribler was closed) # We do this after receiving the settings so we have the default download location. self.process_uri_request() # Set token balance refresh timer and load the token balance self.token_refresh_timer = QTimer() self.token_refresh_timer.timeout.connect(self.load_token_balance) self.token_refresh_timer.start(60000) self.load_token_balance() def on_top_search_button_click(self): current_ts = time.time() current_search_query = self.top_search_bar.text() if self.last_search_query and self.last_search_time \ and self.last_search_query == self.top_search_bar.text() \ and current_ts - self.last_search_time < 1: logging.info("Same search query already sent within 500ms so dropping this one") return self.left_menu_button_search.setChecked(True) self.has_search_results = True self.clicked_menu_button_search() self.search_results_page.perform_search(current_search_query) self.last_search_query = current_search_query self.last_search_time = current_ts def on_settings_button_click(self): self.deselect_all_menu_buttons() self.stackedWidget.setCurrentIndex(PAGE_SETTINGS) self.settings_page.load_settings() self.navigation_stack = [] self.hide_left_menu_playlist() def on_token_balance_click(self, _): self.raise_window() self.deselect_all_menu_buttons() self.stackedWidget.setCurrentIndex(PAGE_TRUST) self.load_token_balance() self.trust_page.load_blocks() self.navigation_stack = [] self.hide_left_menu_playlist() def load_token_balance(self): self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("trustchain/statistics", self.received_trustchain_statistics, capture_errors=False) def received_trustchain_statistics(self, statistics): if not statistics or "statistics" not in statistics: return self.trust_page.received_trustchain_statistics(statistics) statistics = statistics["statistics"] if 'latest_block' in statistics: balance = (statistics["latest_block"]["transaction"]["total_up"] - statistics["latest_block"]["transaction"]["total_down"]) self.set_token_balance(balance) else: self.token_balance_label.setText("0 MB") # If trust page is currently visible, then load the graph as well if self.stackedWidget.currentIndex() == PAGE_TRUST: self.trust_page.load_blocks() def set_token_balance(self, balance): if abs(balance) > 1024 ** 4: # Balance is over a TB balance /= 1024.0 ** 4 self.token_balance_label.setText("%.1f TB" % balance) elif abs(balance) > 1024 ** 3: # Balance is over a GB balance /= 1024.0 ** 3 self.token_balance_label.setText("%.1f GB" % balance) else: balance /= 1024.0 ** 2 self.token_balance_label.setText("%d MB" % balance) def raise_window(self): self.setWindowState(self.windowState() & ~Qt.WindowMinimized | Qt.WindowActive) self.raise_() self.activateWindow() def create_add_torrent_menu(self): """ Create a menu to add new torrents. Shows when users click on the tray icon or the big plus button. """ menu = TriblerActionMenu(self) browse_files_action = QAction('Import torrent from file', self) browse_directory_action = QAction('Import torrent(s) from directory', self) add_url_action = QAction('Import torrent from magnet/URL', self) add_mdblob_action = QAction('Import Tribler metadata from file', self) browse_files_action.triggered.connect(self.on_add_torrent_browse_file) browse_directory_action.triggered.connect(self.on_add_torrent_browse_dir) add_url_action.triggered.connect(self.on_add_torrent_from_url) add_mdblob_action.triggered.connect(self.on_add_mdblob_browse_file) menu.addAction(browse_files_action) menu.addAction(browse_directory_action) menu.addAction(add_url_action) menu.addAction(add_mdblob_action) return menu def on_add_torrent_button_click(self, pos): self.create_add_torrent_menu().exec_(self.mapToGlobal(self.add_torrent_button.pos())) def on_add_torrent_browse_file(self): filenames = QFileDialog.getOpenFileNames(self, "Please select the .torrent file", QDir.homePath(), "Torrent files (*.torrent)") if len(filenames[0]) > 0: [self.pending_uri_requests.append(u"file:%s" % filename) for filename in filenames[0]] self.process_uri_request() def on_add_mdblob_browse_file(self): filenames = QFileDialog.getOpenFileNames(self, "Please select the .mdblob file", QDir.homePath(), "Tribler metadata files (*.mdblob)") if len(filenames[0]) > 0: for filename in filenames[0]: self.pending_uri_requests.append(u"file:%s" % filename) self.process_uri_request() def start_download_from_uri(self, uri): self.download_uri = uri if get_gui_setting(self.gui_settings, "ask_download_settings", True, is_bool=True): # If tribler settings is not available, fetch the settings and inform the user to try again. if not self.tribler_settings: self.fetch_settings() ConfirmationDialog.show_error(self, "Download Error", "Tribler settings is not available yet. " "Fetching it now. Please try again later.") return # Clear any previous dialog if exists if self.dialog: self.dialog.close_dialog() self.dialog = None self.dialog = StartDownloadDialog(self, self.download_uri) self.dialog.button_clicked.connect(self.on_start_download_action) self.dialog.show() self.start_download_dialog_active = True else: # In the unlikely scenario that tribler settings are not available yet, try to fetch settings again and # add the download uri back to self.pending_uri_requests to process again. if not self.tribler_settings: self.fetch_settings() if self.download_uri not in self.pending_uri_requests: self.pending_uri_requests.append(self.download_uri) return self.window().perform_start_download_request(self.download_uri, self.window().tribler_settings['download_defaults'][ 'anonymity_enabled'], self.window().tribler_settings['download_defaults'][ 'safeseeding_enabled'], self.tribler_settings['download_defaults']['saveas'], [], 0) self.process_uri_request() def on_start_download_action(self, action): if action == 1: if self.dialog and self.dialog.dialog_widget: self.window().perform_start_download_request( self.download_uri, self.dialog.dialog_widget.anon_download_checkbox.isChecked(), self.dialog.dialog_widget.safe_seed_checkbox.isChecked(), self.dialog.dialog_widget.destination_input.currentText(), self.dialog.get_selected_files(), self.dialog.dialog_widget.files_list_view.topLevelItemCount()) else: ConfirmationDialog.show_error(self, "Tribler UI Error", "Something went wrong. Please try again.") logging.exception("Error while trying to download. Either dialog or dialog.dialog_widget is None") if self.dialog: self.dialog.close_dialog() self.dialog = None self.start_download_dialog_active = False if action == 0: # We do this after removing the dialog since process_uri_request is blocking self.process_uri_request() def on_add_torrent_browse_dir(self): chosen_dir = QFileDialog.getExistingDirectory(self, "Please select the directory containing the .torrent files", QDir.homePath(), QFileDialog.ShowDirsOnly) if len(chosen_dir) != 0: self.selected_torrent_files = [torrent_file for torrent_file in glob.glob(chosen_dir + "/*.torrent")] self.dialog = ConfirmationDialog(self, "Add torrents from directory", "Are you sure you want to add %d torrents to Tribler?" % len(self.selected_torrent_files), [('ADD', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)]) self.dialog.button_clicked.connect(self.on_confirm_add_directory_dialog) self.dialog.show() def on_confirm_add_directory_dialog(self, action): if action == 0: for torrent_file in self.selected_torrent_files: escaped_uri = u"file:%s" % pathname2url(torrent_file.encode('utf-8')) self.perform_start_download_request(escaped_uri, self.window().tribler_settings['download_defaults'][ 'anonymity_enabled'], self.window().tribler_settings['download_defaults'][ 'safeseeding_enabled'], self.tribler_settings['download_defaults']['saveas'], [], 0) if self.dialog: self.dialog.close_dialog() self.dialog = None def on_add_torrent_from_url(self): # Make sure that the window is visible (this action might be triggered from the tray icon) self.raise_window() if self.video_player_page.isVisible(): # If we're adding a torrent from the video player page, go to the home page. # This is necessary since VLC takes the screen and the popup becomes invisible. self.clicked_menu_button_home() if not self.add_torrent_url_dialog_active: self.dialog = ConfirmationDialog(self, "Add torrent from URL/magnet link", "Please enter the URL/magnet link in the field below:", [('ADD', BUTTON_TYPE_NORMAL), ('CANCEL', BUTTON_TYPE_CONFIRM)], show_input=True) self.dialog.dialog_widget.dialog_input.setPlaceholderText('URL/magnet link') self.dialog.dialog_widget.dialog_input.setFocus() self.dialog.button_clicked.connect(self.on_torrent_from_url_dialog_done) self.dialog.show() self.add_torrent_url_dialog_active = True def on_torrent_from_url_dialog_done(self, action): self.add_torrent_url_dialog_active = False if self.dialog and self.dialog.dialog_widget: uri = self.dialog.dialog_widget.dialog_input.text().strip() # If the URI is a 40-bytes hex-encoded infohash, convert it to a valid magnet link if len(uri) == 40: valid_ih_hex = True try: int(uri, 16) except ValueError: valid_ih_hex = False if valid_ih_hex: uri = "magnet:?xt=urn:btih:" + uri # Remove first dialog self.dialog.close_dialog() self.dialog = None if action == 0: self.start_download_from_uri(uri) def on_download_added(self, result): if not result: return if len(self.pending_uri_requests) == 0: # Otherwise, we first process the remaining requests. self.window().left_menu_button_downloads.click() else: self.process_uri_request() def on_top_menu_button_click(self): if self.left_menu.isHidden(): self.left_menu.show() else: self.left_menu.hide() def deselect_all_menu_buttons(self, except_select=None): for button in self.menu_buttons: if button == except_select: button.setEnabled(False) continue button.setEnabled(True) if button == self.left_menu_button_search and not self.has_search_results: button.setEnabled(False) button.setChecked(False) def clicked_menu_button_home(self): self.deselect_all_menu_buttons(self.left_menu_button_home) self.stackedWidget.setCurrentIndex(PAGE_HOME) self.navigation_stack = [] self.hide_left_menu_playlist() def clicked_menu_button_search(self): self.deselect_all_menu_buttons(self.left_menu_button_search) self.stackedWidget.setCurrentIndex(PAGE_SEARCH_RESULTS) self.navigation_stack = [] self.hide_left_menu_playlist() def clicked_menu_button_discovered(self): self.deselect_all_menu_buttons(self.left_menu_button_discovered) self.stackedWidget.setCurrentIndex(PAGE_DISCOVERED) self.discovered_page.load_discovered_channels() self.discovered_channels_list.setFocus() self.navigation_stack = [] self.hide_left_menu_playlist() def clicked_menu_button_my_channel(self): self.deselect_all_menu_buttons(self.left_menu_button_my_channel) self.stackedWidget.setCurrentIndex(PAGE_EDIT_CHANNEL) self.edit_channel_page.load_my_channel_overview() self.navigation_stack = [] self.hide_left_menu_playlist() def clicked_menu_button_video_player(self): self.deselect_all_menu_buttons(self.left_menu_button_video_player) self.stackedWidget.setCurrentIndex(PAGE_VIDEO_PLAYER) self.navigation_stack = [] self.show_left_menu_playlist() def clicked_menu_button_downloads(self): self.deselect_all_menu_buttons(self.left_menu_button_downloads) self.raise_window() self.left_menu_button_downloads.setChecked(True) self.stackedWidget.setCurrentIndex(PAGE_DOWNLOADS) self.navigation_stack = [] self.hide_left_menu_playlist() def clicked_menu_button_debug(self): if not self.debug_window: self.debug_window = DebugWindow(self.tribler_settings, self.core_manager.events_manager.tribler_version) self.debug_window.show() def clicked_menu_button_subscriptions(self): self.deselect_all_menu_buttons(self.left_menu_button_subscriptions) self.stackedWidget.setCurrentIndex(PAGE_SUBSCRIBED_CHANNELS) self.subscribed_channels_page.load_subscribed_channels() self.navigation_stack = [] self.hide_left_menu_playlist() def hide_left_menu_playlist(self): self.left_menu_seperator.setHidden(True) self.left_menu_playlist_label.setHidden(True) self.left_menu_playlist.setHidden(True) def show_left_menu_playlist(self): self.left_menu_seperator.setHidden(False) self.left_menu_playlist_label.setHidden(False) self.left_menu_playlist.setHidden(False) def on_channel_clicked(self, channel_info): self.channel_page.initialize_with_channel(channel_info) self.navigation_stack.append(self.stackedWidget.currentIndex()) self.stackedWidget.setCurrentIndex(PAGE_CHANNEL_DETAILS) def on_page_back_clicked(self): try: prev_page = self.navigation_stack.pop() self.stackedWidget.setCurrentIndex(prev_page) except IndexError: logging.exception("Unknown page found in stack") def on_credit_mining_error(self, error): ConfirmationDialog.show_error(self, "Credit Mining Error", error[u'message']) def on_edit_channel_clicked(self): self.stackedWidget.setCurrentIndex(PAGE_EDIT_CHANNEL) self.navigation_stack = [] self.channel_page.on_edit_channel_clicked() def resizeEvent(self, _): # Resize home page cells cell_width = self.home_page_table_view.width() / 3 - 3 # We have some padding to the right max_height = self.home_page_table_view.height() / 3 - 4 cell_height = min(cell_width / 2 + 60, max_height) for i in range(0, 3): self.home_page_table_view.setColumnWidth(i, cell_width) self.home_page_table_view.setRowHeight(i, cell_height) self.resize_event.emit() def exit_full_screen(self): self.top_bar.show() self.left_menu.show() self.video_player_page.is_full_screen = False self.showNormal() def close_tribler(self): if not self.core_manager.shutting_down: def show_force_shutdown(): self.window().force_shutdown_btn.show() self.delete_tray_icon() self.show_loading_screen() self.hide_status_bar() self.loading_text_label.setText("Shutting down...") if self.debug_window: self.debug_window.setHidden(True) self.shutdown_timer = QTimer() self.shutdown_timer.timeout.connect(show_force_shutdown) self.shutdown_timer.start(SHUTDOWN_WAITING_PERIOD) self.gui_settings.setValue("pos", self.pos()) self.gui_settings.setValue("size", self.size()) if self.core_manager.use_existing_core: # Don't close the core that we are using QApplication.quit() self.core_manager.stop() self.core_manager.shutting_down = True self.downloads_page.stop_loading_downloads() request_queue.clear() # Stop the token balance timer if self.token_refresh_timer: self.token_refresh_timer.stop() def closeEvent(self, close_event): self.close_tribler() close_event.ignore() def keyReleaseEvent(self, event): if event.key() == Qt.Key_Escape: self.escape_pressed.emit() if self.isFullScreen(): self.exit_full_screen() def dragEnterEvent(self, e): file_urls = [_qurl_to_path(url) for url in e.mimeData().urls()] if e.mimeData().hasUrls() else [] if any(os.path.isfile(filename) for filename in file_urls): e.accept() else: e.ignore() def dropEvent(self, e): file_urls = ([(_qurl_to_path(url), url.toString()) for url in e.mimeData().urls()] if e.mimeData().hasUrls() else []) for filename, fileurl in file_urls: if os.path.isfile(filename): self.start_download_from_uri(fileurl) e.accept() def clicked_force_shutdown(self): process_checker = ProcessChecker() if process_checker.already_running: core_pid = process_checker.get_pid_from_lock_file() os.kill(int(core_pid), 9) # Stop the Qt application QApplication.quit() def on_tribler_shutdown_state_update(self, state): self.loading_text_label.setText(state)
class MainWindow(QMainWindow): """ 主窗体 """ tray_icon = None # 托盘图标 base_widget = None # 界面最基础容器 base_layout = None # 界面最基础布局 left_frame = None # 界面左边容器 left_layout = None # 界面左边布局 right_frame = None # 界面右边容器 right_layout = None # 界面右边布局 option_frame = None # 功能选项容器 option_list_widget = None # 左侧功能选项 option_stack_widget = None # 右侧功能页面 log_widget = None # 右侧日志界面 log_text = None # 日志框 log_text_signal = pyqtSignal(str) def __init__(self, *args, **kwargs): super(MainWindow, self).__init__(*args, **kwargs) self.init_window() self.init_tray() self.log_text_signal.connect(self.append_text) threading.Thread(target=self.read_log).start() def init_window(self): """ 初始化窗体 :return: """ self.setWindowTitle("虾皮助手") self.setMinimumSize(QSize(800, 600)) self.setStyleSheet("background-color: rgb(248,248, 255)") # 基础容器定义 self.base_widget = QWidget(self) self.base_layout = QHBoxLayout() self.base_widget.setLayout(self.base_layout) self.setCentralWidget(self.base_widget) # 左边容器定义 self.left_frame = QFrame(self.base_widget) self.init_left_frame() # 右边容器定义 self.right_frame = QFrame(self.base_widget) self.init_right_frame() self.base_layout.addWidget(self.left_frame) self.base_layout.addWidget(self.right_frame) def init_left_frame(self): """ 初始化左边容器 :return: """ self.left_layout = QVBoxLayout() self.left_frame.setLayout(self.left_layout) self.left_layout.setContentsMargins(0, 0, 0, 0) self.left_frame.setContentsMargins(0, 20, 0, 0) self.left_frame.setMinimumWidth(125) self.left_frame.setMaximumWidth(125) self.left_frame.setStyleSheet("background-color: rgb(222,248, 255)") size_policy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) size_policy.setHorizontalStretch(1) size_policy.setVerticalStretch(0) size_policy.setHeightForWidth( self.left_frame.sizePolicy().hasHeightForWidth()) self.left_frame.setSizePolicy(size_policy) avatar_label = AvatarLabel(image_path=AVATAR_IMAGE_PATH) self.left_layout.addWidget(avatar_label) self.option_frame = QWidget() self.option_frame.setContentsMargins(0, 30, 0, 0) option_layout = QVBoxLayout() option_layout.setContentsMargins(0, 0, 0, 0) self.option_frame.setLayout(option_layout) self.option_list_widget = QListWidget() self.option_list_widget.setContentsMargins(0, 0, 0, 0) option_layout.addWidget(self.option_list_widget) for i in range(len(OPTIONS)): item = QListWidgetItem() item.setText(OPTIONS[i]) item.setIcon( QIcon(os.path.join(OPTIONS_ICON_DIR, '{}.png'.format(i + 1)))) item.setTextAlignment(Qt.AlignCenter) item.setSizeHint(QSize(125, 25)) self.option_list_widget.addItem(item) self.option_list_widget.setFrameStyle(QListWidget.NoFrame) self.left_layout.addWidget(self.option_frame) def init_right_frame(self): """ 初始化右边容器 :return: """ self.right_layout = QVBoxLayout() self.right_layout.setContentsMargins(0, 0, 0, 0) self.right_frame.setContentsMargins(0, 0, 0, 0) self.right_frame.setLayout(self.right_layout) self.right_frame.setStyleSheet("background-color: rgb(248,248, 255)") size_policy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) size_policy.setHorizontalStretch(4) size_policy.setVerticalStretch(0) size_policy.setHeightForWidth( self.right_frame.sizePolicy().hasHeightForWidth()) self.right_frame.setSizePolicy(size_policy) self.option_stack_widget = QStackedWidget() self.option_list_widget.setMinimumHeight(600) self.option_stack_widget.setContentsMargins(0, 0, 0, 0) self.option_list_widget.currentRowChanged.connect( self.option_stack_widget.setCurrentIndex) size_policy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) size_policy.setHorizontalStretch(0) size_policy.setVerticalStretch(10) size_policy.setHeightForWidth( self.option_stack_widget.sizePolicy().hasHeightForWidth()) for frame in FRAMES: obj = frame() self.option_stack_widget.addWidget(obj) self.log_widget = QWidget() self.log_widget.setMaximumHeight(200) self.log_widget.setContentsMargins(0, 0, 0, 0) size_policy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) size_policy.setHorizontalStretch(0) size_policy.setVerticalStretch(1) size_policy.setHeightForWidth( self.log_widget.sizePolicy().hasHeightForWidth()) log_layout = QVBoxLayout() log_layout.setContentsMargins(0, 0, 0, 0) self.log_widget.setLayout(log_layout) log_label = QLabel('日志记录') log_label.setContentsMargins(0, 0, 0, 0) log_label.setAlignment(Qt.AlignCenter) self.log_text = QTextBrowser() self.log_text.setContentsMargins(0, 0, 0, 0) self.log_text.setAlignment(Qt.AlignLeft) log_layout.addWidget(log_label) log_layout.addWidget(self.log_text) self.right_layout.addWidget(self.option_stack_widget) self.right_layout.addWidget(self.log_widget) @pyqtSlot(str) def append_text(self, text): self.log_text.append(text) def read_log(self): while True: while has_log(): msg = read_log() self.log_text_signal.emit(msg) time.sleep(0.05) time.sleep(0.1) def exit(self): self.tray_icon = None os._exit(0) def init_tray(self): """ 初始化系统托盘 :return: """ self.tray_icon = QSystemTrayIcon(self) self.tray_icon.setIcon(QIcon(TRAY_ICON)) show_action = QAction("显示窗口", self) quit_action = QAction("退出程序", self) hide_action = QAction("隐藏窗口", self) show_action.triggered.connect(self.show) hide_action.triggered.connect(self.hide) quit_action.triggered.connect(self.exit) 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): """ 重写右上角X操作的事件 :param event: :return: """ event.ignore() self.hide() self.tray_icon.showMessage("虾皮助手", "应用已收起至托盘!!!", QSystemTrayIcon.Information, 2000)
class SystemTray(QDialog): tray_alert_signal = pyqtSignal((str,)) def __init__(self): super().__init__() self.create_actions() self.create_tray_icon() self.containing_folder = "" def create_actions(self): # menu items self.open_osf_folder_action = QAction("Open OSF Folder", self) self.launch_osf_action = QAction("Launch OSF", self) self.sync_now_action = QAction("Sync Now", self) self.currently_synching_action = QAction("Up to date", self) self.currently_synching_action.setDisabled(True) self.preferences_action = QAction("Settings", self) self.about_action = QAction("&About", self) self.quit_action = QAction("&Quit", self) def create_tray_icon(self): self.tray_icon_menu = QMenu(self) self.tray_icon_menu.addAction(self.open_osf_folder_action) self.tray_icon_menu.addAction(self.launch_osf_action) self.tray_icon_menu.addSeparator() self.tray_icon_menu.addAction(self.sync_now_action) self.tray_icon_menu.addAction(self.currently_synching_action) self.tray_icon_menu.addSeparator() self.tray_icon_menu.addAction(self.preferences_action) self.tray_icon_menu.addAction(self.about_action) self.tray_icon_menu.addSeparator() self.tray_icon_menu.addAction(self.quit_action) self.tray_icon = QSystemTrayIcon(self) self.tray_icon.setContextMenu(self.tray_icon_menu) # todo: do we have a better icon for use with desktop apps? icon = QIcon(":/cos_logo_backup.png") self.tray_icon.setIcon(icon) def start(self): self.tray_icon.show() def update_currently_synching(self, val): self.currently_synching_action.setText(str(val)) self.tray_icon.show() def set_containing_folder(self, new_containing_folder): logging.debug("setting new containing folder is :{}".format(self.containing_folder)) self.containing_folder = new_containing_folder def start_osf(self): url = "http://osf.io" webbrowser.open_new_tab(url) def open_osf_folder(self): # fixme: containing folder not being updated. import logging logging.debug("containing folder is :{}".format(self.containing_folder)) if validate_containing_folder(self.containing_folder): if sys.platform == "win32": os.startfile(self.containing_folder) elif sys.platform == "darwin": subprocess.Popen(["open", self.containing_folder]) else: try: subprocess.Popen(["xdg-open", self.containing_folder]) except OSError: raise NotImplementedError else: AlertHandler.warn("osf folder is not set")
class Sensei(QMainWindow): def __init__(self): super().__init__() self.initUI() print("meta:", USER_ID, SESSION_ID) self.history = {} self.history[USER_ID] = {} self.history[USER_ID][SESSION_ID] = {} self.history[USER_ID][SESSION_ID][datetime.datetime.now().strftime( '%Y-%m-%d_%H-%M-%S')] = "sensitivity: " + str(SENSITIVITY_FORWARD) # Create the worker Thread # TODO: See if any advantage to using thread or if timer alone works. # TODO: Compare to workerThread example at # https://stackoverflow.com/questions/31945125/pyqt5-qthread-passing-variables-to-main self.capture = Capture(self) self.timer = QTimer(self, timeout=self.calibrate) self.mode = 0 # 0: Initial, 1: Calibrate, 2: Monitor self.checkDependencies() def checkDependencies(self): global TERMINAL_NOTIFIER_INSTALLED if 'darwin' in sys.platform and not TERMINAL_NOTIFIER_INSTALLED: # FIXME: Add check for Brew installation and installation of # terminal-notifier. self.instructions.setText( 'Installing terminal-notifier dependency') print('Installing terminal-notifier is required') # FIXME: This line hangs. # subprocess.call( # ['brew', 'install', 'terminal-notifier'], stdout=subprocess.PIPE) # TERMINAL_NOTIFIER_INSTALLED = True self.instructions.setText('Sit upright and click \'Calibrate\'') def aboutEvent(self, event): dialog = QDialog() aboutDialog = Ui_AboutWindow() aboutDialog.setupUi(dialog) aboutDialog.githubButton.clicked.connect(self.openGitHub) dialog.exec_() self.trayIcon.showMessage("Posture Monitor 🙇", "Bad Posture", QSystemTrayIcon.Information, 4000) def closeEvent(self, event): """ Override QWidget close event to save history on exit. """ # TODO: Replace with CSV method. # if os.path.exists('posture.dat'): # with open('posture.dat','rb') as saved_history: # history =pickle.load(saved_history) if self.history: if hasattr(sys, "_MEIPASS"): # PyInstaller deployed here = os.path.join(sys._MEIPASS) else: here = os.path.dirname(os.path.realpath(__file__)) directory = os.path.join(here, 'data', str(USER_ID)) # directory = os.path.join(here, 'data', 'test') if not os.path.exists(directory): os.makedirs(directory) with open(os.path.join(directory, str(SESSION_ID) + '.dat'), 'wb') as f: pickle.dump(self.history, f) print(NOTIFICATION_COUNT) qApp.quit() def pauseEvent(self, event): pass def start(self): self.timer.start() def stop(self): self.timer.stop() def initUI(self): menu = QMenu() # Use Buddha in place of smiley face # iconPath = pyInstallerResourcePath('exit-gray.png') iconPath = pyInstallerResourcePath('meditate.png') self.trayIcon = QSystemTrayIcon(self) supported = self.trayIcon.supportsMessages() self.trayIcon.setIcon(QIcon(iconPath)) self.trayIcon.setContextMenu(menu) self.trayIcon.showMessage('a', 'b') self.trayIcon.show() self.postureIcon = QSystemTrayIcon(self) self.postureIcon.setIcon(QIcon(pyInstallerResourcePath('posture.png'))) self.postureIcon.setContextMenu(menu) self.postureIcon.show() exitAction = QAction("&Quit Sensei", self, shortcut="Ctrl+Q", triggered=self.closeEvent) preferencesAction = QAction("&Preferences...", self, triggered=self.showApp) # preferencesAction.setStatusTip('Sensei Preferences') aboutAction = QAction("&About Sensei", self, triggered=self.aboutEvent) menu.addAction(aboutAction) menu.addSeparator() menu.addAction(preferencesAction) menu.addSeparator() optionsMenu = menu.addMenu('&Options') pauseToggleAction = QAction("Pause", self, triggered=self.togglePause) self.pauseButton = pauseToggleAction optionsMenu.addAction(pauseToggleAction) soundToggleAction = QAction("Sound Off", self, triggered=self.toggleSound) self.soundButton = soundToggleAction optionsMenu.addAction(soundToggleAction) menu.addAction(exitAction) # TODO: Add settings panel. # changeSettings = QAction(QIcon('exit.png'), "&Settings", self, shortcut="Cmd+,", triggered=self.changeSettings) # changeSettings.setStatusTip('Change Settings') # menu.addAction(changeSettings) self.pbar = QProgressBar(self) self.pbar.setGeometry(30, 40, 200, 25) self.pbarValue = 0 self.setGeometry(300, 300, 290, 150) self.setWindowTitle('Posture Monitor') self.startButton = QPushButton('Calibrate', self) self.startButton.move(30, 60) self.startButton.clicked.connect(self.calibrate) self.stopButton = QPushButton('Stop', self) self.stopButton.move(30, 60) self.stopButton.clicked.connect(self.endCalibration) self.stopButton.hide() self.settingsButton = QPushButton('Settings', self) self.settingsButton.move(140, 60) self.settingsButton.clicked.connect(self.settings) self.doneButton = QPushButton('Done', self) self.doneButton.move(30, 60) self.doneButton.hide() self.doneButton.clicked.connect(self.minimize) # TODO: Create QWidget panel for Settings with MONITOR_DELAY # and SENSITIVITY options. # layout = QFormLayout() # self.le = QLineEdit() self.instructions = QLabel(self) self.instructions.move(40, 20) self.instructions.setText('Sit upright and click \'Calibrate\'') self.instructions.setGeometry(40, 20, 230, 25) if not supported: self.instructions.setText( 'Error: Notification is not available on your system.') self.show() def toggleSound(self): global soundOn # FIXME: Replace with preferences dictionary or similar soundOn = not soundOn if soundOn: self.soundButton.setText("Sound Off") else: self.soundButton.setText("Sound On") # print("Sound is: {}".format(soundOn)) def togglePause(self): global pauseOn # FIXME: Replace with preferences dictionary or similar pauseOn = not pauseOn if pauseOn: self.pause() else: self.unpause() def pause(self): self.timer.stop() self.pauseButton.setText("Resume") def unpause(self): self.timer.start(MONITOR_DELAY) self.pauseButton.setText("Pause") def minimize(self): self.reset() self.hide() def reset(self): pass def showApp(self): self.show() self.raise_() self.doneButton.hide() self.startButton.show() self.pbar.show() self.settingsButton.show() self.activateWindow() def settings(self): global MONITOR_DELAY seconds, ok = QInputDialog.getInt( self, "Delay Settings", "Enter number of seconds to check posture\n(Default = 2)") if ok: seconds = seconds if seconds >= 1 else 0.5 MONITOR_DELAY = seconds * 1000 def endCalibration(self): self.mode = 2 # Monitor mode self.timer.stop() self.stopButton.hide() self.startButton.setText('Recalibrate') # Keep hidden. self.instructions.setText('Sit upright and click \'Recalibrate\'') self.instructions.hide() self.pbar.hide() self.settingsButton.hide() self.history[USER_ID][SESSION_ID][datetime.datetime.now().strftime( '%Y-%m-%d_%H-%M-%S')] = "baseline: " + str(self.upright) self.animateClosing() # Begin monitoring posture. self.timer = QTimer(self, timeout=self.monitor) self.timer.start(MONITOR_DELAY) def animateClosing(self): self.doneButton.show() animation = QPropertyAnimation(self.doneButton, b"geometry") animation.setDuration(1000) animation.setStartValue(QRect(10, 60, 39, 20)) animation.setEndValue(QRect(120, 60, 39, 20)) animation.start() self.animation = animation def monitor(self): """ Grab the picture, find the face, and sent notification if needed. """ global LastFaceFoundTime photo = self.capture.takePhoto() face = getFace(photo) while face is None: print("No faces detected for {} seconds.".format( round(time.time() - LastFaceFoundTime)), flush=True) time.sleep(2) photo = self.capture.takePhoto() face = getFace(photo) LastFaceFoundTime = time.time() # Record history for later analyis. # TODO: Make this into cvs-friendly format. self.history[USER_ID][SESSION_ID][datetime.datetime.now().strftime( '%Y-%m-%d_%H-%M-%S')] = face x, y, w, h = face if (w > self.upright * SENSITIVITY_FORWARD) or ( w < self.upright * SENSITIVITY_BACK) or ( y > self.height * SENSITIVITY_HEIGHT): print('{}{}{}'.format( 'F' if w > self.upright * SENSITIVITY_FORWARD else 'f', 'B' if w < (self.upright * SENSITIVITY_BACK) else 'b', 'H' if y > (self.height * SENSITIVITY_HEIGHT) else 'h'), flush=True) global LastNotificationTime if time.time( ) - LastNotificationTime < MINIMUM_SECONDS_BETWEEN_NOTIFICATIONS: return LastNotificationTime = time.time() message = 'Bad Posture, please sit up straight' if w > self.upright * SENSITIVITY_FORWARD: message = 'You\'re sitting too far forward, Please sit up straight.' if w < self.upright * SENSITIVITY_BACK: message = 'You\'re sitting too far back, Please sit up straight.' if y > self.height * SENSITIVITY_HEIGHT: message = 'You\'re slumping, Please sit up straight.' self.notify( title='Posture Monitor 🙇', # TODO: Add doctor emoji `👨⚕️` subtitle='Alert!', message=message, appIcon=APP_ICON_PATH) else: if time.time() - LastNotificationTime > REMINDER_INTERVAL_SECONDS: # No notifications lately - post reminder LastNotificationTime = time.time() self.notify( title='Posture Monitor �', # TODO: Add doctor emoji `�⚕️` subtitle='Reminder!', message='Check your posture.', appIcon=APP_ICON_PATH) def notify(self, title, subtitle, message, sound='default', appIcon=None): """ Mac-only and requires `terminal-notifier` to be installed. # TODO: Add check that terminal-notifier is installed. # TODO: Add Linux and windows compatibility. # TODO: Linux example: # TODO: sudo apt-get install libnotify-bin # TODO: from gi.repository import Notify # TODO: Notify.init("App Name") # TODO: Notify.Notification.new("Hi").show() """ # FIXME: Test following line on windows / linux. # Doesn't work on Mac and might replace `terminal-notifier` dependency # self.trayIcon.showMessage('Title', 'Content') global NOTIFICATION_COUNT global soundOn if 'darwin' in sys.platform and TERMINAL_NOTIFIER_INSTALLED: # Check if on a Mac. t = '-title {!r}'.format(title) s = '-subtitle {!r}'.format(subtitle) m = '-message {!r}'.format(message) snd = '-sound {!r}'.format(sound) i = '-appIcon {!r}'.format(appIcon) if soundOn: os.system('terminal-notifier {}'.format(' '.join( [m, t, s, snd, i]))) else: os.system('terminal-notifier {}'.format(' '.join([m, t, s, i]))) else: self.trayIcon.showMessage( "Posture Monitor 🙇", "Bad posture detected, please sit up striaght.", QSystemTrayIcon.Information, 4000) if soundOn: print("Playing sound", flush=True) player.play() NOTIFICATION_COUNT += 1 def calibrate(self): if self.mode == 2: # Came from 'Recalibrate' # Set up for calibrate mode. self.mode = 1 self.stopButton.show() self.startButton.hide() self.instructions.setText('Press \'stop\' when ready') self.timer.stop() self.timer = QTimer(self, timeout=self.calibrate) self.timer.start(CALIBRATION_SAMPLE_RATE) # Interpolate posture information from face. photo = self.capture.takePhoto() face = getFace(photo) while face is None: print("No faces detected.", flush=True) time.sleep(2) photo = self.capture.takePhoto() face = getFace(photo) # TODO: Focus on user's face rather than artifacts of face detector of others # on camera # if len(faces) > 1: # print(faces) # Take argmax of faces x, y, w, h = face self.upright = w self.height = y self.history[USER_ID][SESSION_ID][ datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S') + ': calibration'] = self.upright self.history["upright_face_width"] = self.upright if self.mode == 0: # Initial mode self.timer.start(CALIBRATION_SAMPLE_RATE) self.startButton.hide() self.stopButton.show() self.instructions.setText('Press \'stop\' when ready') self.mode = 1 # Calibrate mode elif self.mode == 1: # Update posture monitor bar. self.pbar.setValue(self.upright / 4) time.sleep(0.05) def openGitHub(self): import webbrowser webbrowser.open_new_tab('https://github.com/JustinShenk/sensei')
class MainWidget(QTabWidget): """Custom main widget.""" def __init__(self, parent=None, *args, **kwargs): """Init class custom tab widget.""" super(MainWidget, self).__init__(parent=None, *args, **kwargs) self.parent = parent self.setTabBar(TabBar(self)) self.setMovable(False) self.setTabsClosable(False) self.setTabShape(QTabWidget.Triangular) self.init_preview() self.init_corner_menus() self.init_tray() self.addTab(TabSearch(self), "Search") self.addTab(TabTool(self), "Tools") self.addTab(TabHtml(self), "HTML") self.addTab(TabSymbols(self), "Symbols") self._recent_tab = TabRecent(self) self.recentify = self._recent_tab.recentify # shortcut self.addTab(self._recent_tab, "Recent") self.widgets_to_tabs(self.json_to_widgets(UNICODEMOTICONS)) self.make_trayicon() self.setMinimumSize(QDesktopWidget().screenGeometry().width() // 1.5, QDesktopWidget().screenGeometry().height() // 1.5) # self.showMaximized() def init_preview(self): self.previews, self.timer = [], QTimer(self) self.fader, self.previous_pic = FaderWidget(self), None self.timer.setSingleShot(True) self.timer.timeout.connect(lambda: [_.close() for _ in self.previews]) self.taimer, self.preview = QTimer(self), QLabel("Preview") self.taimer.setSingleShot(True) self.taimer.timeout.connect(lambda: self.preview.hide()) font = self.preview.font() font.setPixelSize(100) self.preview.setFont(font) self.preview.setDisabled(True) self.preview.setWindowFlags(Qt.FramelessWindowHint | Qt.Tool) self.preview.setAttribute(Qt.WA_TranslucentBackground, True) def init_corner_menus(self): self.menu_1, self.menu_0 = QToolButton(self), QToolButton(self) self.menu_1.setText(" ⚙ ") self.menu_1.setToolTip("<b>Options, Extras") self.menu_0.setText(" ? ") self.menu_0.setToolTip("<b>Help, Info") font = self.menu_1.font() font.setBold(True) self.menu_1.setFont(font) self.menu_0.setFont(font) self.menu_tool, self.menu_help = QMenu("Tools Extras"), QMenu("Help") self.menu_tool.addAction(" Tools & Extras ").setDisabled(True) self.menu_help.addAction(" Help & Info ").setDisabled(True) self.menu_0.setMenu(self.menu_help) self.menu_1.setMenu(self.menu_tool) self.menu_tool.addAction("Explain Unicode", self.make_explain_unicode) self.menu_tool.addAction("Alternate Case Clipboard", self.alternate_clipboard) self.menu_tool.addSeparator() self.menu_tool.addAction("AutoCenter Window", self.center) self.menu_tool.addAction("Set Icon", self.set_icon) self.menu_tool.addAction( # force recreate desktop file "Add Launcher to Desktop", lambda: set_desktop_launcher( "unicodemoticon", AUTOSTART_DESKTOP_FILE, True)) self.menu_tool.addAction("Move to Mouse position", self.move_to_mouse_position) self.menu_tool.addSeparator() self.menu_tool.addAction("Minimize", self.showMinimized) self.menu_tool.addAction("Hide", self.hide) self.menu_tool.addAction("Quit", exit) self.menu_help.addAction("About Qt 5", lambda: QMessageBox.aboutQt(None)) self.menu_help.addAction("About Unicodemoticon", lambda: open_new_tab(__url__)) self.setCornerWidget(self.menu_1, 1) self.setCornerWidget(self.menu_0, 0) self.currentChanged.connect(self.make_tabs_previews) self.currentChanged.connect(self.make_tabs_fade) def init_tray(self): self.tray, self.menu = QSystemTrayIcon(self), QMenu(__doc__) self.menu.addAction(" Emoticons").setDisabled(True) self.menu.setIcon(self.windowIcon()) self.menu.addSeparator() self.menu.setProperty("emoji_menu", True) list_of_labels = sorted(UNICODEMOTICONS.keys()) # menus menus = [self.menu.addMenu(_.title()) for _ in list_of_labels] self.menu.addSeparator() log.debug("Building Emoticons SubMenus.") for item, label in zip(menus, list_of_labels): item.setStyleSheet("padding:0;margin:0;border:0;menu-scrollable:1") font = item.font() font.setPixelSize(20) item.setFont(font) self.build_submenu(UNICODEMOTICONS[label.lower()], item) self.menu.addSeparator() self.menu.addAction("Alternate Case Clipboard", self.alternate_clipboard) self.menu.addSeparator() self.menu.addAction("Quit", exit) self.menu.addAction("Show", self.showMaximized) self.menu.addAction("Minimize", self.showMinimized) self.tray.setContextMenu(self.menu) def build_submenu(self, char_list: (str, tuple), submenu: QMenu) -> QMenu: """Take a list of characters and a submenu and build actions on it.""" submenu.setProperty("emoji_menu", True) submenu.setWindowOpacity(0.9) submenu.setToolTipsVisible(True) for _char in sorted(char_list): action = submenu.addAction(_char.strip()) action.setToolTip(self.get_description(_char)) action.hovered.connect(lambda _, ch=_char: self.make_preview(ch)) action.triggered.connect( lambda _, char=_char: QApplication.clipboard().setText(char)) return submenu def make_trayicon(self): """Make a Tray Icon.""" if self.windowIcon() and __doc__: self.tray.setIcon(self.windowIcon()) self.tray.setToolTip(__doc__) self.tray.activated.connect( lambda: self.hide() if self.isVisible() else self.showMaximized()) return self.tray.show() def make_explain_unicode(self) -> tuple: """Make an explanation from unicode entered,if at least 1 chars.""" explanation, uni = "", None uni = str(QInputDialog.getText( None, __doc__, "<b>Type Unicode character to explain?")[0]).strip() if uni and len(uni): explanation = ", ".join([self.get_description(_) for _ in uni]) QMessageBox.information(None, __doc__, str((uni, explanation))) log.debug((uni, explanation)) return (uni, explanation) def alternate_clipboard(self) -> str: """Make alternating camelcase clipboard.""" return QApplication.clipboard().setText( self.make_alternate_case(str(QApplication.clipboard().text()))) def make_alternate_case(self, stringy: str) -> str: """Make alternating camelcase string.""" return "".join([_.lower() if i % 2 else _.upper() for i, _ in enumerate(stringy)]) def get_description(self, emote: str): description = "" try: description = unicodedata.name(str(emote).strip()).title() except ValueError: log.debug("Description not found for Unicode: " + emote) finally: return description def make_preview(self, emoticon_text: str): """Make Emoticon Previews for the current Hovered one.""" log.debug(emoticon_text) if self.taimer.isActive(): # Be Race Condition Safe self.taimer.stop() self.preview.setText(" " + emoticon_text + " ") self.preview.move(QCursor.pos()) self.preview.show() self.taimer.start(1000) # how many time display the previews def json_to_widgets(self, jotason: dict): """Take a json string object return QWidgets.""" dict_of_widgets, row = {}, 0 for titlemotes in tuple(sorted(jotason.items())): tit = str(titlemotes[0]).strip()[:9].title() area = ScrollGroup(tit) layout = area.layout() grid_cols = 2 if tit.lower() == "multichar" else 8 for index, emote in enumerate(tuple(set(list(titlemotes[1])))): button = QPushButton(emote, self) button.clicked.connect(lambda _, c=emote: QApplication.clipboard().setText(c)) button.released.connect(self.hide) button.released.connect(lambda c=emote: self.recentify(c)) button.pressed.connect(lambda c=emote: self.make_preview(c)) button.setToolTip("<center><h1>{0}<br>{1}".format( emote, self.get_description(emote))) button.setFlat(True) font = button.font() font.setPixelSize(50) button.setFont(font) row = row + 1 if not index % grid_cols else row layout.addWidget(button, row, index % grid_cols) dict_of_widgets[tit] = area return dict_of_widgets def widgets_to_tabs(self, dict_of_widgets: dict): """Take a dict of widgets and build tabs from them.""" for title, widget in tuple(sorted(dict_of_widgets.items())): self.addTab(widget, title) def center(self): """Center Window on the Current Screen,with Multi-Monitor support.""" self.showNormal() self.resize(QDesktopWidget().screenGeometry().width() // 1.5, QDesktopWidget().screenGeometry().height() // 1.5) window_geometry = self.frameGeometry() mousepointer_position = QApplication.desktop().cursor().pos() screen = QApplication.desktop().screenNumber(mousepointer_position) centerPoint = QApplication.desktop().screenGeometry(screen).center() window_geometry.moveCenter(centerPoint) return bool(not self.move(window_geometry.topLeft())) def move_to_mouse_position(self): """Center the Window on the Current Mouse position.""" self.showNormal() self.resize(QDesktopWidget().screenGeometry().width() // 1.5, QDesktopWidget().screenGeometry().height() // 1.5) window_geometry = self.frameGeometry() window_geometry.moveCenter(QApplication.desktop().cursor().pos()) return bool(not self.move(window_geometry.topLeft())) def set_icon(self, icon: (None, str)=None) -> str: """Return a string with opendesktop standard icon name for Qt.""" if not icon: try: cur_idx = STD_ICON_NAMES.index(self.windowIcon().name()) except ValueError: cur_idx = 0 icon = QInputDialog.getItem(None, __doc__, "<b>Choose Icon name?:", STD_ICON_NAMES, cur_idx, False)[0] if icon: log.debug("Setting Tray and Window Icon name to:{}.".format(icon)) self.tray.setIcon(QIcon.fromTheme("{}".format(icon))) self.setWindowIcon(QIcon.fromTheme("{}".format(icon))) return icon def make_tabs_fade(self, index): """Make tabs fading transitions.""" self.fader.fade( self.previous_pic, self.widget(index).geometry(), 1 if self.tabPosition() else self.tabBar().tabRect(0).height()) self.previous_pic = self.currentWidget().grab() def make_undock(self): """Undock a Tab from TabWidget and promote to a Dialog.""" dialog, index = QDialog(self), self.currentIndex() widget_from_tab = self.widget(index) dialog_layout = QVBoxLayout(dialog) dialog.setWindowTitle(self.tabText(index)) dialog.setToolTip(self.tabToolTip(index)) dialog.setWhatsThis(self.tabWhatsThis(index)) dialog.setWindowIcon(self.tabIcon(index)) dialog.setFont(widget_from_tab.font()) dialog.setStyleSheet(widget_from_tab.styleSheet()) dialog.setMinimumSize(widget_from_tab.minimumSize()) dialog.setMaximumSize(widget_from_tab.maximumSize()) dialog.setGeometry(widget_from_tab.geometry()) def closeEvent_override(event): """Re-dock back from Dialog to a new Tab.""" msg = "<b>Close this Floating Tab Window and Re-Dock as a new Tab?" conditional = QMessageBox.question( self, "Undocked Tab", msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes if conditional: index_plus_1 = self.count() + 1 self.insertTab(index_plus_1, widget_from_tab, dialog.windowIcon(), dialog.windowTitle()) self.setTabToolTip(index_plus_1, dialog.toolTip()) self.setTabWhatsThis(index_plus_1, dialog.whatsThis()) return event.accept() else: return event.ignore() dialog.closeEvent = closeEvent_override self.removeTab(index) widget_from_tab.setParent(self.parent if self.parent else dialog) dialog_layout.addWidget(widget_from_tab) dialog.setLayout(dialog_layout) widget_from_tab.show() dialog.show() # exec_() for modal dialog, show() for non-modal dialog dialog.move(QCursor.pos()) def make_tabs_previews(self, index): """Make Tabs Previews for all tabs except current, if > 3 Tabs.""" if self.count() < 4 or not self.tabBar().tab_previews: return False # At least 4Tabs to use preview,and should be Enabled if self.timer.isActive(): # Be Race Condition Safe self.timer.stop() for old_widget in self.previews: old_widget.close() # Visually Hide the Previews closing it old_widget.setParent(None) # Orphan the old previews old_widget.destroy() # Destroy to Free Resources self.previews = [QLabel(self) for i in range(self.count())] # New Ones y_pos = self.size().height() - self.tabBar().tabRect(0).size().height() for i, widget in enumerate(self.previews): # Iterate,set QPixmaps,Show if i != index: # Dont make a pointless preview for the current Tab widget.setScaledContents(True) # Auto-Scale QPixmap contents tabwidth = self.tabBar().tabRect(i).size().width() tabwidth = 200 if tabwidth > 200 else tabwidth # Limit sizes widget.setPixmap(self.widget(i).grab().scaledToWidth(tabwidth)) widget.resize(tabwidth - 1, tabwidth) if self.tabPosition(): # Move based on Top / Bottom positions widget.move(self.tabBar().tabRect(i).left() * 1.1, y_pos - tabwidth - 3) else: widget.move(self.tabBar().tabRect(i).bottomLeft() * 1.1) widget.show() self.timer.start(1000) # how many time display the previews return True
class SVApplication(QApplication): # Signals need to be on a QObject create_new_window_signal = pyqtSignal(str, object) cosigner_received_signal = pyqtSignal(object, object) labels_changed_signal = pyqtSignal(object, object, object) window_opened_signal = pyqtSignal(object) window_closed_signal = pyqtSignal(object) # Async tasks async_tasks_done = pyqtSignal() # Logging new_category = pyqtSignal(str) new_log = pyqtSignal(object) # Preferences updates fiat_ccy_changed = pyqtSignal() custom_fee_changed = pyqtSignal() op_return_enabled_changed = pyqtSignal() num_zeros_changed = pyqtSignal() base_unit_changed = pyqtSignal() fiat_history_changed = pyqtSignal() fiat_balance_changed = pyqtSignal() update_check_signal = pyqtSignal(bool, object) # Contact events contact_added_signal = pyqtSignal(object, object) contact_removed_signal = pyqtSignal(object) identity_added_signal = pyqtSignal(object, object) identity_removed_signal = pyqtSignal(object, object) new_notification = pyqtSignal(object, object) def __init__(self, argv): QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads) if hasattr(QtCore.Qt, "AA_ShareOpenGLContexts"): QtCore.QCoreApplication.setAttribute( QtCore.Qt.AA_ShareOpenGLContexts) if hasattr(QGuiApplication, 'setDesktopFileName'): QGuiApplication.setDesktopFileName('electrum-sv.desktop') super().__init__(argv) self.windows = [] self.log_handler = SVLogHandler() self.log_window = None self.net_dialog = None self.timer = QTimer() self.exception_hook = None # A floating point number, e.g. 129.1 self.dpi = self.primaryScreen().physicalDotsPerInch() # init tray self.dark_icon = app_state.config.get("dark_icon", False) self.tray = QSystemTrayIcon(self._tray_icon(), None) self.tray.setToolTip('ElectrumSV') self.tray.activated.connect(self._tray_activated) self._build_tray_menu() self.tray.show() # FIXME Fix what.. what needs to be fixed here? set_language(app_state.config.get('language', get_default_language())) logs.add_handler(self.log_handler) self._start() def _start(self): self.setWindowIcon(read_QIcon("electrum-sv.png")) self.installEventFilter(OpenFileEventFilter(self.windows)) self.create_new_window_signal.connect(self.start_new_window) self.async_tasks_done.connect(app_state.async_.run_pending_callbacks) self.num_zeros_changed.connect( partial(self._signal_all, 'on_num_zeros_changed')) self.fiat_ccy_changed.connect( partial(self._signal_all, 'on_fiat_ccy_changed')) self.base_unit_changed.connect( partial(self._signal_all, 'on_base_unit_changed')) self.fiat_history_changed.connect( partial(self._signal_all, 'on_fiat_history_changed')) # Toggling of showing addresses in the fiat preferences. self.fiat_balance_changed.connect( partial(self._signal_all, 'on_fiat_balance_changed')) self.update_check_signal.connect( partial(self._signal_all, 'on_update_check')) ColorScheme.update_from_widget(QWidget()) def _signal_all(self, method, *args): for window in self.windows: getattr(window, method)(*args) def _close(self): for window in self.windows: window.close() def close_window(self, window): app_state.daemon.stop_wallet_at_path(window._wallet.get_storage_path()) self.windows.remove(window) self.window_closed_signal.emit(window) self._build_tray_menu() if not self.windows: self._last_window_closed() def setup_app(self): # app_state.daemon is initialised after app. Setup things dependent on daemon here. pass def _build_tray_menu(self): # Avoid immediate GC of old menu when window closed via its action if self.tray.contextMenu() is None: m = QMenu() self.tray.setContextMenu(m) else: m = self.tray.contextMenu() m.clear() for window in self.windows: submenu = m.addMenu(window._wallet.name()) submenu.addAction(_("Show/Hide"), window.show_or_hide) submenu.addAction(_("Close"), window.close) m.addAction(_("Dark/Light"), self._toggle_tray_icon) m.addSeparator() m.addAction(_("Exit ElectrumSV"), self._close) self.tray.setContextMenu(m) def _tray_icon(self): if self.dark_icon: return read_QIcon('electrumsv_dark_icon.png') else: return read_QIcon('electrumsv_light_icon.png') def _toggle_tray_icon(self): self.dark_icon = not self.dark_icon app_state.config.set_key("dark_icon", self.dark_icon, True) self.tray.setIcon(self._tray_icon()) def _tray_activated(self, reason): if reason == QSystemTrayIcon.DoubleClick: if all([w.is_hidden() for w in self.windows]): for w in self.windows: w.bring_to_top() else: for w in self.windows: w.hide() def new_window(self, path: Optional[str], uri: Optional[str] = None) -> None: # Use a signal as can be called from daemon thread self.create_new_window_signal.emit(path, uri) def show_network_dialog(self, parent) -> None: if not app_state.daemon.network: parent.show_warning(_( 'You are using ElectrumSV in offline mode; restart ' 'ElectrumSV if you want to get connected'), title=_('Offline')) return if self.net_dialog: self.net_dialog.on_update() self.net_dialog.show() self.net_dialog.raise_() return from . import network_dialog # from importlib import reload # reload(network_dialog) self.net_dialog = network_dialog.NetworkDialog( app_state.daemon.network, app_state.config) self.net_dialog.show() def show_log_viewer(self) -> None: if self.log_window is None: self.log_window = SVLogWindow(None, self.log_handler) self.log_window.show() def _last_window_closed(self): for dialog in (self.net_dialog, self.log_window): if dialog: dialog.accept() def on_transaction_label_change(self, account: AbstractAccount, tx_hash: bytes, text: str) -> None: self.label_sync.set_transaction_label(account, tx_hash, text) def on_keyinstance_label_change(self, account: AbstractAccount, key_id: int, text: str) -> None: self.label_sync.set_keyinstance_label(account, key_id, text) def _create_window_for_wallet(self, wallet: Wallet): w = ElectrumWindow(wallet) self.windows.append(w) self._build_tray_menu() self._register_wallet_events(wallet) self.window_opened_signal.emit(w) return w def _register_wallet_events(self, wallet: Wallet) -> None: wallet.contacts._on_contact_added = self._on_contact_added wallet.contacts._on_contact_removed = self._on_contact_removed wallet.contacts._on_identity_added = self._on_identity_added wallet.contacts._on_identity_removed = self._on_identity_removed def _on_identity_added(self, contact: ContactEntry, identity: ContactIdentity) -> None: self.identity_added_signal.emit(contact, identity) def _on_identity_removed(self, contact: ContactEntry, identity: ContactIdentity) -> None: self.identity_removed_signal.emit(contact, identity) def _on_contact_added(self, contact: ContactEntry, identity: ContactIdentity) -> None: self.contact_added_signal.emit(contact, identity) def _on_contact_removed(self, contact: ContactEntry) -> None: self.contact_removed_signal.emit(contact) def on_new_wallet_event(self, wallet_path: str, row: WalletEventRow) -> None: self.new_notification.emit(wallet_path, row) def get_wallet_window(self, path: str) -> Optional[ElectrumWindow]: for w in self.windows: if w._wallet.get_storage_path() == path: return w def get_wallet_window_by_id(self, account_id: int) -> Optional[ElectrumWindow]: for w in self.windows: for account in w._wallet.get_accounts(): if account.get_id() == account_id: return w def start_new_window(self, wallet_path: Optional[str], uri: Optional[str] = None, is_startup: bool = False) -> Optional[ElectrumWindow]: '''Raises the window for the wallet if it is open. Otherwise opens the wallet and creates a new window for it.''' for w in self.windows: if w._wallet.get_storage_path() == wallet_path: w.bring_to_top() break else: wizard_window: Optional[WalletWizard] = None if wallet_path is not None: is_valid, was_aborted, wizard_window = WalletWizard.attempt_open( wallet_path) if was_aborted: return None if not is_valid: wallet_filename = os.path.basename(wallet_path) MessageBox.show_error( _("Unable to load file '{}'.").format(wallet_filename)) return None else: wizard_window = WalletWizard(is_startup=is_startup) if wizard_window is not None: result = wizard_window.run() if result != QDialog.Accepted: return None wallet_path = wizard_window.get_wallet_path() # We cannot rely on accept alone indicating success. if wallet_path is None: return None wallet = app_state.daemon.load_wallet(wallet_path) assert wallet is not None w = self._create_window_for_wallet(wallet) if uri: w.pay_to_URI(uri) w.bring_to_top() w.setWindowState(w.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive) # this will activate the window w.activateWindow() return w def update_check(self) -> None: if (not app_state.config.get('check_updates', True) or app_state.config.get("offline", False)): return def f(): import requests try: response = requests.request( 'GET', "https://electrumsv.io/release.json", headers={'User-Agent': 'ElectrumSV'}, timeout=10) result = response.json() self._on_update_check(True, result) except Exception: self._on_update_check(False, sys.exc_info()) t = threading.Thread(target=f) t.setDaemon(True) t.start() def _on_update_check(self, success: bool, result: dict) -> None: if success: when_checked = datetime.datetime.now().astimezone().isoformat() app_state.config.set_key('last_update_check', result) app_state.config.set_key('last_update_check_time', when_checked, True) self.update_check_signal.emit(success, result) def initial_dialogs(self) -> None: '''Suppressible dialogs that are shown when first opening the app.''' dialogs.show_named('welcome-ESV-1.3.0') def event_loop_started(self) -> None: self.cosigner_pool = CosignerPool() self.label_sync = LabelSync() if app_state.config.get("show_crash_reporter", default=True): self.exception_hook = Exception_Hook(self) self.timer.start() signal.signal(signal.SIGINT, lambda *args: self.quit()) self.initial_dialogs() path = app_state.config.get_cmdline_wallet_filepath() if not self.start_new_window( path, app_state.config.get('url'), is_startup=True): self.quit() def run_app(self) -> None: when_started = datetime.datetime.now().astimezone().isoformat() app_state.config.set_key('previous_start_time', app_state.config.get("start_time")) app_state.config.set_key('start_time', when_started, True) self.update_check() threading.current_thread().setName('GUI') self.timer.setSingleShot(False) self.timer.setInterval(500) # msec self.timer.timeout.connect(app_state.device_manager.timeout_clients) QTimer.singleShot(0, self.event_loop_started) self.exec_() logs.remove_handler(self.log_handler) # Shut down the timer cleanly self.timer.stop() # clipboard persistence # see http://www.mail-archive.com/[email protected]/msg17328.html event = QtCore.QEvent(QtCore.QEvent.Clipboard) self.sendEvent(self.clipboard(), event) self.tray.hide() def run_coro(self, coro, *args, on_done=None): '''Run a coroutine. on_done, if given, is passed the future containing the reuslt or exception, and is guaranteed to be called in the context of the GUI thread. ''' def task_done(future): self.async_tasks_done.emit() future = app_state.async_.spawn(coro, *args, on_done=on_done) future.add_done_callback(task_done) return future def run_in_thread(self, func, *args, on_done=None): '''Run func(*args) in a thread. on_done, if given, is passed the future containing the reuslt or exception, and is guaranteed to be called in the context of the GUI thread. ''' return self.run_coro(run_in_thread, func, *args, on_done=on_done)
class MainWindow(QMainWindow): """The main GUI application.""" def __init__(self, config): """Initializer for the GUI widgets. Pass in an instance of Config class, so that it may interact with the config.""" super().__init__() self.config = config self.setWindowTitle("Livestreamer GUI v{}".format(APPVERSION)) self.setup_systray() self.setup_menu() self.setup_geometry() self.livestreamer_thread = None self.thread_exit_grace_time = 10000 # How long a thread can take to exit in milliseconds self.timestamp_format = self.config.get_config_value("timestamp-format") self.setup_control_widgets() self.update_colors() # Load all streaming-related data self.selections = {"streamer": None, "channel": None} self.load_streamers() self.load_channels(self.streamer_input.currentText()) # Do the first configuration, if the application was run for the first time self.do_init_config() # Finally show the window and the system tray icon, if it should be shown self.show() self.close_override = False self.show_hide_systray() self.check_and_do_database_migration() def do_init_config(self): do_config = self.config.get_config_value("is-configured") if do_config == 0: self.menu_cmd_configure() self.config.set_config_value("is-configured", 1) self.insertText("Using config database version '{}'".format(self.config.get_config_value("db-version"))) def setup_systray(self): if not self.config.get_config_value("enable-systray-icon"): self.systray = None return self.systray = QSystemTrayIcon(self) self.systray.activated.connect(self.systray_activated) main_menu = QMenu(self) quit_action = QAction("&Quit", self) quit_action.triggered.connect(self.on_close_override) main_menu.addAction(quit_action) self.systray.setContextMenu(main_menu) def systray_activated(self, reason): if reason == QSystemTrayIcon.Trigger: if self.isVisible(): self.hide() else: self.showNormal() def check_and_do_database_migration(self): current_version = self.config.get_config_value("db-version") if self.config.is_migration_needed(): self.insertText("Detected pending config database upgrade to version '{}'. Awaiting user input...".format(DBVERSION)) message = "You are using an older version of the application config database.\n\nWould you like to upgrade the database now? Your existing config database will be backed up." upgrade_is_mandatory = current_version < MANDATORY_DBVERSION if upgrade_is_mandatory: message = message + "\n\nWARNING: Your config database is not compatible with this version of Livestreamer GUI. UPDATE IS MANDATORY! If you cancel the update, the application will exit." reply = QMessageBox.question(self, "Pending config database upgrade", message, QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) if reply == QMessageBox.Yes: self.insertText("Backing up config database...") backup = self.config.make_database_backup() self.insertText("Current config database backed up to '{}'".format(backup)) self.insertText("Config database update initialized...") self.update() self.config.execute_migration() new_version = self.config.get_config_value("db-version") self.insertText("Config database update from version '{}' to '{}' finished.".format(current_version, new_version)) elif reply == QMessageBox.No and upgrade_is_mandatory: QtCore.QTimer.singleShot(500, self.on_close_override) # self.on_close_override() # Calling this in an __init__()-called method doesn't seem to work... else: self.insertText("Config database update cancelled. No changes were made.") def setup_menu(self): config_action = QAction("&Configure...", self) config_action.triggered.connect(self.menu_cmd_configure) quit_action = QAction("&Quit", self) quit_action.setShortcut("Ctrl+Q") quit_action.triggered.connect(self.on_close_override) menu = self.menuBar() file_menu = menu.addMenu("&File") file_menu.addAction(config_action) file_menu.addSeparator() file_menu.addAction(quit_action) def setup_geometry(self): width = self.config.get_config_value("root-width") height = self.config.get_config_value("root-height") topleft = QApplication.desktop().availableGeometry().topLeft() if self.config.get_config_value("remember-window-position"): xoffset = self.config.get_config_value("root-xoffset") yoffset = self.config.get_config_value("root-yoffset") topleft.setX(self.config.get_config_value("root-xoffset")) topleft.setY(self.config.get_config_value("root-yoffset")) self.resize(width, height) self.setMinimumSize(500, 300) self.move(topleft) # Center the window # center_point = QApplication.desktop().availableGeometry().center() # frame_geometry = self.frameGeometry() # frame_geometry.moveCenter(center_point) # self.move(frame_geometry.topLeft()) def setup_control_widgets(self): self.cwidget = QWidget(self) self.setCentralWidget(self.cwidget) layout = QGridLayout(self.cwidget) self.cwidget.setLayout(layout) fg_fav = self.config.get_config_value("button-foreground-favorite") fg_edit = self.config.get_config_value("button-foreground-edit") fg_add = self.config.get_config_value("button-foreground-add") fg_delete = self.config.get_config_value("button-foreground-delete") control_button_width = 30 control_button_font_style = "QPushButton { font-family: Arial, sans-serif; font-size: 16px }" column = 0 label_streamer_input = QLabel("Streamer", self.cwidget) layout.addWidget(label_streamer_input, 0, column) label_channel_input = QLabel("Channel", self.cwidget) layout.addWidget(label_channel_input, 1, column) label_quality_input = QLabel("Stream quality", self.cwidget) layout.addWidget(label_quality_input, 2, column) column += 1 self.streamer_input = QComboBox(self.cwidget) self.streamer_input.setEnabled(False) self.streamer_input.currentIndexChanged.connect(self.on_streamer_select) layout.addWidget(self.streamer_input, 0, column) self.channel_input = QComboBox(self.cwidget) self.channel_input.setEnabled(False) self.channel_input.currentIndexChanged.connect(self.on_channel_select) layout.addWidget(self.channel_input, 1, column) self.quality_input = QComboBox(self.cwidget) self.quality_input.addItem("(auto-refresh is disabled; please refresh manually)") self.quality_input.setEnabled(False) layout.addWidget(self.quality_input, 2, column) layout.setColumnStretch(column, 5) column += 1 self.fav_streamer_button = QPushButton("\u2764", self.cwidget) self.fav_streamer_button.setMaximumWidth(control_button_width) self.fav_streamer_button.setStyleSheet(":enabled {{ color: {0} }} {1}".format(fg_fav, control_button_font_style)) self.fav_streamer_button.setEnabled(False) self.fav_streamer_button.setToolTip("Set the selected streamer as your most favorite streamer") self.fav_streamer_button.clicked.connect(self.cmd_set_favorite_streamer) layout.addWidget(self.fav_streamer_button, 0, column) self.fav_channel_button = QPushButton("\u2764", self.cwidget) self.fav_channel_button.setMaximumWidth(control_button_width) self.fav_channel_button.setStyleSheet(':enabled {{ color: {0} }} {1}'.format(fg_fav, control_button_font_style)) self.fav_channel_button.setEnabled(False) self.fav_channel_button.setToolTip("Set the selected channel as your most favorite channel") self.fav_channel_button.clicked.connect(self.cmd_set_favorite_channel) layout.addWidget(self.fav_channel_button, 1, column) self.clear_quality_cache_button = QPushButton("Refresh streams", self.cwidget) self.clear_quality_cache_button.setEnabled(False) self.clear_quality_cache_button.clicked.connect(self.cmd_refresh_quality_cache) layout.addWidget(self.clear_quality_cache_button, 2, column, 1, 4) column += 1 self.edit_streamer_button = QPushButton("\u270E", self.cwidget) self.edit_streamer_button.setMaximumWidth(control_button_width) self.edit_streamer_button.setStyleSheet(":enabled {{ color: {0} }} {1}".format(fg_edit, control_button_font_style)) self.edit_streamer_button.setEnabled(False) self.edit_streamer_button.setToolTip("Edit data about the selected streamer") self.edit_streamer_button.clicked.connect(self.cmd_edit_streamer) layout.addWidget(self.edit_streamer_button, 0, column) self.edit_channel_button = QPushButton("\u270E", self.cwidget) self.edit_channel_button.setMaximumWidth(control_button_width) self.edit_channel_button.setStyleSheet(":enabled {{ color: {0} }} {1}".format(fg_edit, control_button_font_style)) self.edit_channel_button.setToolTip("Edit data about the selected channel") self.edit_channel_button.clicked.connect(self.cmd_edit_channel) layout.addWidget(self.edit_channel_button, 1, column) column += 1 self.add_streamer_button = QPushButton("\u271A", self.cwidget) self.add_streamer_button.setMaximumWidth(control_button_width) self.add_streamer_button.setStyleSheet(":enabled {{ color: {0} }} {1}".format(fg_add, control_button_font_style)) self.add_streamer_button.setEnabled(False) self.add_streamer_button.setToolTip("Add a new streamer") self.add_streamer_button.clicked.connect(self.cmd_add_streamer) layout.addWidget(self.add_streamer_button, 0, column) self.add_channel_button = QPushButton("\u271A", self.cwidget) self.add_channel_button.setMaximumWidth(control_button_width) self.add_channel_button.setStyleSheet(":enabled {{ color: {0} }} {1}".format(fg_add, control_button_font_style)) self.add_channel_button.setToolTip("Add a new channel") self.add_channel_button.clicked.connect(self.cmd_add_channel) layout.addWidget(self.add_channel_button, 1, column) column += 1 self.delete_streamer_button = QPushButton("\u2716", self.cwidget) self.delete_streamer_button.setMaximumWidth(control_button_width) self.delete_streamer_button.setStyleSheet(":enabled {{ color: {0} }} {1}".format(fg_delete, control_button_font_style)) self.delete_streamer_button.setEnabled(False) self.delete_streamer_button.setToolTip("Remove the selected streamer permanently") self.delete_streamer_button.clicked.connect(self.cmd_delete_streamer) layout.addWidget(self.delete_streamer_button, 0, column) self.delete_channel_button = QPushButton("\u2716", self.cwidget) self.delete_channel_button.setMaximumWidth(control_button_width) self.delete_channel_button.setStyleSheet(":enabled {{ color: {0} }} {1}".format(fg_delete, control_button_font_style)) self.delete_channel_button.setToolTip("Remove the selected channel permanently") self.delete_channel_button.clicked.connect(self.cmd_delete_channel) layout.addWidget(self.delete_channel_button, 1, column) # Add button for running livestreamer at the fourth row self.run_livestreamer_button = QPushButton("Run Livestreamer", self.cwidget) self.run_livestreamer_button.setEnabled(False) self.run_livestreamer_button.clicked.connect(self.run_livestreamer) layout.addWidget(self.run_livestreamer_button, 3, 0) self.log_widget = QTextEdit(self.cwidget) layout.addWidget(self.log_widget, 4, 0, 1, column+1) self.log_widget.setAcceptRichText(False) self.log_widget.setReadOnly(True) self.log_widget.setTabChangesFocus(True) def set_window_icon(self): """Sets the root window's icon, which is also shown in the taskbar.""" streamer = self.config.get_streamer(self.streamer_input.currentText()) icon = QIcon(os.path.join(IMAGESROOT, streamer["icon"])) self.setWindowIcon(icon) if self.systray is not None: self.systray.setIcon(icon) def closeEvent(self, event): """When the QWidget is closed, QCloseEvent is triggered, and this method catches and handles it.""" if not self.close_override and self.put_to_systray("close"): event.ignore() return if self.livestreamer_thread is not None and self.livestreamer_thread.keep_running: reply = QMessageBox.question(self, "Really quit Livestreamer GUI?", "Livestreamer is still running. Quitting will close it and the opened player.\n\nQuit?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: # Terminate the child process, else it'll keep running even after this application is closed if self.livestreamer_thread is not None: self.livestreamer_thread.term_process() self.livestreamer_thread.wait(self.thread_exit_grace_time) self.update() event.accept() else: event.ignore() # Explicitly hide the icon, if it remains visible after the application closes if self.systray is not None: self.systray.hide() # Remember the position of the window self.remember_window_position() event.accept() def changeEvent(self, event): if type(event) is not QWindowStateChangeEvent: return # It's one of the window state change events (normal, minimize, maximize, fullscreen, active) if self.isMinimized(): self.put_to_systray("minimize") def remember_window_position(self): if self.config.get_config_value("remember-window-position"): point = self.frameGeometry().topLeft() self.config.set_config_value("root-xoffset", point.x()) self.config.set_config_value("root-yoffset", point.y()) self.insertText("Window position saved.") def show_hide_systray(self): if self.systray is None: self.setup_systray() if self.systray is None: return if self.config.get_config_value("enable-systray-icon"): self.systray.show() else: self.systray.hide() def put_to_systray(self, event): if event == "minimize": config_value = "minimize-to-systray" elif event == "close": config_value = "close-to-systray" else: return False if self.systray is not None and self.config.get_config_value(config_value) and self.isVisible(): self.hide() return True return False def menu_cmd_configure(self): streamer = self.config.get_streamer(self.streamer_input.currentText()) dialog = AppConfigDialog(self, self.config, streamer_icon=os.path.join(IMAGESROOT, streamer["icon"])) dialog.exec() if dialog.result() == QDialog.Accepted: self.show_hide_systray() self.update_colors() dialog.close() dialog = None def cmd_set_favorite_streamer(self): raise NotImplementedException() # self.fav_streamer_button.setEnabled(False) # self.config.set_favorite_streamer(self.streamer_input.setCurrentText()) # self.insertText("Favorited streamer '{}'.".format(self.streamer_input.setCurrentText())) def cmd_edit_streamer(self): raise NotImplementedException() def cmd_add_streamer(self): raise NotImplementedException() def cmd_delete_streamer(self): raise NotImplementedException() def cmd_set_favorite_channel(self): self.fav_channel_button.setEnabled(False) self.config.set_favorite_channel(self.streamer_input.currentText(), self.channel_input.currentText()) self.insertText("Favorited channel '{}'.".format(self.channel_input.currentText())) def cmd_edit_channel(self): streamer = self.config.get_streamer(self.streamer_input.currentText()) streamer_icon = os.path.join(IMAGESROOT, streamer["icon"]) channel_data = self.config.get_streamer_channel(streamer["name"], self.channel_input.currentText()) dialog = AddEditChannelsDialog(self, self.config, title="Edit the channel", streamer_icon=streamer_icon, streamer=streamer, channel_data=channel_data) dialog.exec() result = dialog.result_data dialog.close() dialog = None if result is not None: self.insertText("Updated channel name '{old_name}' => '{new_name}, URL '{old_url}' => '{new_url}'".format(old_name=channel_data["name"], new_name=result["name"], old_url=channel_data["url"], new_url=result["url"])) self.load_channels(streamer["name"]) # Set the active channel to the previously selected (due to possible name change and sorting) self.channel_input.setCurrentIndex(self.channel_input.findText(result["name"])) def cmd_add_channel(self): streamer = self.config.get_streamer(self.streamer_input.currentText()) streamer_icon = os.path.join(IMAGESROOT, streamer["icon"]) dialog = AddEditChannelsDialog(self, self.config, title="Add a channel", streamer_icon=streamer_icon, streamer=streamer) dialog.exec() result = dialog.result_data dialog.close() dialog = None if result is not None: self.insertText("Added channel '{}' with URL '{}'".format(result["name"], result["url"])) self.load_channels(streamer["name"]) def cmd_delete_channel(self): channel = self.config.get_streamer_channel(self.streamer_input.currentText(), self.channel_input.currentText()) reply = QMessageBox.question(self, "Delete channel", "Are you sure you want to remove the channel?\nName: {}\nURL: {}".format(channel["name"], channel["url"]), QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: self.config.delete_channel(self.streamer_input.currentText(), channel["name"]) self.insertText("Removed channel '{}' with URL '{}'".format(channel["name"], channel["url"])) self.load_channels(self.streamer_input.currentText()) def cmd_refresh_quality_cache(self): self.insertText("Refreshing cache for channel '{}'.".format(self.channel_input.currentText())) self.clear_quality_cache_button.setEnabled(False) self.clear_quality_cache_button.repaint() # Loading streams seems to block repainting of the GUI, so force a repaint here self.config.clean_quality_cache(self.streamer_input.currentText(), self.channel_input.currentText(), True) self.load_streams(True) self.clear_quality_cache_button.setEnabled(True) def on_close_override(self): self.close_override = True self.close() def on_streamer_select(self, event): # If the previously selected item is selected again, don't do anything if self.selections["streamer"] == self.streamer_input.currentText(): return self.selections["streamer"] = self.streamer_input.currentText() streamer = self.config.get_streamer(self.streamer_input.currentText()) self.set_window_icon() if streamer["favorite"]: self.fav_streamer_button.setEnabled(False) else: self.fav_streamer_button.setEnabled(True) def on_channel_select(self, event): # If the previously selected item is selected again, don't do anything if self.selections["channel"] == self.channel_input.currentText() or not self.channel_input.currentText(): return self.selections["channel"] = self.channel_input.currentText() channel = self.config.get_streamer_channel(self.streamer_input.currentText(), self.channel_input.currentText()) if channel and channel["favorite"]: self.fav_channel_button.setEnabled(False) else: self.fav_channel_button.setEnabled(True) self.load_streams() self.channel_input.setFocus(True) def load_streamers(self): streamers = self.config.get_streamers() favorite_streamer_index = 0 streamer_list = [] for index, streamer in enumerate(streamers): streamer_list.append(streamer["name"]) if streamer["favorite"]: favorite_streamer_index = index self.streamer_input.clear() self.streamer_input.addItems(streamer_list) if len(streamer_list) != 0: self.streamer_input.setCurrentIndex(favorite_streamer_index) self.selections["streamer"] = self.streamer_input.currentText() self.fav_streamer_button.setEnabled(False) def load_channels(self, streamer_name): channels = self.config.get_streamer_channels(streamer_name) self.channel_input.clear() favorite_channel = None channel_list = [] self.fav_channel_button.setEnabled(False) for index, channel in enumerate(channels): channel_list.append(channel["name"]) if channel["favorite"]: favorite_channel = channel["name"] self.channel_input.addItems(sorted(channel_list)) if len(channel_list) == 0: self.channel_input.addItem("(no channels exist for this streamer)") self.fav_channel_button.setEnabled(False) self.edit_channel_button.setEnabled(False) self.delete_channel_button.setEnabled(False) self.clear_quality_cache_button.setEnabled(False) self.channel_input.setEnabled(False) else: self.edit_channel_button.setEnabled(True) self.delete_channel_button.setEnabled(True) self.clear_quality_cache_button.setEnabled(True) self.channel_input.setEnabled(True) if favorite_channel is None: self.channel_input.setCurrentIndex(0) self.fav_channel_button.setEnabled(True) else: self.channel_input.setCurrentIndex(self.channel_input.findText(favorite_channel)) self.selections["channel"] = self.channel_input.currentText() def display_loaded_streams(self, streams, skip_caching=False): self.quality_input.clear() if len(streams) == 0: self.quality_input.addItem("(channel is currently not streaming)") else: self.run_livestreamer_button.setEnabled(True) self.clear_quality_cache_button.setEnabled(True) self.quality_input.addItems(sorted(streams)) self.quality_input.setCurrentIndex(0) self.quality_input.setEnabled(True) if not skip_caching: self.insertText("Cleaning any cached streams for channel '{}'...".format(self.channel_input.currentText())) self.config.clean_quality_cache(self.streamer_input.currentText(), self.channel_input.currentText()) self.insertText("Adding probed streams for channel '{}' to cache...".format(self.channel_input.currentText())) self.config.add_quality_to_cache(self.streamer_input.currentText(), self.channel_input.currentText(), streams) self.insertText("Done.") def load_streams(self, force_refresh=False): self.quality_input.clear() self.run_livestreamer_button.setEnabled(False) self.channel_input.setEnabled(False) self.quality_input.setEnabled(False) if self.channel_input.count() == 0: return streams = self.config.get_quality_from_cache(self.streamer_input.currentText(), self.channel_input.currentText()) if len(streams) > 0: self.display_loaded_streams(streams, True) self.insertText("Loaded streams for channel '{}' from cache.".format(self.channel_input.currentText())) else: self.insertText("No cached channel streams found for channel '{}'".format(self.channel_input.currentText())) if not force_refresh and self.config.get_config_value('auto-refresh-quality') == 0: self.quality_input.addItem("(auto-refresh is disabled; please refresh manually)") self.quality_input.setEnabled(False) else: stream_url = self.get_streamer_url() if stream_url is None: self.insertText("Failed to form a complete streamer URL (missing streamer/channel/stream)!") return self.probe_for_streams(stream_url) self.channel_input.setEnabled(True) def probe_for_streams(self, stream_url): self.insertText("Probing streamer's channel for live streams: {}".format(stream_url)) livestreamer = self.config.get_config_value("livestreamer-path") if livestreamer is None or livestreamer.strip() == "" or not os.path.isfile(livestreamer): self.insertText("Livestreamer path is not configured or file doesn't exist!") return command_format = self.config.get_config_value("probe-command-format") command = command_format.format(livestreamer=livestreamer, url=stream_url) self.livestreamer_thread = LivestreamerWorker(shlex.split(command)) self.livestreamer_thread.statusMessage.connect(self.parse_probed_streams, False) self.livestreamer_thread.start() self.livestreamer_thread.wait(self.thread_exit_grace_time) def parse_probed_streams(self, event): streams = [] message = event.message.lower() if "no streams found on this url" in message: self.insertText("No streams found. The channel is probably not streaming.") else: pos = message.find("available streams:") if pos == -1: return if "(best, worst)" in message: message = message.replace("(best, worst)", "(best and worst)") elif "(worst, best)" in message: message = message.replace("(worst, best)", "(worst and best)") qualities = message[pos+18:].split(",") for item in qualities: streams.append(item.strip()) left_parenthesis = item.find("(") if left_parenthesis == -1: continue if item.find("worst", left_parenthesis) >= left_parenthesis: streams.append("worst") if item.find("best", left_parenthesis) >= left_parenthesis: streams.append("best") streams.sort() self.insertText("Found {} stream(s): {}".format(len(streams), ", ".join(streams))) self.display_loaded_streams(streams) def get_streamer_url(self): streamer = self.config.get_streamer(self.streamer_input.currentText()) if streamer is None: self.insertText("No streamer selected!") return if streamer["url"] is None or streamer["url"].strip() == "": self.insertText("Invalid streamer URL!") return if self.channel_input.count() == 0: self.insertText("No channels exist!") return channel = self.config.get_streamer_channel(streamer["name"], self.channel_input.currentText()) return urljoin(streamer["url"], channel["url"]) def run_livestreamer(self): if self.livestreamer_thread is not None: if self.livestreamer_thread.isRunning(): self.insertText("Livestreamer should still be running!") return else: self.livestreamer_thread.wait(self.thread_exit_grace_time) self.livestreamer_thread = None self.update() if self.livestreamer_thread is None: livestreamer = self.config.get_config_value("livestreamer-path") if livestreamer is None or livestreamer.strip() == "" or not os.path.isfile(livestreamer): self.insertText("Livestreamer path is not configured or file doesn't exist!") return player = self.config.get_config_value("player-path") if player is None or player.strip() == "" or not os.path.isfile(player): self.insertText("Player path is not configured or file doesn't exist!") return stream_url = self.get_streamer_url() if stream_url is None: self.insertText("Failed to form a complete streamer URL (missing streamer/channel/stream)!") return command_format = self.config.get_config_value("command-format") quality = self.quality_input.currentText() if "(" in quality: quality = quality[:quality.find("(")].strip() command = command_format.format(livestreamer=livestreamer, player=player, url=stream_url, quality=quality) self.livestreamer_thread = LivestreamerWorker(shlex.split(command)) self.insertText("Starting Livestreamer thread.") self.livestreamer_thread.finished.connect(self.handle_livestreamer_thread_finished_signal) self.livestreamer_thread.statusMessage.connect(self.handle_livestreamer_thread_message_signal) self.livestreamer_thread.start() @QtCore.pyqtSlot(object) def handle_livestreamer_thread_message_signal(self, event): self.insertText(event.message, event.add_newline, event.add_timestamp) def handle_livestreamer_thread_finished_signal(self): self.livestreamer_thread = None def update_colors(self): foreground_color = self.config.get_config_value("foreground-color") background_color = self.config.get_config_value("background-color") self.cwidget.setStyleSheet("QWidget QLabel {{ color: {0} }} .QWidget {{ background-color: {1} }}".format(foreground_color, background_color)) self.cwidget.update() def insertText(self, msg, add_newline=True, timestamp=True): """Helper method for outputting text to the text box.""" text = "" if timestamp and self.timestamp_format is not None: timestamp = format(datetime.now().strftime(self.timestamp_format)) text = "{} ".format(timestamp) text += msg self.log_widget.moveCursor(QTextCursor.End) self.log_widget.insertPlainText(text) if add_newline: self.log_widget.insertPlainText("\n") self.log_widget.update()
def init_desktop_app(): app = Application([]) app.setQuitOnLastWindowClosed(False) def sigint_handler(*args): """Handler for the SIGINT signal.""" quit_specter(app) # fix termination ctrl+c signal.signal(signal.SIGINT, sigint_handler) # Create the icon icon = QIcon(os.path.join(resource_path('icons'), 'icon.png')) # Create the tray tray = QSystemTrayIcon() tray.setIcon(icon) tray.setVisible(True) # Create webview view = WebView(tray) # Create the menu menu = QMenu() start_specterd_menu = QAction( "Start Specter{} daemon".format(' HWIBridge' if settings.value( "remote_mode", defaultValue=False, type=bool) else '')) start_specterd_menu.triggered.connect(lambda: watch_specterd(menu, view)) menu.addAction(start_specterd_menu) open_webview_menu = QAction("Open Specter App") open_webview_menu.triggered.connect(lambda: open_webview(view)) menu.addAction(open_webview_menu) open_specter_menu = QAction("Open in the browser") open_specter_menu.triggered.connect(open_specter_window) menu.addAction(open_specter_menu) toggle_specterd_status(menu) open_settings_menu = QAction("Preferences") open_settings_menu.triggered.connect(open_settings) menu.addAction(open_settings_menu) # Add a Quit option to the menu. quit = QAction("Quit") quit.triggered.connect(lambda: quit_specter(app)) menu.addAction(quit) # Add the menu to the tray tray.setContextMenu(menu) app.setWindowIcon(icon) # Setup settings first_time = settings.value('first_time', defaultValue=True, type=bool) if first_time: settings.setValue('first_time', False) settings.setValue('remote_mode', False) settings.setValue('specter_url', "http://localhost:25441/") open_settings() # start server global specterd_thread # add hwibridge to args if settings.value("remote_mode", defaultValue=False, type=bool): sys.argv.append("--hwibridge") # start thread specterd_thread = threading.Thread(target=server) specterd_thread.daemon = True specterd_thread.start() watch_specterd(menu, view) sys.exit(app.exec_())
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 ExampleApp(QtWidgets.QMainWindow, design.Ui_MainWindow): def __init__(self): super().__init__() self.setupUi(self) # Это нужно для инициализации нашего дизайна self.SERVER = "127.0.0.1" # адресс YaMPD self.PORT = 5592 # порт для YaMPD self.showIS = False # переменная в которой храниться состояние окна () self.pushButton_3.clicked.connect(self.onwardYaMPC) self.pushButton_2.clicked.connect(self.pauseYaMPC) self.pushButton.clicked.connect(self.backYaMPC) self.tray_icon = QSystemTrayIcon(self) self.tray_icon.setIcon(self.style().standardIcon( QStyle.SP_ComputerIcon)) self.tray_icon.activated.connect(self.action) self.tray_icon.show() self.hide() def pauseYaMPC(self): client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect((self.SERVER, self.PORT)) client.sendall(bytes("pause", 'UTF-8')) data = client.recv(1024) print(data.decode()) client.close() self.hide() def backYaMPC(self): client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect((self.SERVER, self.PORT)) client.sendall(bytes("back", 'UTF-8')) data = client.recv(1024) print(data.decode()) client.close() self.hide() def onwardYaMPC(self): client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect((self.SERVER, self.PORT)) client.sendall(bytes("onward", 'UTF-8')) data = client.recv(1024) print(data.decode()) client.close() self.hide() def action(self): if self.showIS: self.hide() self.showIS = False else: client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect((self.SERVER, self.PORT)) client.sendall(bytes("returnName", 'UTF-8')) data = client.recv(1024) rnj = json.loads(data.decode()) client.close() self.label_2.setText(rnj["title"]) self.label_4.setText(rnj["artists"][0]["name"]) self.show() self.showIS = True print('System tray icon clicked.')
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 QuickLaunchPanelsWindow(QtWidgets.QMainWindow): def __init__(self, parent=None): super(QuickLaunchPanelsWindow, self).__init__(None) self.ui = Ui_QuickLaunchPanelsWindow() self.ui.setupUi(self) self.move(QApplication.desktop().screen().rect().center() - self.rect().center()) self.canClose = False self.configdir = settings.Settings.getConfigDir() self.initTray() self.app = parent self.canClose = False self.dialogs = [] #self.fn = os.path.dirname(os.path.abspath(__file__)) #self.fn += "/panels.lst" self.panels = [] self.model = QStandardItemModel(self) self.ui.lv.setModel(self.model) self.ui.lv.setStyleSheet( "QListView {background: #2b2b2b; padding: 4px; border-style: inset; border-width: 1px; border-color: #90404050;border-radius: 10px;}" ) self.ui.lv.setContextMenuPolicy(Qt.CustomContextMenu) self.ui.lv.customContextMenuRequested.connect(self.initContextMenu) self.loadpanels() self.ui.bEdit.clicked.connect(self.editclick) self.ui.bAdd.clicked.connect(self.addclick) self.ui.bDel.clicked.connect(self.delclick) self.setWindowIcon(QIcon(':/run.png')) def initContextMenu(self, pos): menu = QMenu(self) #menu.setStyleSheet("background: #222") act = QAction("Показать окно", self) act.triggered.connect(self.showdialog) menu.addAction(act) p = self.mapToGlobal(pos) p.setX(p.x() + 1) p.setY(p.y() + 1) menu.exec_(p) def showdialog(self): if self.model.rowCount() == 0: return i = self.ui.lv.currentIndex().row() self.dialogs[i].show() self.dialogs[i].onResize() self.panels[i]['visible'] = True def loadpanels(self): fn = self.configdir / 'panels.json' if not os.path.isfile(fn): return False with open(fn) as f: self.panels = json.load(f) for info in self.panels: item = QtGui.QStandardItem(info['title']) self.model.appendRow(item) visible = True if 'visible' in info: visible = info['visible'] else: info['visible'] = True self.createdialog(info['filename'], visible) def savepanels(self): fn = self.configdir / 'panels.json' with open(fn, 'w', encoding='utf-8') as f: json.dump(self.panels, f, ensure_ascii=False, indent=4) def closePanel(self, panel): index = -1 for i in range(len(self.dialogs)): if panel == self.dialogs[i]: index = i break if index < 0: return self.panels[i]['visible'] = False def createdialog(self, filename, visible, title=None): dialog = Panel(self.app, filename, title) self.dialogs.append(dialog) dialog.onCloseSignal.connect(self.closePanel) if visible: dialog.show() def closeDialogs(self): for dialog in self.dialogs: dialog.close() self.dialogs.clear() def doQuit(self): self.canClose = True if self.app != None and "QApplication" in str(type(self.app)): self.closeDialogs() self.savepanels() self.app.quit() def trayclick(self, i_reason): if i_reason == 3: # buttons & Qt.LeftButton: for dialog in self.dialogs: dialog.activateWindow() def closeEvent(self, event): if not self.canClose: event.ignore() self.hide() else: self.closeDialogs() def initTray(self): self.tray = QSystemTrayIcon(self) self.tray.setIcon( QIcon(":/run.png")) #QIcon.fromTheme("preferences-system")) show_action = QAction("Показать список", self) quit_action = QAction("Выход", self) #hide_action = QAction("Hide",self) show_action.triggered.connect(self.show) quit_action.triggered.connect(self.doQuit) #hide_action.triggered.connect(self.hide) traymenu = QMenu() traymenu.addAction(show_action) #traymenu.addAction(hide_action) traymenu.addAction(quit_action) self.tray.setContextMenu(traymenu) self.tray.activated.connect(self.trayclick) self.tray.show() def editclick(self): if self.model.rowCount() == 0: return i = self.ui.lv.currentIndex().row() txt = self.ui.lv.currentIndex().data() text, ok = QInputDialog.getText(self, 'Заголовок панели', 'Введите имя панели:', text=txt) if ok and txt != text: self.panels[i]['title'] = text self.dialogs[i].changeTitle(text) self.model.item(i).setText(text) self.savepanels() def addclick(self): text, ok = QInputDialog.getText(self, 'Заголовок панели', 'Введите имя панели:') if ok and len(text) > 1: fn = "".join(x for x in text if x.isalnum()) fn += '.json' item = QtGui.QStandardItem(text) self.model.appendRow(item) self.panels.append({'title': text, 'filename': fn}) self.createdialog(fn, True, text) def delclick(self): if self.model.rowCount() == 0: return i = self.ui.lv.currentIndex().row() reply = QMessageBox.question(self, "Внимание!!", "Удалить панель со значками?", QMessageBox.Yes | QMessageBox.No) if reply != QMessageBox.Yes: return panelname = self.panels[i]['title'] fn = self.configdir / self.panels[i]['filename'] if os.path.exists(fn): os.remove(fn) self.dialogs[i].close() self.dialogs.pop(i) self.panels.pop(i) self.model.removeRow(i)
class MainWindow(QMainWindow): """Voice Changer main window.""" def __init__(self, parent=None): super(MainWindow, self).__init__() self.statusBar().showMessage("Move Dial to Deform Microphone Voice !.") self.setWindowTitle(__doc__) self.setMinimumSize(240, 240) self.setMaximumSize(480, 480) self.resize(self.minimumSize()) self.setWindowIcon(QIcon.fromTheme("audio-input-microphone")) self.tray = QSystemTrayIcon(self) self.center() QShortcut("Ctrl+q", self, activated=lambda: self.close()) self.menuBar().addMenu("&File").addAction("Quit", lambda: exit()) self.menuBar().addMenu("Sound").addAction( "STOP !", lambda: call('killall rec', shell=True)) windowMenu = self.menuBar().addMenu("&Window") windowMenu.addAction("Hide", lambda: self.hide()) windowMenu.addAction("Minimize", lambda: self.showMinimized()) windowMenu.addAction("Maximize", lambda: self.showMaximized()) windowMenu.addAction("Restore", lambda: self.showNormal()) windowMenu.addAction("FullScreen", lambda: self.showFullScreen()) windowMenu.addAction("Center", lambda: self.center()) windowMenu.addAction("Top-Left", lambda: self.move(0, 0)) windowMenu.addAction("To Mouse", lambda: self.move_to_mouse_position()) # widgets group0 = QGroupBox("Voice Deformation") self.setCentralWidget(group0) self.process = QProcess(self) self.process.error.connect( lambda: self.statusBar().showMessage("Info: Process Killed", 5000)) self.control = QDial() self.control.setRange(-10, 20) self.control.setSingleStep(5) self.control.setValue(0) self.control.setCursor(QCursor(Qt.OpenHandCursor)) self.control.sliderPressed.connect( lambda: self.control.setCursor(QCursor(Qt.ClosedHandCursor))) self.control.sliderReleased.connect( lambda: self.control.setCursor(QCursor(Qt.OpenHandCursor))) self.control.valueChanged.connect( lambda: self.control.setToolTip(f"<b>{self.control.value()}")) self.control.valueChanged.connect( lambda: self.statusBar().showMessage( f"Voice deformation: {self.control.value()}", 5000)) self.control.valueChanged.connect(self.run) self.control.valueChanged.connect(lambda: self.process.kill()) # Graphic effect self.glow = QGraphicsDropShadowEffect(self) self.glow.setOffset(0) self.glow.setBlurRadius(99) self.glow.setColor(QColor(99, 255, 255)) self.control.setGraphicsEffect(self.glow) self.glow.setEnabled(False) # Timer to start self.slider_timer = QTimer(self) self.slider_timer.setSingleShot(True) self.slider_timer.timeout.connect(self.on_slider_timer_timeout) # an icon and set focus QLabel(self.control).setPixmap( QIcon.fromTheme("audio-input-microphone").pixmap(32)) self.control.setFocus() QVBoxLayout(group0).addWidget(self.control) self.menu = QMenu(__doc__) self.menu.addAction(__doc__).setDisabled(True) self.menu.setIcon(self.windowIcon()) self.menu.addSeparator() self.menu.addAction( "Show / Hide", lambda: self.hide() if self.isVisible() else self.showNormal()) self.menu.addAction("STOP !", lambda: call('killall rec', shell=True)) self.menu.addSeparator() self.menu.addAction("Quit", lambda: exit()) self.tray.setContextMenu(self.menu) self.make_trayicon() def run(self): """Run/Stop the QTimer.""" if self.slider_timer.isActive(): self.slider_timer.stop() self.glow.setEnabled(True) call('killall rec ; killall play', shell=True) self.slider_timer.start(3000) def on_slider_timer_timeout(self): """Run subprocess to deform voice.""" self.glow.setEnabled(False) value = int(self.control.value()) * 100 command = f'play -q -V0 "|rec -q -V0 -n -d -R riaa bend pitch {value} "' print(f"Voice Deformation Value: {value}") print(f"Voice Deformation Command: {command}") self.process.start(command) if self.isVisible(): self.statusBar().showMessage("Minimizing to System TrayIcon", 3000) print("Minimizing Main Window to System TrayIcon now...") sleep(3) self.hide() def center(self): """Center Window on the Current Screen,with Multi-Monitor support.""" window_geometry = self.frameGeometry() mousepointer_position = QApplication.desktop().cursor().pos() screen = QApplication.desktop().screenNumber(mousepointer_position) centerPoint = QApplication.desktop().screenGeometry(screen).center() window_geometry.moveCenter(centerPoint) self.move(window_geometry.topLeft()) def move_to_mouse_position(self): """Center the Window on the Current Mouse position.""" window_geometry = self.frameGeometry() window_geometry.moveCenter(QApplication.desktop().cursor().pos()) self.move(window_geometry.topLeft()) def make_trayicon(self): """Make a Tray Icon.""" if self.windowIcon() and __doc__: self.tray.setIcon(self.windowIcon()) self.tray.setToolTip(__doc__) self.tray.activated.connect( lambda: self.hide() if self.isVisible() else self.showNormal()) return self.tray.show()
class CMainWindow(QtWidgets.QDialog): def __init__(self): encfsgui_helper.print_debug("Start %s" % inspect.stack()[0][3]) super(CMainWindow, self).__init__() uic.loadUi('encfsgui_main.ui', self) # disable/remove buttons self.setWindowFlags(self.windowFlags() | QtCore.Qt.CustomizeWindowHint | QtCore.Qt.WindowStaysOnTopHint) self.setWindowFlag(QtCore.Qt.WindowMaximizeButtonHint, False) self.setWindowFlag(QtCore.Qt.WindowCloseButtonHint, False) # assign methods to buttons self.quitbutton = self.findChild(QtWidgets.QToolButton, 'btn_Quit') self.quitbutton.clicked.connect(self.QuitButtonClicked) self.hidebutton = self.findChild(QtWidgets.QToolButton, 'btn_Hide') self.hidebutton.clicked.connect(self.HideButtonClicked) self.createvolumebutton = self.findChild(QtWidgets.QToolButton, 'btn_createVolume') self.createvolumebutton.clicked.connect(self.CreateVolumeButtonClicked) self.addvolumebutton = self.findChild(QtWidgets.QToolButton, 'btn_openVolume') self.addvolumebutton.clicked.connect(self.AddVolumeButtonClicked) self.editvolumebutton = self.findChild(QtWidgets.QToolButton, 'btn_editVolume') self.editvolumebutton.clicked.connect(self.EditVolumeButtonClicked) self.settingsbutton = self.findChild(QtWidgets.QToolButton, 'btn_Settings') self.settingsbutton.clicked.connect(self.SetttingsButtonClicked) self.volumetable = self.findChild(QtWidgets.QTableWidget, 'tbl_Volumes') self.volumetable.itemSelectionChanged.connect(self.TableEntrySelected) self.browsevolumebutton = self.findChild(QtWidgets.QToolButton, 'btn_browseVolume') self.browsevolumebutton.clicked.connect(self.BrowseVolumeClicked) self.removevolumebutton = self.findChild(QtWidgets.QToolButton, 'btn_removeVolume') self.removevolumebutton.clicked.connect(self.RemoveVolumeClicked) self.infovolumebutton = self.findChild(QtWidgets.QToolButton, 'btn_infoVolume') self.infovolumebutton.clicked.connect(self.ShowVolumeInfoClicked) self.refreshbutton = self.findChild(QtWidgets.QToolButton, 'btn_refreshVolumes') self.refreshbutton.clicked.connect(self.RefreshVolumesClicked) self.mountvolumebutton = self.findChild(QtWidgets.QToolButton, 'btn_mountVolume') self.mountvolumebutton.clicked.connect(self.MountVolumeClicked) self.unmountvolumebutton = self.findChild(QtWidgets.QToolButton, 'btn_unmountVolume') self.unmountvolumebutton.clicked.connect(self.UnmountVolumeClicked) self.unmountallbutton = self.findChild(QtWidgets.QToolButton, 'btn_unmountAll') self.unmountallbutton.clicked.connect(self.UnmountAllClicked) self.lbl_updatestate = self.findChild(QtWidgets.QLabel, 'lbl_updatestate') self.lbl_updatestate.setText("") self.lbl_infolabel = self.findChild(QtWidgets.QLabel, 'lbl_InfoLabel') self.lbl_infolabel.setText("") def initMainWindow(self): # only call this after checking for update # enable/disablebuttons as needed self.RefreshSettings() self.RefreshVolumes() self.EnableDisableButtons() # system tray menu self.tray_icon = QSystemTrayIcon(self) #self.tray_icon.setIcon(self.style().standardIcon(QStyle.SP_DriveHDIcon)) #self.tray_icon.setIcon(QIcon('./bitmaps/encfsgui.png')) icondir = encfsgui_helper.getCurDir() iconfolder = os.path.join(icondir,'bitmaps' ) iconpath = os.path.join(iconfolder, 'encfsgui.ico') wiconpath = os.path.join(iconfolder, 'encfsgui.png') self.tray_icon.setIcon(QIcon(iconpath)) self.tray_icon.setVisible(True) self.tray_menu = QMenu() self.volume_menu = QMenu() self.CreateTrayMenu() self.setWindowIcon(QIcon(wiconpath)) # context menu for TableWidget self.volumetable = self.findChild(QtWidgets.QTableWidget, 'tbl_Volumes') self.volumetablemenu = QMenu() self.volumetable.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.volumetable.customContextMenuRequested.connect(self.CreateVolumeMenu) # capture right click self.volumetable.viewport().installEventFilter(self) # capture double click self.volumetable.doubleClicked.connect(self.TableDoubleClicked) def eventFilter(self, source, event): if(event.type() == QtCore.QEvent.MouseButtonPress and event.buttons() == QtCore.Qt.RightButton and source is self.volumetable.viewport()): item = self.volumetable.itemAt(event.pos()) encfsgui_helper.print_debug('Right-click at Global Pos: %s' % event.globalPos()) if item is not None: encfsgui_helper.print_debug('Right-click Table Item: %s %s' % (item.row(), item.column())) encfsgui_helper.print_debug('Currently selected volume: %s' % encfsgui_globals.g_CurrentlySelected) self.CreateVolumeMenu() self.volumetablemenu.exec_(event.globalPos()) #menu.exec_(event.globalPos()) return super(CMainWindow, self).eventFilter(source, event) # table double click def TableDoubleClicked(self): if encfsgui_globals.g_Settings["doubleclickmount"].lower() == "true": if encfsgui_globals.g_CurrentlySelected != "": volumename = encfsgui_globals.g_CurrentlySelected if volumename in encfsgui_globals.g_Volumes: EncVolumeObj = encfsgui_globals.g_Volumes[volumename] if EncVolumeObj.ismounted: self.UnmountVolumeClicked() else: self.MountVolumeClicked() return #methods linked to buttons def QuitButtonClicked(self): encfsgui_helper.print_debug("Start %s" % inspect.stack()[0][3]) doexit = False autunmount = False if "autounmount" in encfsgui_globals.g_Settings: if str(encfsgui_globals.g_Settings["autounmount"]) == "true": autunmount = True if "noconfirmationexit" in encfsgui_globals.g_Settings: if str(encfsgui_globals.g_Settings["noconfirmationexit"]) == "false": msgBox = QMessageBox() msgBox.setIcon(QMessageBox.Question) msgBox.setWindowTitle("Please confirm?") if autunmount: msgBox.setText("Are you sure you would like to exit?\n\nPlease note that some volumes may be unmounted automatically when you exit the application, so please make sure all files are closed.") else: msgBox.setText("Are you sure you would like to exit?") msgBox.setStandardButtons(QtWidgets.QMessageBox.No) msgBox.addButton(QtWidgets.QMessageBox.Yes) msgBox.show() msgBox.setFocus() if (msgBox.exec_() == QtWidgets.QMessageBox.Yes): doexit = True else: doexit = True if doexit: self.AutoUnMount() encfsgui_helper.print_debug("Application has exited.") sys.exit(0) return def ShowButtonClicked(self): encfsgui_helper.print_debug("Start %s" % inspect.stack()[0][3]) self.show_action.setEnabled(False) self.hide_action.setEnabled(True) encfsgui_globals.ishidden = False self.show() # force reload of modules and update window self.lbl_infolabel.setText("") self.volumetable.clearContents() self.volumetable.setRowCount(0) encfsgui_globals.appconfig.getVolumes() self.RefreshVolumes() self.PopulateVolumeMenu() self.setFocus() return def HideButtonClicked(self): encfsgui_helper.print_debug("Start %s" % inspect.stack()[0][3]) self.show_action.setEnabled(True) self.hide_action.setEnabled(False) if encfsgui_globals.g_Settings["clearkeywhenhidden"].lower() == "true": print_debug("Hiding window, clearing masterkey") encfsgui_globals.masterkey = "" encfsgui_globals.ishidden = True self.PopulateVolumeMenu() # will disable menu if needed # only hide on macos try: if encfsgui_helper.ismacOS(): self.hide() elif encfsgui_helper.isLinux(): self.showMinimized() except Exception as e: print_debug("Error hiding/minimizing: %s" % str(e)) return def AboutClicked(self): encfsgui_helper.print_debug("Start %s" % inspect.stack()[0][3]) abouttext = "pyencfsgui (EncFSGui) is a python3/PyQT5 based GUI wrapper around encfs and/or gocryptfs.\n\n" abouttext += "This version has been tested with encfs 1.9.x on OSX Catalina (and newer macOS versions), \n" abouttext += "and with gocryptfs 1.8.x on OSX Big Sur (and newer macOS versions). \n" abouttext += "Additionally, EncFSGui has been confirmed to work in Kali Linux.\n\n" abouttext += "Development started in 2019. The utility was written by Peter 'corelanc0d3r' Van Eeckhoutte.\n" abouttext += "Corelan Consulting bv\nwww.corelan-consulting.com | www.corelan-training.com\n\n" abouttext += "Project repository:\nhttps://github.com/corelan/pyencfsgui\n\n" abouttext += "Version info:\n" abouttext += "EncFSGui version %s.\n" % encfsgui_helper.getVersion() if os.path.exists(encfsgui_globals.g_Settings["encfspath"]): abouttext += "encfs version %s.\n" % getEncFSVersion() else: abouttext += "encfs not found.\n" if os.path.exists(encfsgui_globals.g_Settings["gocryptfspath"]): abouttext += "gocryptfs version %s.\n\n" % getGoCryptFSVersion() else: abouttext += "gocryptfs not found.\n\n" abouttext += "This application uses icons from https://icons8.com.\n" abouttext += "\nYou are running %s" % encfsgui_helper.getOSType() msgBox = QMessageBox() msgBox.setWindowTitle("About pyencfsgui") msgBox.setText(abouttext) msgBox.show() msgBox.exec_() return def checkFilenameEncodings(self): encfsgui_helper.print_debug("Start %s" % inspect.stack()[0][3]) encfsgui_helper.print_debug("Encodings: %s" % encfsgui_globals.g_Encodings) if len(encfsgui_globals.g_Encodings) == 0: self.volumetable.setEnabled(False) self.lbl_infolabel.setText("Getting filename encoding capabilities, hold on...") self.lbl_infolabel.update() encfsgui_globals.app.processEvents() self.update() encfsgui_helper.determineFileNameEncodings() encfsgui_helper.print_debug("Encodings: %s" % encfsgui_globals.g_Encodings) encfsgui_globals.appconfig.saveSettings() self.volumetable.setEnabled(True) self.SetInfoLabel() return # context menu def CreateVolumeMenu(self): encfsgui_helper.print_debug("Start %s" % inspect.stack()[0][3]) self.volumetablemenu.clear() volumename = encfsgui_globals.g_CurrentlySelected if volumename != "": if volumename in encfsgui_globals.g_Volumes: EncVolumeObj = encfsgui_globals.g_Volumes[volumename] self.volumemenuheader = QAction("Actions for volume '%s':" % volumename, self) self.volumetablemenu.addAction(self.volumemenuheader) self.volumetablemenu.addSeparator() if not EncVolumeObj.ismounted: self.volumemountaction = QAction(QIcon("./bitmaps/icons8-unlock-24.png"),"Mount volume", self) self.volumetablemenu.addAction(self.volumemountaction) self.volumemountaction.triggered.connect(self.TableMenuMountVolume) self.volumeeditaction = QAction("Edit volume", self) self.volumetablemenu.addAction(self.volumeeditaction) self.volumeeditaction.triggered.connect(self.EditVolumeButtonClicked) else: self.volumeunmountaction = QAction(QIcon("./bitmaps/icons8-lock-24.png"), "Unmount volume", self) self.volumetablemenu.addAction(self.volumeunmountaction) self.volumeunmountaction.triggered.connect(self.TableMenuUnmountVolume) self.volumeinfoaction = QAction("Show info", self) self.volumetablemenu.addAction(self.volumeinfoaction) self.volumeinfoaction.triggered.connect(self.ShowVolumeInfoClicked) return # system tray menu def CreateTrayMenu(self): encfsgui_helper.print_debug("Start %s" % inspect.stack()[0][3]) self.show_action = QAction("Show", self) self.hide_action = QAction("Hide", self) self.settings_action = QAction("Settings", self) self.about_action = QAction("About", self) self.quit_action = QAction("Quit", self) self.refresh_action = QAction("Refresh", self) self.show_action.triggered.connect(self.ShowButtonClicked) self.hide_action.triggered.connect(self.HideButtonClicked) self.settings_action.triggered.connect(self.SetttingsButtonClicked) self.about_action.triggered.connect(self.AboutClicked) self.quit_action.triggered.connect(self.QuitButtonClicked) self.refresh_action.triggered.connect(self.RefreshVolumesClicked) self.PopulateVolumeMenu() self.tray_menu.addAction(self.show_action) self.tray_menu.addAction(self.hide_action) self.tray_menu.addSeparator() self.tray_menu.addAction(self.refresh_action) self.tray_menu.addSeparator() self.tray_menu.addAction(self.settings_action) self.tray_menu.addSeparator() self.volume_menu.setTitle("Volumes") self.tray_menu.addMenu(self.volume_menu) self.tray_menu.addSeparator() self.tray_menu.addAction(self.about_action) self.tray_menu.addSeparator() self.tray_menu.addAction(self.quit_action) if self.isVisible(): self.show_action.setEnabled(False) self.hide_action.setEnabled(True) else: self.show_action.setEnabled(True) self.hide_action.setEnabled(False) self.tray_icon.setContextMenu(self.tray_menu) self.tray_icon.show() return def PopulateVolumeMenu(self): encfsgui_helper.print_debug("Start %s" % inspect.stack()[0][3]) self.volume_menu.clear() buildmenu = False # only build menu in certain cases encfsgui_helper.print_debug("Main window hidden? %s" % str(encfsgui_globals.ishidden)) encfsgui_helper.print_debug("Encrypt? %s" % encfsgui_globals.g_Settings["encrypt"].lower() ) encfsgui_helper.print_debug("Clear Key? %s" % encfsgui_globals.g_Settings["clearkeywhenhidden"].lower()) if not encfsgui_globals.ishidden: buildmenu = True else: if encfsgui_globals.g_Settings["encrypt"].lower() == "true" and encfsgui_globals.g_Settings["clearkeywhenhidden"].lower() == "true": buildmenu = False else: buildmenu = True if buildmenu: self.volume_menu.setEnabled(True) sorted_volumes = {k: encfsgui_globals.g_Volumes[k] for k in sorted(encfsgui_globals.g_Volumes)} for volumename in sorted_volumes: EncVolumeObj = encfsgui_globals.g_Volumes[volumename] addtolist = True if encfsgui_globals.g_Settings["hidevolumenotfound"].lower() == "true": addtolist = EncVolumeObj.enc_path_exists if addtolist: self.volume_mount = QAction(QIcon("./bitmaps/icons8-unlock-24.png"), "Mount '%s'" % volumename, self) self.volume_mount.triggered.connect(self.MenuMountVolume) self.volume_menu.addAction(self.volume_mount) self.volume_unmount = QAction(QIcon("./bitmaps/icons8-lock-24.png"), "Unmount '%s'" % volumename, self) self.volume_unmount.triggered.connect(self.MenuUnmountVolume) self.volume_menu.addAction(self.volume_unmount) self.volume_menu.addSeparator() if EncVolumeObj.ismounted: self.volume_mount.setEnabled(False) self.volume_mount.setVisible(False) self.volume_unmount.setEnabled(True) self.volume_unmount.setVisible(True) else: self.volume_mount.setEnabled(True) self.volume_mount.setVisible(True) self.volume_unmount.setEnabled(False) self.volume_unmount.setVisible(False) else: self.volume_menu.setEnabled(False) return def MenuMountVolume(self): encfsgui_helper.print_debug("Start %s" % inspect.stack()[0][3]) actionname = self.sender().text() volumename = self.getVolumeNameFromAction(actionname) if volumename in encfsgui_globals.g_Volumes: thispassword = self.getPasswordForVolume(volumename) self.MountVolume(volumename, thispassword) self.PopulateVolumeMenu() return def TableMenuMountVolume(self): encfsgui_helper.print_debug("Start %s" % inspect.stack()[0][3]) volumename = encfsgui_globals.g_CurrentlySelected if volumename in encfsgui_globals.g_Volumes: thispassword = self.getPasswordForVolume(volumename) self.MountVolume(volumename, thispassword) self.PopulateVolumeMenu() return def MenuUnmountVolume(self, action): encfsgui_helper.print_debug("Start %s" % inspect.stack()[0][3]) actionname = self.sender().text() volumename = self.getVolumeNameFromAction(actionname) self.UnmountVolume(volumename) self.PopulateVolumeMenu() return def TableMenuUnmountVolume(self, action): encfsgui_helper.print_debug("Start %s" % inspect.stack()[0][3]) volumename = encfsgui_globals.g_CurrentlySelected self.UnmountVolume(volumename) self.PopulateVolumeMenu() return def getVolumeNameFromAction(self, actionname): encfsgui_helper.print_debug("Start %s" % inspect.stack()[0][3]) volumename = "" if (actionname.startswith("Mount")): volumename = actionname.lstrip("Mount ").lstrip("'").rstrip("'") elif (actionname.startswith("Unmount")): volumename = actionname.lstrip("Unmount ").lstrip("'").rstrip("'") return volumename def CreateVolumeButtonClicked(self): encfsgui_helper.print_debug("Start %s" % inspect.stack()[0][3]) self.checkFilenameEncodings() createvolumewindow = CVolumeWindow() createvolumewindow.show() createvolumewindow.setRunMode(0) # create createvolumewindow.setFocus() createvolumewindow.activateWindow() createvolumewindow.exec_() self.RefreshVolumes() self.PopulateVolumeMenu() return def AddVolumeButtonClicked(self): encfsgui_helper.print_debug("Start %s" % inspect.stack()[0][3]) addvolumewindow = CVolumeWindow() addvolumewindow.show() addvolumewindow.setRunMode(1) # add addvolumewindow.setFocus() addvolumewindow.activateWindow() addvolumewindow.exec_() self.RefreshVolumes() self.PopulateVolumeMenu() return def EditVolumeButtonClicked(self): encfsgui_helper.print_debug("Start %s" % inspect.stack()[0][3]) self.checkFilenameEncodings() if encfsgui_globals.g_CurrentlySelected != "": editvolumewindow = CVolumeWindow() editvolumewindow.show() editvolumewindow.setRunMode(2) # edit editvolumewindow.origvolumename = encfsgui_globals.g_CurrentlySelected editvolumewindow.PopulateFields(encfsgui_globals.g_CurrentlySelected) editvolumewindow.setFocus() editvolumewindow.activateWindow() editvolumewindow.exec_() encfsgui_globals.appconfig.getVolumes() self.RefreshVolumes() return def RemoveVolumeClicked(self): encfsgui_helper.print_debug("Start %s" % inspect.stack()[0][3]) volumename = encfsgui_globals.g_CurrentlySelected if volumename != "": if volumename in encfsgui_globals.g_Volumes: EncVolumeObj = encfsgui_globals.g_Volumes[volumename] msgBox = QtWidgets.QMessageBox() msgBox.setIcon(QtWidgets.QMessageBox.Question) msgBox.setWindowTitle("Are you sure?") msgBox.setText("Are you sure you would like to remove volume '%s' from this app?\n (mounted at '%s')?\n\nNote: this will not unmount the volume, and will not remove the actual encrypted folder.\nI will only remove the volume from the application." % (volumename, EncVolumeObj.mount_path)) msgBox.setStandardButtons(QtWidgets.QMessageBox.No) msgBox.addButton(QtWidgets.QMessageBox.Yes) msgBox.show() if (msgBox.exec_() == QtWidgets.QMessageBox.Yes): encfsgui_helper.RemovePasswordFromKeyChain(volumename) encfsgui_globals.appconfig.delVolume(volumename) encfsgui_globals.appconfig.getVolumes() self.RefreshVolumes() return def ShowVolumeInfoClicked(self): encfsgui_helper.print_debug("Start %s" % inspect.stack()[0][3]) volumename = encfsgui_globals.g_CurrentlySelected if volumename != "": if volumename in encfsgui_globals.g_Volumes: EncVolumeObj = encfsgui_globals.g_Volumes[volumename] infotext = "" if EncVolumeObj.enctype == "encfs": if os.path.exists(encfsgui_globals.g_Settings["encfspath"]): cmd = "%sctl info '%s'" % (encfsgui_globals.g_Settings["encfspath"], EncVolumeObj.enc_path) cmdoutput = encfsgui_helper.execOSCmd(cmd) infotext = "EncFS volume info for '%s'\n" % volumename infotext += "Encrypted folder '%s'\n\n" % EncVolumeObj.enc_path for l in cmdoutput: infotext = infotext + l + "\n" if EncVolumeObj.enctype == "gocryptfs": if os.path.exists(encfsgui_globals.g_Settings["gocryptfspath"]): cmd = "%s -info '%s'" % (encfsgui_globals.g_Settings["gocryptfspath"], EncVolumeObj.enc_path) cmdoutput = encfsgui_helper.execOSCmd(cmd) infotext = "GoCryptFS volume info for '%s'\n" % volumename infotext += "Encrypted folder '%s'\n\n" % EncVolumeObj.enc_path for l in cmdoutput: infotext = infotext + l + "\n" if not infotext == "": msgBox = QtWidgets.QMessageBox() msgBox.setIcon(QtWidgets.QMessageBox.Information) msgBox.setWindowTitle("Encrypted Volume info (%s)" % EncVolumeObj.enctype) msgBox.setText(infotext) msgBox.setStandardButtons(QtWidgets.QMessageBox.Ok) msgBox.show() msgBox.exec_() return def RefreshVolumesClicked(self): self.RefreshVolumes() return def SetttingsButtonClicked(self): encfsgui_helper.print_debug("Start %s" % inspect.stack()[0][3]) settingswindow = CSettingsWindow() settingswindow.loadSettings() settingswindow.show() settingswindow.setFocus() settingswindow.activateWindow() settingswindow.exec_() # when dialog closes, refresh settings (in case user made a change) self.RefreshSettings() # don't refresh gui if gui is hidden, otherwise app might ask for master key if not encfsgui_globals.ishidden: self.RefreshVolumes() self.PopulateVolumeMenu() return def MountVolumeClicked(self): encfsgui_helper.print_debug("Start %s" % inspect.stack()[0][3]) # do we need to ask for password? if encfsgui_globals.g_CurrentlySelected != "": if encfsgui_globals.g_CurrentlySelected in encfsgui_globals.g_Volumes: thispassword = self.getPasswordForVolume(encfsgui_globals.g_CurrentlySelected) self.MountVolume(encfsgui_globals.g_CurrentlySelected, thispassword) return def getPasswordForVolume(self, volumename): encfsgui_helper.print_debug("Start %s" % inspect.stack()[0][3]) thispassword = "" EncVolumeObj = encfsgui_globals.g_Volumes[volumename] if str(EncVolumeObj.passwordsaved) == "0": frmpassword = CMountPasswordWindow() frmpassword.setEncPath(EncVolumeObj.enc_path) frmpassword.setMountPath(EncVolumeObj.mount_path) frmpassword.setWindowTitle("Please enter password for volume '%s'" % volumename) frmpassword.show() frmpassword.setFocus() frmpassword.activateWindow() frmpassword.exec_() thispassword = frmpassword.getPassword() else: thispassword = str(encfsgui_helper.getKeyChainPassword(volumename)) return thispassword def UnmountVolumeClicked(self): encfsgui_helper.print_debug("Start %s" % inspect.stack()[0][3]) if encfsgui_globals.g_CurrentlySelected != "": volumename = encfsgui_globals.g_CurrentlySelected self.UnmountVolume(volumename) return def UnmountAllClicked(self): encfsgui_helper.print_debug("Start %s" % inspect.stack()[0][3]) continueunmount = True if encfsgui_globals.g_Settings["confirmforceunmountall"].lower() == "true": forcedmsgBox = QtWidgets.QMessageBox() forcedmsgBox.setIcon(QMessageBox.Question) forcedmsgBox.setWindowTitle("Are you sure?") forcedmsgBox.setText("Are you sure you would like to forcibly unmount all volumes now?") forcedmsgBox.setStandardButtons(QtWidgets.QMessageBox.Yes) forcedmsgBox.addButton(QtWidgets.QMessageBox.No) forcedmsgBox.show() if (forcedmsgBox.exec_() == QtWidgets.QMessageBox.No): continueunmount = False if continueunmount: for volumename in encfsgui_globals.g_Volumes: self.UnmountVolume(volumename, True) self.RefreshVolumes() return def UnmountVolume(self, volumename, forced=False): encfsgui_helper.print_debug("Start %s" % inspect.stack()[0][3]) # do we need to ask for confirmation? askforconfirmation = True dounmount = False if "noconfirmationunmount" in encfsgui_globals.g_Settings: if encfsgui_globals.g_Settings["noconfirmationunmount"].lower() == "true": askforconfirmation = False if volumename in encfsgui_globals.g_Volumes: EncVolumeObj = encfsgui_globals.g_Volumes[volumename] if EncVolumeObj.ismounted: dounmount = True if askforconfirmation and not forced: msgBox = QtWidgets.QMessageBox() msgBox.setIcon(QMessageBox.Question) msgBox.setWindowTitle("Are you sure?") msgBox.setText("Unmount volume '%s' \n '%s'?\n\n(Make sure all files on this volume are closed first!)" % (volumename, EncVolumeObj.mount_path)) msgBox.setStandardButtons(QtWidgets.QMessageBox.Yes) msgBox.addButton(QtWidgets.QMessageBox.No) msgBox.show() if (msgBox.exec_() == QtWidgets.QMessageBox.No): dounmount = False if dounmount: cmd = "'%s' '%s'" % (encfsgui_globals.g_Settings["umountpath"], EncVolumeObj.mount_path) if EncVolumeObj.sudo == "1": cmd = "sudo '%s' '%s'" % (encfsgui_globals.g_Settings["umountpath"], EncVolumeObj.mount_path) encfsgui_helper.execOSCmd(cmd) # did unmount work? self.RefreshVolumes() EncVolumeObj = encfsgui_globals.g_Volumes[volumename] if EncVolumeObj.ismounted: QtWidgets.QMessageBox.critical(None,"Error unmounting volume","Unable to unmount volume '%s'\nMake sure all files are closed and try again." % volumename) # update context menu's self.PopulateVolumeMenu() return def BrowseVolumeClicked(self): encfsgui_helper.print_debug("Start %s" % inspect.stack()[0][3]) if encfsgui_globals.g_CurrentlySelected != "": if encfsgui_globals.g_CurrentlySelected in encfsgui_globals.g_Volumes: EncVolumeObj = encfsgui_globals.g_Volumes[encfsgui_globals.g_CurrentlySelected] encfsgui_helper.openFolder(EncVolumeObj.mount_path) return def MountVolume(self, volumename, password): encfsgui_helper.print_debug("Start %s" % inspect.stack()[0][3]) if volumename in encfsgui_globals.g_Volumes: EncVolumeObj = encfsgui_globals.g_Volumes[volumename] if (password != ""): if not encfsgui_helper.ifExists(EncVolumeObj.enctype): QtWidgets.QMessageBox.critical(None,"Error mounting volume","Unable to mount volume '%s', '%s' binary not found\n" % ( volumename, EncVolumeObj.enctype)) else: usesudo = "" if EncVolumeObj.sudo == "1": usesudo = "sudo" # if volume is encfs: if EncVolumeObj.enctype == "encfs": extra_osxfuse_opts = "" #mountcmd = "%s '%s' '%s' %s" % (encfsgui_globals.g_Settings["encfspath"], EncVolumeObj.enc_path, EncVolumeObj.mount_path, EncVolumeObj.encfsmountoptions) if (str(EncVolumeObj.allowother) == "1"): extra_osxfuse_opts += "-o allow_other " if (str(EncVolumeObj.mountaslocal) == "1"): extra_osxfuse_opts += "-o local " # first, create mount point if necessary createfoldercmd = "mkdir -p '%s'" % EncVolumeObj.mount_path encfsgui_helper.execOSCmd(createfoldercmd) encfsbin = encfsgui_globals.g_Settings["encfspath"] encvol = EncVolumeObj.enc_path mountvol = EncVolumeObj.mount_path encfsmountoptions = "" if not EncVolumeObj.encfsmountoptions == "": encfsmountoptions = "'%s'" % EncVolumeObj.encfsmountoptions # do the actual mount mountcmd = "sh -c \"echo '%s' | %s '%s' -v -S %s %s -o volname='%s' '%s' '%s' \"" % (str(password), usesudo, encfsbin, extra_osxfuse_opts, encfsmountoptions, volumename, encvol, mountvol) encfsgui_helper.execOSCmd(mountcmd) # if volume is gocryptfs: if EncVolumeObj.enctype == "gocryptfs": extra_osxfuse_opts = "" extra_gocryptfs_opts = "" if (str(EncVolumeObj.allowother) == "1"): extra_gocryptfs_opts += "-allow_other " if (str(EncVolumeObj.mountaslocal) == "1"): extra_osxfuse_opts += "-ko local " # first, create mount point if necessary createfoldercmd = "mkdir -p '%s'" % EncVolumeObj.mount_path encfsgui_helper.execOSCmd(createfoldercmd) gocryptfsbin = encfsgui_globals.g_Settings["gocryptfspath"] encvol = EncVolumeObj.enc_path mountvol = EncVolumeObj.mount_path if not EncVolumeObj.encfsmountoptions == "": extra_gocryptfs_opts += "'%s'" % EncVolumeObj.encfsmountoptions # do the actual mount #mountcmd = "sh -c \"echo '%s' | %s -v -S %s %s -o volname='%s' '%s' '%s' \"" % (str(password), gocryptfsbin, extra_osxfuse_opts, gocryptfsmountoptions, volumename, encvol, mountvol) mountcmd = "sh -c \"echo '%s' | %s '%s' -ko volname='%s' -ko fsname='%s' %s %s '%s' '%s'\"" % (str(password), usesudo, gocryptfsbin, volumename, volumename, extra_osxfuse_opts, extra_gocryptfs_opts, encvol, mountvol) encfsgui_helper.execOSCmd(mountcmd) self.RefreshVolumes() EncVolumeObj = encfsgui_globals.g_Volumes[volumename] if not EncVolumeObj.ismounted: QtWidgets.QMessageBox.critical(None,"Error mounting volume","Unable to mount volume '%s'\n\n%s" % ( volumename, EncVolumeObj.errormessage )) else: encfsgui_helper.print_debug("Did not attempt to mount, empty password given") # update context menu's self.PopulateVolumeMenu() return def TableEntrySelected(self): encfsgui_helper.print_debug("Start %s" % inspect.stack()[0][3]) # update the currently selected volume encfsgui_globals.g_CurrentlySelected = "" selectedindex = 0 for currentQTableWidgetItem in self.volumetable.selectedItems(): if selectedindex == 1: encfsgui_globals.g_CurrentlySelected = currentQTableWidgetItem.text() encfsgui_helper.print_debug("Selected entry %s" % encfsgui_globals.g_CurrentlySelected) selectedindex += 1 # enable/disable buttons accordingly self.EnableDisableButtons() return def RefreshVolumes(self): encfsgui_helper.print_debug("Start %s" % inspect.stack()[0][3]) # don't reload if main window is hidden, prevent masterkey to be required curframe = inspect.currentframe() calframe = inspect.getouterframes(curframe, 2) encfsgui_helper.print_debug("%s() Called from: %s()" % (inspect.stack()[0][3],calframe[1][3])) # get volumes from config file encfsgui_globals.appconfig.getVolumes() # show volumes in the table self.volumetable.clearContents() self.volumetable.setColumnCount(5) #self.volumetable.setRowCount(len(encfsgui_globals.g_Volumes)) self.volumetable.setRowCount(0) columnheaders = ['Mounted?', 'Volume Name', 'Encrypted folder', 'Mount at', 'Automount?'] self.volumetable.setHorizontalHeaderLabels(columnheaders) self.volumetable.setColumnWidth(0,75) self.volumetable.setColumnWidth(1,125) self.volumetable.setColumnWidth(2,365) self.volumetable.setColumnWidth(3,325) self.volumetable.setColumnWidth(4,95) # sort sorted_volumes = {k: encfsgui_globals.g_Volumes[k] for k in sorted(encfsgui_globals.g_Volumes)} volumeindex = 0 volumesfoundsofar = 0 for volumekey in sorted_volumes: EncVolumeObj = encfsgui_globals.g_Volumes[volumekey] addtolist = True if encfsgui_globals.g_Settings["hidevolumenotfound"].lower() == "true": addtolist = EncVolumeObj.enc_path_exists if addtolist: volumesfoundsofar += 1 self.volumetable.setRowCount(volumesfoundsofar) mountedtext = "NO" if EncVolumeObj.ismounted: mountedtext = "YES" automounttext = "NO" if str(EncVolumeObj.automount) == "1": automounttext = "YES" boldfont = QFont() boldfont.setBold(True) regularfont = QFont() regularfont.setBold(False) mountstate = QTableWidgetItem(mountedtext) if EncVolumeObj.ismounted: mountstate.setFont(boldfont) mountstate.setForeground(QColor(255,0,0)) else: mountstate.setFont(regularfont) mountstate.setForeground(QColor(0,255,0)) self.volumetable.setItem(volumeindex,0, mountstate) self.volumetable.setItem(volumeindex,1, QTableWidgetItem(volumekey)) self.volumetable.setItem(volumeindex,2, QTableWidgetItem(EncVolumeObj.enc_path)) self.volumetable.setItem(volumeindex,3, QTableWidgetItem(EncVolumeObj.mount_path)) self.volumetable.setItem(volumeindex,4, QTableWidgetItem(automounttext)) self.volumetable.setRowHeight(volumeindex,12) volumeindex += 1 self.SetInfoLabel() return def AutoMount(self): encfsgui_helper.print_debug("Start %s" % inspect.stack()[0][3]) # process automounts for volumename in encfsgui_globals.g_Volumes: EncVolumeObj = encfsgui_globals.g_Volumes[volumename] if str(EncVolumeObj.automount) == "1": if not EncVolumeObj.ismounted: thispassword = self.getPasswordForVolume(volumename) self.MountVolume(volumename, thispassword) return def AutoUnMount(self): encfsgui_helper.print_debug("Start %s" % inspect.stack()[0][3]) if str(encfsgui_globals.g_Settings["autounmount"]) == "true": # unmount the volumes that don't have preventautounmount set for volumename in encfsgui_globals.g_Volumes: EncVolumeObj = encfsgui_globals.g_Volumes[volumename] if str(EncVolumeObj.preventautounmount) == "0": self.UnmountVolume(volumename) return def RefreshSettings(self): encfsgui_helper.print_debug("Start %s" % inspect.stack()[0][3]) encfsgui_globals.appconfig.getSettings() #if not encfsgui_globals.ishidden: # self.SetInfoLabel() return def SetInfoLabel(self): encfsgui_helper.print_debug("Start %s" % inspect.stack()[0][3]) encfspath = encfsgui_globals.g_Settings["encfspath"] gocryptfspath = encfsgui_globals.g_Settings["gocryptfspath"] encfsinfo = "encfs not found" if os.path.exists(encfspath): encfsinfo = "encfs v%s" % (getEncFSVersion()) gocryptfsinfo = "gocryptfs not found" if os.path.exists(gocryptfspath): gocryptfsinfo = "gocryptfs %s" % (getGoCryptFSVersion()) self.lbl_infolabel.setText(" %s | %s | Nr of volumes: %d | %s" % (encfsinfo, gocryptfsinfo, self.volumetable.rowCount(), encfsgui_globals.volumesfile )) self.lbl_infolabel.update() encfsgui_globals.app.processEvents() return def EnableDisableButtons(self): encfsgui_helper.print_debug("Start %s" % inspect.stack()[0][3]) # did we select an entry in the table? selectedenable = False if encfsgui_globals.g_CurrentlySelected != "": selectedenable = True mounted = False if encfsgui_globals.g_CurrentlySelected in encfsgui_globals.g_Volumes: mounted = encfsgui_globals.g_Volumes[encfsgui_globals.g_CurrentlySelected].ismounted if mounted: self.mountvolumebutton.setEnabled(False) self.unmountvolumebutton.setEnabled(True) self.browsevolumebutton.setEnabled(True) self.editvolumebutton.setEnabled(False) else: self.mountvolumebutton.setEnabled(True) self.unmountvolumebutton.setEnabled(False) self.browsevolumebutton.setEnabled(False) self.editvolumebutton.setEnabled(True) else: self.mountvolumebutton.setEnabled(False) self.unmountvolumebutton.setEnabled(False) self.browsevolumebutton.setEnabled(False) self.editvolumebutton.setEnabled(False) self.infovolumebutton.setEnabled(selectedenable) self.removevolumebutton.setEnabled(selectedenable) return
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 Frame(QWidget, Setting): def __init__(self, parent=None): QWidget.__init__(self) self.platformstr = platform.system() if self.platformstr == "Linux": import os os.chdir(os.path.dirname(sys.argv[0])) self.SettingLoad() self.inSetting = False self.setGeometry(self.X, self.Y, self.W, self.H) self.Tray() self.ui = Ui_Frame() self.ui.setupUi(self, QColor(self.CL)) self.dftFlag = self.windowFlags() self.TransParent() self.timer = QTimer(self) self.timer.timeout.connect(self.ShowLcd) self.ShowLcd() if self.platformstr == "Windows": self.timerT = QTimer(self) self.timerT.timeout.connect(self.TopMost) self.timerT.start(10) # 解决无法关闭QAPP的问题 self.setAttribute(Qt.WA_QuitOnClose, True) def close(self): if self.inSetting: self.tray.showMessage(u"错误", '用户正在修改设置中, 无法退出', icon=3) # icon的值 0没有图标 1是提示 2是警告 3是错误 else: del self.ui # python不保证析构, 因此托盘可能无法消失, 需要手动hide self.tray.hide() del self.tray return QWidget.close(self) # 窗口初始设置 def TransParent(self): self.setWindowOpacity(self.TP) # 控件透明 self.setAttribute(Qt.WA_TranslucentBackground, True) # 窗口透明 self.setFocusPolicy(Qt.NoFocus) # 无焦点 if self.platformstr == "Linux": self.setAttribute(Qt.WA_TransparentForMouseEvents, True) # 鼠标穿透, 必须放在前面 self.setWindowFlags(self.windowFlags() | Qt.Tool | Qt.X11BypassWindowManagerHint | Qt.FramelessWindowHint) if self.platformstr == "Windows": self.setWindowFlags(self.windowFlags() | Qt.Tool | Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint) import win32gui import win32con self.hwnd = int(self.winId()) win32gui.SetWindowLong( self.hwnd, win32con.GWL_EXSTYLE, win32gui.GetWindowLong(self.hwnd, win32con.GWL_EXSTYLE) | win32con.WS_EX_TRANSPARENT | win32con.WS_EX_LAYERED | win32con.WS_EX_NOACTIVATE) # 更新时间 def ShowLcd(self): timev = QTime.currentTime() time = timev.addMSecs(500) nextTime = (1500 - (time.msec()) % 1000) text = time.toString(self.FM) self.ui.lcdNumber.display(text) self.timer.start(nextTime) # self.update() def TopMost(self): if self.platformstr == "Windows": import win32gui import win32con win32gui.SetWindowPos( self.hwnd, win32con.HWND_TOPMOST, 0, 0, 0, 0, win32con.SWP_NOMOVE | win32con.SWP_SHOWWINDOW | win32con.SWP_NOSIZE | win32con.SWP_NOACTIVATE) def ApplyChange(self): self.setGeometry(self.X, self.Y, self.W, self.H) self.setWindowOpacity(self.TP) self.ui.setupLcdColor(QColor(self.CL)) def SettingChange(self): if self.inSetting: self.tray.showMessage(u"错误", '正在修改设置中', icon=3) # icon的值 0没有图标 1是提示 2是警告 3是错误 else: self.inSetting = True from PyQt5.QtGui import QColor self.SettingDialog(self.ApplyChange) self.inSetting = False # 托盘 def Tray(self): self.tray = QSystemTrayIcon(self) # 创建托盘 if hasattr(sys, "_MEIPASS"): self.tray.setIcon(QIcon(sys._MEIPASS + r'/Icon.ico')) else: self.tray.setIcon(QIcon(r'./Icon.ico')) # 提示信息 self.tray.setToolTip(u'桌面时钟') # 创建托盘的右键菜单 menu = QMenu() menu.addAction(QAction(u'窗口设置', self, triggered=self.SettingChange)) menu.addAction(QAction(u'退出', self, triggered=self.close)) self.tray.setContextMenu(menu) # 把menu设定为托盘的右键菜单 self.tray.show()
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 GuiCore(): "Infrastructure layer class that manages the graphical user interface" def __init__(self, daily_article_selector): self.logger = logging.getLogger(__name__) fh = logging.handlers.RotatingFileHandler('logs/' + __name__ + '.log', maxBytes=10000000, backupCount=100) fh.setFormatter(logging.Formatter( fmt='%(asctime)s - %(levelname)s - %(name)s - %(message)s')) self.logger.addHandler(fh) self.logger.debug("Loading GUI") self.app = QApplication(sys.argv) os_name = platform.system() self.logger.info("Platform: " + os_name) self.article_selector = daily_article_selector self.article_title = self.article_selector.getDailyArticleTitle() self.setSystemTrayGUI() def setSettingsGui(self, settings_gui): """Inits the module that manages the GUI for the settings window.""" self.settings_gui = settings_gui def setSystemTrayGUI(self): """Manages how DailyWiki appears in the system tray. Note: the installer automatically edits Windows' registry to make sure that balloons mssages are supported.""" self.app.setQuitOnLastWindowClosed(False) self.icon = QIcon("resources/wikipedia_icon.png") self.tray = QSystemTrayIcon() self.tray.setIcon(self.icon) self.tray.setToolTip("DailyWiki") self.tray.setVisible(True) self.menu = QMenu() self.settings_action = QAction("Settings") self.exit_action = QAction("Exit") self.menu.addAction(self.settings_action) self.menu.addAction(self.exit_action) self.settings_action.triggered.connect(self.openSettings) self.exit_action.triggered.connect(self.exitFromBackground) self.tray.setContextMenu(self.menu) self.logger.debug("Balloon messages supported by the platform: " + str(QSystemTrayIcon.supportsMessages())) self.tray.showMessage("Your daily article is ready!", self.article_title) self.tray.messageClicked.connect(self.accessArticleFromTray) self.tray.activated.connect(self.accessArticleFromTray) self.tray.show() def accessArticleFromTray(self, activation_reason=QSystemTrayIcon.DoubleClick): """Allows to access the Wikipedia article when the user double-clicks the icon in the system tray""" if activation_reason != QSystemTrayIcon.DoubleClick: return self.logger.debug("Accessing article...") try: self.article_selector.accessDailyArticle() except requests.exceptions.ConnectionError: self.dialog_msg.setText("It looks like the connection has been interrupted... Try again later!") def openSettings(self): self.logger.debug("Opening settings...") self.settings_gui.displaySettings() def run(self): """Main loop function""" self.logger.debug("Entering main execution loop") sys.exit(self.app.exec_()) def exitFromBackground(self): """Called to exit when the program is running in the system tray.""" sys.exit()
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 ElectrumGui(Logger): @profiler def __init__(self, config: 'SimpleConfig', daemon: 'Daemon', plugins: 'Plugins'): set_language(config.get('language', get_default_language())) Logger.__init__(self) self.logger.info(f"Qt GUI starting up... Qt={QtCore.QT_VERSION_STR}, PyQt={QtCore.PYQT_VERSION_STR}") # Uncomment this call to verify objects are being properly # GC-ed when windows are closed #network.add_jobs([DebugMem([Abstract_Wallet, SPV, Synchronizer, # ElectrumWindow], interval=5)]) QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads) if hasattr(QtCore.Qt, "AA_ShareOpenGLContexts"): QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_ShareOpenGLContexts) if hasattr(QGuiApplication, 'setDesktopFileName'): QGuiApplication.setDesktopFileName('electrum-doi.desktop') self.gui_thread = threading.current_thread() self.config = config self.daemon = daemon self.plugins = plugins self.windows = [] # type: List[ElectrumWindow] self.efilter = OpenFileEventFilter(self.windows) self.app = QElectrumApplication(sys.argv) self.app.installEventFilter(self.efilter) self.app.setWindowIcon(read_QIcon("electrum_doi.png")) # timer self.timer = QTimer(self.app) self.timer.setSingleShot(False) self.timer.setInterval(500) # msec self.network_dialog = None self.lightning_dialog = None self.watchtower_dialog = None self.network_updated_signal_obj = QNetworkUpdatedSignalObject() self._num_wizards_in_progress = 0 self._num_wizards_lock = threading.Lock() # init tray self.dark_icon = self.config.get("dark_icon", False) self.tray = QSystemTrayIcon(self.tray_icon(), None) self.tray.setToolTip('Electrum-DOI') self.tray.activated.connect(self.tray_activated) self.build_tray_menu() self.tray.show() self.app.new_window_signal.connect(self.start_new_window) self.set_dark_theme_if_needed() run_hook('init_qt', self) def set_dark_theme_if_needed(self): use_dark_theme = self.config.get('qt_gui_color_theme', 'default') == 'dark' if use_dark_theme: try: import qdarkstyle self.app.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5()) except BaseException as e: use_dark_theme = False self.logger.warning(f'Error setting dark theme: {repr(e)}') # Apply any necessary stylesheet patches patch_qt_stylesheet(use_dark_theme=use_dark_theme) # Even if we ourselves don't set the dark theme, # the OS/window manager/etc might set *a dark theme*. # Hence, try to choose colors accordingly: ColorScheme.update_from_widget(QWidget(), force_dark=use_dark_theme) def build_tray_menu(self): # Avoid immediate GC of old menu when window closed via its action if self.tray.contextMenu() is None: m = QMenu() self.tray.setContextMenu(m) else: m = self.tray.contextMenu() m.clear() network = self.daemon.network m.addAction(_("Network"), self.show_network_dialog) if network and network.lngossip: m.addAction(_("Lightning Network"), self.show_lightning_dialog) if network and network.local_watchtower: m.addAction(_("Local Watchtower"), self.show_watchtower_dialog) for window in self.windows: name = window.wallet.basename() submenu = m.addMenu(name) submenu.addAction(_("Show/Hide"), window.show_or_hide) submenu.addAction(_("Close"), window.close) m.addAction(_("Dark/Light"), self.toggle_tray_icon) m.addSeparator() m.addAction(_("Exit Electrum-DOI"), self.close) def tray_icon(self): if self.dark_icon: return read_QIcon('electrum_dark_icon.png') else: return read_QIcon('electrum_light_icon.png') def toggle_tray_icon(self): self.dark_icon = not self.dark_icon self.config.set_key("dark_icon", self.dark_icon, True) self.tray.setIcon(self.tray_icon()) def tray_activated(self, reason): if reason == QSystemTrayIcon.DoubleClick: if all([w.is_hidden() for w in self.windows]): for w in self.windows: w.bring_to_top() else: for w in self.windows: w.hide() def close(self): for window in self.windows: window.close() if self.network_dialog: self.network_dialog.close() if self.lightning_dialog: self.lightning_dialog.close() if self.watchtower_dialog: self.watchtower_dialog.close() def new_window(self, path, uri=None): # Use a signal as can be called from daemon thread self.app.new_window_signal.emit(path, uri) def show_lightning_dialog(self): if not self.daemon.network.is_lightning_running(): return if not self.lightning_dialog: self.lightning_dialog = LightningDialog(self) self.lightning_dialog.bring_to_top() def show_watchtower_dialog(self): if not self.watchtower_dialog: self.watchtower_dialog = WatchtowerDialog(self) self.watchtower_dialog.bring_to_top() def show_network_dialog(self): if self.network_dialog: self.network_dialog.on_update() self.network_dialog.show() self.network_dialog.raise_() return self.network_dialog = NetworkDialog(self.daemon.network, self.config, self.network_updated_signal_obj) self.network_dialog.show() def _create_window_for_wallet(self, wallet): w = ElectrumWindow(self, wallet) self.windows.append(w) self.build_tray_menu() # FIXME: Remove in favour of the load_wallet hook run_hook('on_new_window', w) w.warn_if_testnet() w.warn_if_watching_only() return w def count_wizards_in_progress(func): def wrapper(self: 'ElectrumGui', *args, **kwargs): with self._num_wizards_lock: self._num_wizards_in_progress += 1 try: return func(self, *args, **kwargs) finally: with self._num_wizards_lock: self._num_wizards_in_progress -= 1 return wrapper @count_wizards_in_progress def start_new_window(self, path, uri, *, app_is_starting=False): '''Raises the window for the wallet if it is open. Otherwise opens the wallet and creates a new window for it''' wallet = None try: wallet = self.daemon.load_wallet(path, None) except BaseException as e: self.logger.exception('') custom_message_box(icon=QMessageBox.Warning, parent=None, title=_('Error'), text=_('Cannot load wallet') + ' (1):\n' + repr(e)) # if app is starting, still let wizard to appear if not app_is_starting: return if not wallet: try: wallet = self._start_wizard_to_select_or_create_wallet(path) except (WalletFileException, BitcoinException) as e: self.logger.exception('') custom_message_box(icon=QMessageBox.Warning, parent=None, title=_('Error'), text=_('Cannot load wallet') + ' (2):\n' + repr(e)) if not wallet: return # create or raise window try: for window in self.windows: if window.wallet.storage.path == wallet.storage.path: break else: window = self._create_window_for_wallet(wallet) except BaseException as e: self.logger.exception('') custom_message_box(icon=QMessageBox.Warning, parent=None, title=_('Error'), text=_('Cannot create window for wallet') + ':\n' + repr(e)) if app_is_starting: wallet_dir = os.path.dirname(path) path = os.path.join(wallet_dir, get_new_wallet_name(wallet_dir)) self.start_new_window(path, uri) return if uri: window.pay_to_URI(uri) window.bring_to_top() window.setWindowState(window.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive) window.activateWindow() return window def _start_wizard_to_select_or_create_wallet(self, path) -> Optional[Abstract_Wallet]: wizard = InstallWizard(self.config, self.app, self.plugins, gui_object=self) try: path, storage = wizard.select_storage(path, self.daemon.get_wallet) # storage is None if file does not exist if storage is None: wizard.path = path # needed by trustedcoin plugin wizard.run('new') storage, db = wizard.create_storage(path) else: db = WalletDB(storage.read(), manual_upgrades=False) wizard.run_upgrades(storage, db) except (UserCancelled, GoBack): return except WalletAlreadyOpenInMemory as e: return e.wallet finally: wizard.terminate() # return if wallet creation is not complete if storage is None or db.get_action(): return wallet = Wallet(db, storage, config=self.config) wallet.start_network(self.daemon.network) self.daemon.add_wallet(wallet) return wallet def close_window(self, window: ElectrumWindow): if window in self.windows: self.windows.remove(window) self.build_tray_menu() # save wallet path of last open window if not self.windows: self.config.save_last_wallet(window.wallet) run_hook('on_close_window', window) self.daemon.stop_wallet(window.wallet.storage.path) def init_network(self): # Show network dialog if config does not exist if self.daemon.network: if self.config.get('auto_connect') is None: wizard = InstallWizard(self.config, self.app, self.plugins, gui_object=self) wizard.init_network(self.daemon.network) wizard.terminate() def main(self): try: self.init_network() except UserCancelled: return except GoBack: return except BaseException as e: self.logger.exception('') return self.timer.start() path = self.config.get_wallet_path(use_gui_last_wallet=True) if not self.start_new_window(path, self.config.get('url'), app_is_starting=True): return signal.signal(signal.SIGINT, lambda *args: self.app.quit()) def quit_after_last_window(): # keep daemon running after close if self.config.get('daemon'): return # check if a wizard is in progress with self._num_wizards_lock: if self._num_wizards_in_progress > 0 or len(self.windows) > 0: return if self.config.get('persist_daemon'): return self.app.quit() self.app.setQuitOnLastWindowClosed(False) # so _we_ can decide whether to quit self.app.lastWindowClosed.connect(quit_after_last_window) def clean_up(): # Shut down the timer cleanly self.timer.stop() # clipboard persistence. see http://www.mail-archive.com/[email protected]/msg17328.html event = QtCore.QEvent(QtCore.QEvent.Clipboard) self.app.sendEvent(self.app.clipboard(), event) self.tray.hide() self.app.aboutToQuit.connect(clean_up) # main loop self.app.exec_() # on some platforms the exec_ call may not return, so use clean_up() def stop(self): self.logger.info('closing GUI') self.app.quit()
class MainWindow(QMainWindow): """Voice Changer main window.""" def __init__(self, parent=None): super(MainWindow, self).__init__() self.statusBar().showMessage("Move Dial to Deform Microphone Voice !.") self.setWindowTitle(__doc__) self.setMinimumSize(240, 240) self.setMaximumSize(480, 480) self.resize(self.minimumSize()) self.setWindowIcon(QIcon.fromTheme("audio-input-microphone")) self.tray = QSystemTrayIcon(self) self.center() QShortcut("Ctrl+q", self, activated=lambda: self.close()) self.menuBar().addMenu("&File").addAction("Quit", lambda: exit()) self.menuBar().addMenu("Sound").addAction( "STOP !", lambda: call('killall rec', shell=True)) windowMenu = self.menuBar().addMenu("&Window") windowMenu.addAction("Hide", lambda: self.hide()) windowMenu.addAction("Minimize", lambda: self.showMinimized()) windowMenu.addAction("Maximize", lambda: self.showMaximized()) windowMenu.addAction("Restore", lambda: self.showNormal()) windowMenu.addAction("FullScreen", lambda: self.showFullScreen()) windowMenu.addAction("Center", lambda: self.center()) windowMenu.addAction("Top-Left", lambda: self.move(0, 0)) windowMenu.addAction("To Mouse", lambda: self.move_to_mouse_position()) # widgets group0 = QGroupBox("Voice Deformation") self.setCentralWidget(group0) self.process = QProcess(self) self.process.error.connect( lambda: self.statusBar().showMessage("Info: Process Killed", 5000)) self.control = QDial() self.control.setRange(-10, 20) self.control.setSingleStep(5) self.control.setValue(0) self.control.setCursor(QCursor(Qt.OpenHandCursor)) self.control.sliderPressed.connect( lambda: self.control.setCursor(QCursor(Qt.ClosedHandCursor))) self.control.sliderReleased.connect( lambda: self.control.setCursor(QCursor(Qt.OpenHandCursor))) self.control.valueChanged.connect( lambda: self.control.setToolTip(f"<b>{self.control.value()}")) self.control.valueChanged.connect(lambda: self.statusBar().showMessage( f"Voice deformation: {self.control.value()}", 5000)) self.control.valueChanged.connect(self.run) self.control.valueChanged.connect(lambda: self.process.kill()) # Graphic effect self.glow = QGraphicsDropShadowEffect(self) self.glow.setOffset(0) self.glow.setBlurRadius(99) self.glow.setColor(QColor(99, 255, 255)) self.control.setGraphicsEffect(self.glow) self.glow.setEnabled(False) # Timer to start self.slider_timer = QTimer(self) self.slider_timer.setSingleShot(True) self.slider_timer.timeout.connect(self.on_slider_timer_timeout) # an icon and set focus QLabel(self.control).setPixmap( QIcon.fromTheme("audio-input-microphone").pixmap(32)) self.control.setFocus() QVBoxLayout(group0).addWidget(self.control) self.menu = QMenu(__doc__) self.menu.addAction(__doc__).setDisabled(True) self.menu.setIcon(self.windowIcon()) self.menu.addSeparator() self.menu.addAction( "Show / Hide", lambda: self.hide() if self.isVisible() else self.showNormal()) self.menu.addAction("STOP !", lambda: call('killall rec', shell=True)) self.menu.addSeparator() self.menu.addAction("Quit", lambda: exit()) self.tray.setContextMenu(self.menu) self.make_trayicon() def run(self): """Run/Stop the QTimer.""" if self.slider_timer.isActive(): self.slider_timer.stop() self.glow.setEnabled(True) call('killall rec ; killall play', shell=True) self.slider_timer.start(3000) def on_slider_timer_timeout(self): """Run subprocess to deform voice.""" self.glow.setEnabled(False) value = int(self.control.value()) * 100 command = 'play -q -V0 "|rec -q -V0 -n -d -R riaa bend pitch {}"'.format( value) print("Voice Deformation Value: " + str(value)) print("Voice Deformation Command: " + str(command)) self.process.start(command) if self.isVisible(): self.statusBar().showMessage("Minimizing to System TrayIcon", 3000) print("Minimizing Main Window to System TrayIcon now...") sleep(3) def center(self): """Center Window on the Current Screen,with Multi-Monitor support.""" window_geometry = self.frameGeometry() mousepointer_position = QApplication.desktop().cursor().pos() screen = QApplication.desktop().screenNumber(mousepointer_position) centerPoint = QApplication.desktop().screenGeometry(screen).center() window_geometry.moveCenter(centerPoint) self.move(window_geometry.topLeft()) def move_to_mouse_position(self): """Center the Window on the Current Mouse position.""" window_geometry = self.frameGeometry() window_geometry.moveCenter(QApplication.desktop().cursor().pos()) self.move(window_geometry.topLeft()) def make_trayicon(self): """Make a Tray Icon.""" if self.windowIcon() and __doc__: self.tray.setIcon(self.windowIcon()) self.tray.setToolTip(__doc__) self.tray.activated.connect( lambda: self.hide() if self.isVisible() else self.showNormal()) return self.tray.show()
def main(): exitcode = 0 try: menu = QMenu() # Proxy m_proxy = QAction("Proxy: Disabled") m_proxy.setShortcut('Ctrl+P') m_proxy.setCheckable(True) m_proxy.setDisabled(True) m_proxy.triggered.connect(lambda: current['proxy'].disable(m_proxy)) menu.addAction(m_proxy) proxy_dict = {} proxy_group = QActionGroup(menu) for proxy in profile.get_items('Proxy'): proxyname = proxy[0] proxy_dict[proxyname] = Proxy(proxy, m_proxy) proxy_group.addAction(proxy_dict[proxyname].QAction) menu.addAction(proxy_dict[proxyname].QAction) # Bypass menu.addSeparator() m_bypass = QAction("Bypass: Disabled") m_bypass.setShortcut('Ctrl+B') m_bypass.setCheckable(True) m_bypass.setDisabled(True) m_bypass.triggered.connect(lambda: current['bypass'].disable(m_bypass)) menu.addAction(m_bypass) bypass_dict = {} bypass_group = QActionGroup(menu) for bypass in profile.get_items('Bypass'): bypassname = bypass[0] bypass_dict[bypassname] = Bypass(bypass, m_bypass) bypass_group.addAction(bypass_dict[bypassname].QAction) menu.addAction(bypass_dict[bypassname].QAction) # Capture menu.addSeparator() m_capture = QAction("Capture: Disabled") m_capture.setShortcut('Ctrl+C') m_capture.setCheckable(True) m_capture.setDisabled(True) m_dashboard = QAction("Open Dashboard...") m_dashboard.setShortcut('Ctrl+D') m_dashboard.setDisabled(True) m_capture.triggered.connect( lambda: current['capture'].disable(m_capture, m_dashboard)) menu.addAction(m_capture) capture_dict = {} capture_group = QActionGroup(menu) for capture in profile.get_items('Capture'): capturename = capture[0] capture_dict[capturename] = Capture(capture, m_capture, m_dashboard) capture_group.addAction(capture_dict[capturename].QAction) menu.addAction(capture_dict[capturename].QAction) menu.addAction(m_dashboard) # Common m_log = QAction("Log Folder") m_log.setShortcut('Ctrl+L') m_log.triggered.connect(lambda: subprocess.call(["open", log_path])) m_profile = QAction("Profile Folder") m_profile.setShortcut('Ctrl+F') m_profile.triggered.connect( lambda: subprocess.call(["open", profile_path])) m_extension = QAction("Extension Folder") m_extension.setShortcut('Ctrl+E') m_extension.triggered.connect( lambda: subprocess.call(["open", ext_path])) m_copy_shell = QAction("Copy Shell Command") m_copy_shell.setShortcut('Ctrl+S') m_set_system = QAction("As System Proxy: " + user_port) m_set_system.setShortcut('Ctrl+A') m_set_system.triggered.connect(lambda: setproxy_menu(m_set_system)) m_copy_shell.triggered.connect(copy_shell) m_set_system.setCheckable(True) if system: m_set_system.setChecked(True) setproxy() menu.addSeparator() menu.addAction(m_set_system) menu.addSeparator() menu.addAction(m_log) menu.addAction(m_profile) menu.addAction(m_extension) menu.addAction(m_copy_shell) menu.addSeparator() m_quit = QAction('Quit V2Net (' + version + ')') m_quit.setShortcut('Ctrl+Q') m_quit.triggered.connect(APP.quit) menu.addAction(m_quit) # Add Tray tray = QSystemTrayIcon() tray.setIcon(QIcon("icon.png")) tray.setVisible(True) tray.setContextMenu(menu) # sys.exit(app.exec_()) exitcode = APP.exec_() finally: quitapp(exitcode)
def initUI(self): # 获取电脑屏幕宽高 让主界面初始化后处于屏幕中间 wh = QApplication.desktop().screenGeometry() self.screen_w , self.screen_h = wh.width() ,wh.height() self.setGeometry(int((self.screen_w-300)/2),int((self.screen_h-600)/2),300,600) # self.setWindowOpacity(0.97); #当前播放歌曲的封面 songer_img = DragLabel(self) # songer_img.setwinflag.connect(self.setwinflag) songer_img.setParent(self) songer_img.resize(300,200) self.picture = QLabel(songer_img) self.picture.resize(300,200) self.picture.setStyleSheet("QLabel{ border-image:url("+conf['pifu']+")}") # syl = QLabel(songer_img) # syl.setGeometry(15,5,34,15) # syl.setStyleSheet("QLabel{ border-image:url(image/newimg/logo.png);}") # ================================ songinfo = QLabel(songer_img) songinfo.setGeometry(0,30,300,80) songinfo.setStyleSheet("QLabel{ background:transparent;}") songpic = QLabel(songinfo) songpic.setGeometry(10,0,80,80) songpic.setStyleSheet("QLabel{ border-image:url(image/newimg/user.jpg);border-radius:2px;}") self.songname = QLabel("老鼠爱大米 - 香香",songinfo) self.songname.setGeometry(105,0,210,25) self.songname.setStyleSheet("QLabel{ color:#EEE;font-size:15px;}") uploaduser = QLabel("By 张三的歌",songinfo) uploaduser.move(105,25) # uploaduser.setCursor(QCursor(Qt.PointingHandCursor)) uploaduser.setStyleSheet("QLabel{ color:yellow;font-size:15px;} QLabel:hover{color:red}") fenshu = QLabel("评分 - 7.6",songinfo) fenshu.setGeometry(105,50,210,25) # self.picture.setGraphicsEffect(QGraphicsBlurEffect()) fenshu.setStyleSheet("QLabel{ color:#EEE;font-size:15px;}") songtool = QLabel(songer_img) songtool.setGeometry(0,110,300,35) songtool.setStyleSheet("QLabel{ background:transparent;}") # 喜欢歌曲 lovesong = QLabel(songtool) lovesong.setGeometry(20,10,25,25) lovesong.setStyleSheet("QLabel{ border-image:url(image/newimg/kg_ic_player_liked.png);}") # 评论 pinglun = QLabel(songtool) pinglun.setGeometry(50,5,33,33) pinglun.setStyleSheet("QLabel{ border-image:url(image/newimg/pinglun.png);}") # 歌曲更多信息 songmore = QLabel("查看这首歌的更多资料",songtool) songmore.move(100,10) # songmore.setCursor(QCursor(Qt.PointingHandCursor)) songmore.setStyleSheet("QLabel{ color:#BBB} QLabel:hover{color:pink}") # ====================================== # 顶部工具栏 # 隐藏 btn = QPushButton("",self) btn.setGeometry(270,0,15,32) # btn.setCursor(QCursor(Qt.PointingHandCursor)) btn.setStyleSheet("QPushButton{ border:none;color:white;background:transparent;border-image:url(image/newimg/mini.png) } QPushButton:hover{ border-image:url(image/newimg/mini_2.png) } ") btn.clicked.connect(self.close) # 换皮肤 btn = QPushButton("",self) btn.setGeometry(230,10,20,20) # btn.setCursor(QCursor(Qt.PointingHandCursor)) btn.setStyleSheet("QPushButton{ border:none;color:white;background:transparent;border-image:url(image/newimg/fx_slide_menu_change_bg_2.png) } QPushButton:hover{ border-image:url(image/newimg/fx_slide_menu_change_bg.png) } ") btn.clicked.connect(self.huanfu) # 设置封面 # btn = QPushButton("",self) # btn.setGeometry(230,-10,41,48) # btn.setCursor(QCursor(Qt.PointingHandCursor)) # btn.setStyleSheet("QPushButton{ border:none;color:white;background:transparent;border-image:url(image/newimg/fengmian.png) } ") # btn.clicked.connect(self.setHeaderImg) # 开启/关闭歌词 # btn = QPushButton("",self) # btn.setGeometry(200,0,30,30) # btn.setCursor(QCursor(Qt.PointingHandCursor)) # btn.setStyleSheet("QPushButton{ border:none;color:white;background:transparent;border-image:url(image/newimg/geci.png) } ") # btn.clicked.connect(self.lrc) # 播放组件 ( 播放 前进 后退 播放时间 进度条 歌曲名 音量 ) # 播放/暂停 self.playBtn = QPushButton("",songer_img) self.playBtn.setGeometry(130,155,32,25) self.playBtn.setStyleSheet("QPushButton{ border-image:url(image/newimg/statusbar_btn_play.png);border:none } QPushButton:hover{ border-image:url(image/newimg/statusbar_btn_play_2.png)} ") # 下一首 self.nextBtn = QPushButton("",songer_img) self.nextBtn.setGeometry(186,159,20,20) self.nextBtn.setStyleSheet("QPushButton{ border-image:url(image/newimg/statusbar_btn_next.png);border:none } QPushButton:hover{ border-image:url(image/newimg/statusbar_btn_next_2.png)}") # 音量调节 self.songvolume = QPushButton("",songer_img) self.songvolume.setGeometry(236,159,20,20) self.songvolume.setStyleSheet("QPushButton{ border-image:url(image/newimg/ic_player_menu_volume.png);border:none } QPushButton:hover{ border-image:url(image/newimg/ic_player_menu_volume_2.png)}") self.songvolume.clicked.connect(self.setvolume) # 音量 self.volslider = QSlider(Qt.Horizontal,self) self.volslider.setCursor(QCursor(Qt.UpArrowCursor)) self.volslider.setGeometry(250,165,45,6) self.volslider.setValue(70) self.volslider.setRange(0,100) self.volslider.setStyleSheet(qss_vol) self.volslider.setVisible(False) # 上一首 self.prevBtn = QPushButton("",songer_img) self.prevBtn.setGeometry(85,159,20,20) self.prevBtn.setStyleSheet("QPushButton{ border-image:url(image/newimg/statusbar_btn_prev.png);border:none } QPushButton:hover{ border-image:url(image/newimg/statusbar_btn_prev_2.png)}") # 播放模式 self.playmodel = QPushButton("",songer_img) self.playmodel.setGeometry(35,156,25,25) self.playmodel.setStyleSheet("QPushButton{ border-image:url(image/newimg/allmodel.png);border:none } QPushButton:hover{ border-image:url(image/newimg/allmodel_2.png)}") self.playmodel.clicked.connect(self.moshi) # 当前播放时间 self.songTime = QLabel("",self) self.songTime.setGeometry(240,180,80,20) self.songTime.setStyleSheet("QLabel{ color:#AAA;font-size:12px;}") self.songTime.setAlignment(Qt.AlignHCenter) # 当前歌曲名 self.currentMusicName = QLabel("",songer_img) self.currentMusicName.setGeometry(0,180,200,20) self.currentMusicName.setStyleSheet("QLabel{ color:white ;font-weight:100;font-size:12px;margin-left:5px;}") # 歌曲进度条 self.processSlider = QSlider(Qt.Horizontal,self) self.processSlider.setGeometry(0,193,300,7) # self.processSlider.setRange(1,100) self.processSlider.setValue(0) self.processSlider.setStyleSheet(qss_process_slider) self.processSlider.setCursor(QCursor(Qt.UpArrowCursor)) # 歌曲列表 --------------------------- listWgt = QWidget(self) listWgt.setGeometry(0, 200, 300,380) listWgt.setStyleSheet(qss_scrollbar) #列表 self.songList = QListWidget(listWgt) self.songList.setGeometry(5,0,235,380) self.songList.setStyleSheet(qss_songlist) # 列表添加右键菜单 # self.songList.setContextMenuPolicy(Qt.CustomContextMenu) # self.songList.customContextMenuRequested.connect(self.rightMenuShow) #歌曲列表右边的功能列表 funcList = QListWidget(listWgt) funcList.setGeometry(240,0,55,380) funcList.setStyleSheet(qss_menu) btn = QPushButton("",funcList) btn.clicked.connect(self.newwindow) btn.setGeometry(15,10,30,30) btn.setStyleSheet("QPushButton{ border-image:url(image/home.png)} \ QPushButton:hover{ border-image:url(image/homehover.png) }") # btn.setCursor(QCursor(Qt.PointingHandCursor)) btn = QPushButton("",funcList) btn.setGeometry(15,60,30,30) btn.setStyleSheet("QPushButton{ border-image:url(image/tuijian.png) } \ QPushButton:hover{ border-image:url(image/tuijianhover.png) }") # btn.setCursor(QCursor(Qt.PointingHandCursor)) btn = QPushButton("",funcList) btn.setGeometry(15,100,30,30) btn.setStyleSheet("QPushButton{ border-image:url(image/shoucang.png) }\QPushButton:hover{ border-image:url(image/shoucanghover.png) }") # btn.setCursor(QCursor(Qt.PointingHandCursor)) btn = QPushButton("",funcList) btn.setGeometry(15,140,30,30) btn.setStyleSheet("QPushButton{ border-image:url(image/rizhi.png) }\ QPushButton:hover{ border-image:url(image/rizhihover.png) }") # btn.setCursor(QCursor(Qt.PointingHandCursor)) btn = QPushButton("",funcList) btn.setGeometry(17,180,30,30) btn.setStyleSheet("QPushButton{ border-image:url(image/mv.png) }\ QPushButton:hover{ border-image:url(image/mvhover.png) }") # btn.setCursor(QCursor(Qt.PointingHandCursor)) setbtn = QPushButton("",funcList) setbtn.setGeometry(15,225,33,33) setbtn.setStyleSheet("QPushButton{ border-image:url(image/settinghover.png) }\ QPushButton:hover{ border-image:url(image/setting.png) }") setbtn.clicked.connect(self.openseting) #底部状态栏 wg = QWidget(self) wg.setGeometry(0, 580, 300,20) wg.setStyleSheet("QWidget{ background:#2D2D2D; } ") # ql = QLabel(" <a style='color:#444;text-decoration:none;font-size:12px;' href ='https://github.com/codeAB/music-player' >S Y L </a>",wg) # ql.resize(300,20) # ql.setAlignment(Qt.AlignRight) # ql.linkActivated.connect(self.openurl) #设置托盘图标 tray = QSystemTrayIcon(self) tray.setIcon(QIcon('image/tray.png')) self.trayIconMenu = QMenu(self) self.trayIconMenu.setStyleSheet(qss_tray) showAction = QAction(QIcon('image/tray.png'),u"显示主面板", self,triggered=self.show) self.trayIconMenu.addAction(showAction) # self.trayIconMenu.addAction(preAction) # self.trayIconMenu.addAction(pauseAction) # self.trayIconMenu.addAction(nextAction) # self.trayIconMenu.addAction(quitAction) tray.setContextMenu(self.trayIconMenu) tray.show() tray.activated.connect(self.dbclick_tray)
class MainWindow(MainWindow_Ui): def __init__(self): super().__init__() self.statusbar.showMessage('Please Wait ...') #threads self.threadPool=[] #starting aria start_aria = StartAria2Thread() self.threadPool.append(start_aria) self.threadPool[0].start() self.threadPool[0].ARIA2RESPONDSIGNAL.connect(self.startAriaMessage) #initializing #add downloads to the download_table f_download_list_file = Open(download_list_file) download_list_file_lines = f_download_list_file.readlines() f_download_list_file.close() for line in download_list_file_lines: gid = line.strip() self.download_table.insertRow(0) download_info_file = download_info_folder + "/" + gid f = Open(download_info_file) download_info_file_lines = f.readlines() f.close() for i in range(10): item = QTableWidgetItem(download_info_file_lines[i].strip()) self.download_table.setItem(0 , i , item) row_numbers = self.download_table.rowCount() for row in range(row_numbers): status = self.download_table.item(row , 1).text() if (status != "complete" and status != "error"): gid = self.download_table.item(row,8).text() add_link_dictionary_str = self.download_table.item(row,9).text() add_link_dictionary = ast.literal_eval(add_link_dictionary_str.strip()) add_link_dictionary['start_hour'] = None add_link_dictionary['start_minute'] = None add_link_dictionary['end_hour'] = None add_link_dictionary['end_minute'] = None add_link_dictionary['after_download'] = 'None' download_info_file = download_info_folder + "/" + gid f = Open(download_info_file) download_info_file_lines = f.readlines() f.close() f = Open(download_info_file , "w") for i in range(10): if i == 1 : f.writelines("stopped" + "\n") item = QTableWidgetItem('stopped') self.download_table.setItem(row , i , item ) elif i == 9 : f.writelines(str(add_link_dictionary) + "\n") item = QTableWidgetItem(str(add_link_dictionary)) self.download_table.setItem(row,i , item) else: f.writelines(download_info_file_lines[i].strip() + "\n") f.close() self.addlinkwindows_list = [] self.propertieswindows_list = [] self.progress_window_list = [] self.progress_window_list_dict = {} check_download_info = CheckDownloadInfoThread() self.threadPool.append(check_download_info) self.threadPool[1].start() self.threadPool[1].DOWNLOAD_INFO_SIGNAL.connect(self.checkDownloadInfo) check_selected_row = CheckSelectedRowThread() self.threadPool.append(check_selected_row) self.threadPool[2].start() self.threadPool[2].CHECKSELECTEDROWSIGNAL.connect(self.checkSelectedRow) check_flashgot = CheckFlashgot() self.threadPool.append(check_flashgot) self.threadPool[3].start() self.threadPool[3].CHECKFLASHGOTSIGNAL.connect(self.checkFlashgot) self.system_tray_icon = QSystemTrayIcon() self.system_tray_icon.setIcon(QIcon('icon')) system_tray_menu = QMenu() system_tray_menu.addAction(self.addlinkAction) system_tray_menu.addAction(self.pauseAllAction) system_tray_menu.addAction(self.stopAllAction) system_tray_menu.addAction(self.minimizeAction) system_tray_menu.addAction(self.exitAction) self.system_tray_icon.setContextMenu(system_tray_menu) self.system_tray_icon.activated.connect(self.systemTrayPressed) self.system_tray_icon.show() def startAriaMessage(self,message): global aria_startup_answer if message == 'yes': sleep (2) self.statusbar.showMessage('Ready...') aria_startup_answer = 'ready' else: self.statusbar.showMessage('Error...') notifySend('Persepolis can not connect to Aria2' , 'Restart Persepolis' ,10000,'critical' ) def checkDownloadInfo(self,gid): try: #get download information from download_info_file according to gid and write them in download_table cells download_info_file = config_folder + "/download_info/" + gid f = Open(download_info_file) download_info_file_lines = f.readlines() f.close() #finding row of this gid! for i in range(self.download_table.rowCount()): row_gid = self.download_table.item(i , 8).text() if gid == row_gid : row = i break for i in range(10): #remove gid of completed download from active downloads list file if i == 1 : status = download_info_file_lines[i].strip() status = str(status) status_download_table = str(self.download_table.item(row , 1 ) . text()) if status == "complete": f = Open(download_list_file_active) download_list_file_active_lines = f.readlines() f.close() f = Open(download_list_file_active , "w") for line in download_list_file_active_lines : if line.strip() != gid : f.writelines(line.strip() + "\n") f.close() #update download_table cells item = QTableWidgetItem(download_info_file_lines[i].strip()) self.download_table.setItem(row , i , item) self.download_table.viewport().update() #update progresswindow try : member_number = self.progress_window_list_dict[gid] progress_window = self.progress_window_list[member_number] #link add_link_dictionary_str = str(download_info_file_lines[9].strip()) add_link_dictionary = ast.literal_eval(add_link_dictionary_str) link = "<b>Link</b> : " + str(add_link_dictionary ['link']) progress_window.link_label.setText(link) progress_window.setToolTip(link) #Save as final_download_path = add_link_dictionary['final_download_path'] if final_download_path == None : final_download_path = str(add_link_dictionary['download_path']) save_as = "<b>Save as</b> : " + final_download_path + "/" + str(download_info_file_lines[0].strip()) progress_window.save_label.setText(save_as) file_name = str(download_info_file_lines[0].strip()) if file_name != "***": progress_window.setWindowTitle(file_name ) #status progress_window.status = download_info_file_lines[1].strip() status = "<b>status</b> : " + progress_window.status progress_window.status_label.setText(status) if progress_window.status == "downloading": progress_window.resume_pushButton.setEnabled(False) progress_window.stop_pushButton.setEnabled(True) progress_window.pause_pushButton.setEnabled(True) elif progress_window.status == "paused": progress_window.resume_pushButton.setEnabled(True) progress_window.stop_pushButton.setEnabled(True) progress_window.pause_pushButton.setEnabled(False) elif progress_window.status == "waiting": progress_window.resume_pushButton.setEnabled(False) progress_window.stop_pushButton.setEnabled(False) progress_window.pause_pushButton.setEnabled(False) elif progress_window.status == "scheduled": progress_window.resume_pushButton.setEnabled(False) progress_window.stop_pushButton.setEnabled(True) progress_window.pause_pushButton.setEnabled(False) elif progress_window.status == "stopped" or progress_window.status == "error" or progress_window.status == "complete" : progress_window.close() self.progress_window_list[member_number] = [] del self.progress_window_list_dict[gid] if progress_window.status == "complete": notifySend("Download Complete" ,str(download_info_file_lines[0]) , 10000 , 'ok' ) elif progress_window.status == "stopped": notifySend("Download Stopped" , str(download_info_file_lines[0]) , 10000 , 'no') elif progress_window.status == "error": notifySend("Download Error" , str(download_info_file_lines[0]) , 10000 , 'fail') add_link_dictionary['start_hour'] = None add_link_dictionary['start_minute'] = None add_link_dictionary['end_hour'] = None add_link_dictionary['end_minute'] = None add_link_dictionary['after_download'] = 'None' f = Open(download_info_file , "w") for i in range(10): if i == 9 : f.writelines(str(add_link_dictionary) + "\n") else: f.writelines(download_info_file_lines[i].strip() + "\n") f.close() if os.path.isfile('/tmp/persepolis/shutdown/' + gid ) == True and progress_window.status != 'stopped': answer = download.shutDown() if answer == 'error': os.system('killall aria2c') f = Open('/tmp/persepolis/shutdown/' + gid , 'w') f.writelines('shutdown') f.close() elif os.path.isfile('/tmp/persepolis/shutdown/' + gid ) == True and progress_window.status == 'stopped': f = Open('/tmp/persepolis/shutdown/' + gid , 'w') f.writelines('canceled') f.close() #downloaded downloaded = "<b>Downloaded</b> : " + str(download_info_file_lines[3].strip()) + "/" + str(download_info_file_lines[2].strip()) progress_window.downloaded_label.setText(downloaded) #Transfer rate rate = "<b>Transfer rate</b> : " + str(download_info_file_lines[6].strip()) progress_window.rate_label.setText(rate) #Estimate time left estimate_time_left = "<b>Estimate time left</b> : " + str(download_info_file_lines[7].strip()) progress_window.time_label.setText(estimate_time_left) #Connections connections = "<b>Connections</b> : " + str(download_info_file_lines[5].strip()) progress_window.connections_label.setText(connections) #progressbar value = download_info_file_lines[4].strip() value = value[:-1] progress_window.download_progressBar.setValue(int(value)) except : pass except: pass #contex menu def contextMenuEvent(self, event): self.tablewidget_menu = QMenu(self) self.tablewidget_menu.addAction(self.resumeAction) self.tablewidget_menu.addAction(self.pauseAction) self.tablewidget_menu.addAction(self.stopAction) self.tablewidget_menu.addAction(self.removeAction) self.tablewidget_menu.addAction(self.propertiesAction) self.tablewidget_menu.addAction(self.progressAction) self.tablewidget_menu.popup(QtGui.QCursor.pos()) #drag and drop for links def dragEnterEvent(self, droplink): text = str(droplink.mimeData().text()) if ("tp:/" in text[2:6]) or ("tps:/" in text[2:7]) : droplink.accept() else: droplink.ignore() def dropEvent(self, droplink): link_clipborad = QApplication.clipboard() link_clipborad.clear(mode=link_clipborad.Clipboard ) link_string = droplink.mimeData().text() link_clipborad.setText(str(link_string), mode=link_clipborad.Clipboard) self.addLinkButtonPressed(button =link_clipborad ) def gidGenerator(self): my_gid = hex(random.randint(1152921504606846976,18446744073709551615)) my_gid = my_gid[2:18] my_gid = str(my_gid) f = Open(download_list_file_active) active_gid_list = f.readlines() f.close() while my_gid in active_gid_list : my_gid = self.gidGenerator() active_gids = download.activeDownloads() while my_gid in active_gids: my_gid = self.gidGenerator() return my_gid def selectedRow(self): try: item = self.download_table.selectedItems() selected_row_return = self.download_table.row(item[1]) download_info = self.download_table.item(selected_row_return , 9).text() download_info = ast.literal_eval(download_info) link = download_info['link'] self.statusbar.showMessage(str(link)) except : selected_row_return = None return selected_row_return def checkSelectedRow(self): try: item = self.download_table.selectedItems() selected_row_return = self.download_table.row(item[1]) except : selected_row_return = None if selected_row_return != None : status = self.download_table.item(selected_row_return , 1).text() if status == "scheduled": self.resumeAction.setEnabled(False) self.pauseAction.setEnabled(False) self.stopAction.setEnabled(True) self.removeAction.setEnabled(False) self.propertiesAction.setEnabled(False) self.progressAction.setEnabled(True) elif status == "stopped" or status == "error" : self.stopAction.setEnabled(False) self.pauseAction.setEnabled(False) self.resumeAction.setEnabled(True) self.removeAction.setEnabled(True) self.propertiesAction.setEnabled(True) self.progressAction.setEnabled(False) elif status == "downloading": self.resumeAction.setEnabled(False) self.pauseAction.setEnabled(True) self.stopAction.setEnabled(True) self.removeAction.setEnabled(False) self.propertiesAction.setEnabled(False) self.progressAction.setEnabled(True) elif status == "waiting": self.stopAction.setEnabled(False) self.resumeAction.setEnabled(False) self.pauseAction.setEnabled(False) self.removeAction.setEnabled(False) self.propertiesAction.setEnabled(False) self.progressAction.setEnabled(True) elif status == "complete": self.stopAction.setEnabled(False) self.resumeAction.setEnabled(False) self.pauseAction.setEnabled(False) self.removeAction.setEnabled(True) self.propertiesAction.setEnabled(True) self.progressAction.setEnabled(False) elif status == "paused": self.stopAction.setEnabled(True) self.resumeAction.setEnabled(True) self.pauseAction.setEnabled(False) self.removeAction.setEnabled(False) self.propertiesAction.setEnabled(False) self.progressAction.setEnabled(True) else: self.resumeAction.setEnabled(True) self.stopAction.setEnabled(True) self.pauseAction.setEnabled(True) self.propertiesAction.setEnabled(True) else: self.resumeAction.setEnabled(True) self.stopAction.setEnabled(True) self.pauseAction.setEnabled(True) self.removeAction.setEnabled(True) self.propertiesAction.setEnabled(True) def checkFlashgot(self): sleep(0.5) flashgot_file = Open("/tmp/persepolis-flashgot") flashgot_line = flashgot_file.readlines() flashgot_file.close() flashgot_file.remove() flashgot_add_link_dictionary_str = flashgot_line[0] flashgot_add_link_dictionary = ast.literal_eval(flashgot_add_link_dictionary_str) self.flashgotAddLink(flashgot_add_link_dictionary) def flashgotAddLink(self,flashgot_add_link_dictionary): addlinkwindow = AddLinkWindow(self.callBack , flashgot_add_link_dictionary) self.addlinkwindows_list.append(addlinkwindow) self.addlinkwindows_list[len(self.addlinkwindows_list) - 1].show() def addLinkButtonPressed(self ,button): addlinkwindow = AddLinkWindow(self.callBack) self.addlinkwindows_list.append(addlinkwindow) self.addlinkwindows_list[len(self.addlinkwindows_list) - 1].show() def callBack(self , add_link_dictionary): gid = self.gidGenerator() download_info_file_list = ['***','waiting','***','***','***','***','***','***',gid , str(add_link_dictionary)] download_info_file = config_folder + "/download_info/" + gid os.system("touch " + download_info_file ) f = Open(download_info_file , "w") for i in range(10): f.writelines(download_info_file_list[i] + "\n") f.close() self.download_table.insertRow(0) j = 0 for i in download_info_file_list : item = QTableWidgetItem(i) self.download_table.setItem(0,j,item) j = j + 1 f = Open (download_list_file , "a") f.writelines(gid + "\n") f.close() f = Open (download_list_file_active , "a") f.writelines(gid + "\n") f.close() new_download = DownloadLink(gid) self.threadPool.append(new_download) self.threadPool[len(self.threadPool) - 1].start() self.progressBarOpen(gid) if add_link_dictionary['start_hour'] == None : message = "Download Starts" else: message = "Download Scheduled" notifySend(message ,'' , 10000 , 'no') def resumeButtonPressed(self,button): selected_row_return = self.selectedRow() if selected_row_return != None: gid = self.download_table.item(selected_row_return , 8 ).text() download_status = self.download_table.item(selected_row_return , 1).text() if download_status == "paused" : answer = download.downloadUnpause(gid) if answer == 'None': notifySend("Aria2 did not respond!","Try agian!",10000,'warning' ) else: new_download = DownloadLink(gid) self.threadPool.append(new_download) self.threadPool[len(self.threadPool) - 1].start() sleep(1) self.progressBarOpen(gid) else: self.statusbar.showMessage("Please select an item first!") def stopButtonPressed(self,button): selected_row_return = self.selectedRow() if selected_row_return != None: gid = self.download_table.item(selected_row_return , 8 ).text() answer = download.downloadStop(gid) if answer == 'None': notifySend("Aria2 did not respond!","Try agian!" , 10000 , 'critical' ) else: self.statusbar.showMessage("Please select an item first!") def pauseButtonPressed(self,button): selected_row_return = self.selectedRow() if selected_row_return != None: gid = self.download_table.item(selected_row_return , 8 ).text() answer = download.downloadPause(gid) if answer == 'None': notifySend("Aria2 did not respond!" , "Try agian!" , 10000 , 'critical' ) else: self.statusbar.showMessage("Please select an item first!") sleep(1) def removeButtonPressed(self,button): self.removeAction.setEnabled(False) selected_row_return = self.selectedRow() if selected_row_return != None: gid = self.download_table.item(selected_row_return , 8 ).text() try: file_name = self.download_table.item(selected_row_return , 0).text() except: file_name = None sleep(0.5) self.download_table.removeRow(selected_row_return) #remove gid of download from download list file f = Open(download_list_file) download_list_file_lines = f.readlines() f.close() f = Open(download_list_file , "w") for i in download_list_file_lines: if i.strip() != gid: f.writelines(i.strip() + "\n") f.close() #remove gid of download from active download list file f = Open(download_list_file_active) download_list_file_active_lines = f.readlines() f.close() f = Open(download_list_file_active , "w") for i in download_list_file_active_lines: if i.strip() != gid: f.writelines(i.strip() + "\n") f.close() #remove download_info_file download_info_file = download_info_folder + "/" + gid f = Open(download_info_file) f.close() f.remove() #remove file of download form download temp folder if file_name != None : file_name_path = temp_download_folder + "/" + str(file_name) os.system('rm "' + str(file_name_path) +'"') file_name_aria = file_name_path + str('.aria2') os.system('rm "' + str(file_name_aria) +'"') else: self.statusbar.showMessage("Please select an item first!") self.selectedRow() def propertiesButtonPressed(self,button): selected_row_return = self.selectedRow() if selected_row_return != None : add_link_dictionary_str = self.download_table.item(selected_row_return , 9).text() add_link_dictionary = ast.literal_eval(add_link_dictionary_str) gid = self.download_table.item(selected_row_return , 8 ).text() propertieswindow = PropertiesWindow(self.propertiesCallback ,gid) self.propertieswindows_list.append(propertieswindow) self.propertieswindows_list[len(self.propertieswindows_list) - 1].show() def propertiesCallback(self,add_link_dictionary , gid ): download_info_file = download_info_folder + "/" + gid f = Open(download_info_file) download_info_file_lines = f.readlines() f.close() f = Open(download_info_file , "w") for i in range(10): if i == 9 : f.writelines(str(add_link_dictionary) + "\n") else: f.writelines(download_info_file_lines[i].strip() + "\n") f.close() def progressButtonPressed(self,button): selected_row_return = self.selectedRow() if selected_row_return != None: gid = self.download_table.item(selected_row_return , 8 ).text() member_number = self.progress_window_list_dict[gid] if self.progress_window_list[member_number].isVisible() == False: self.progress_window_list[member_number].show() else : self.progress_window_list[member_number].hide() def progressBarOpen(self,gid): progress_window = ProgressWindow(gid) self.progress_window_list.append(progress_window) member_number = len(self.progress_window_list) - 1 self.progress_window_list_dict[gid] = member_number self.progress_window_list[member_number].show() #close event def closeEvent(self, event): self.hide() self.system_tray_icon.hide() download.shutDown() sleep(0.5) global shutdown_notification shutdown_notification = 1 while shutdown_notification != 2: sleep (0.1) QCoreApplication.instance().closeAllWindows() for qthread in self.threadPool : try: qthread.exit(0) sleep(0.1) answer = qthread.isRunning() print(answer) except: print("not quit") QCoreApplication.instance().quit print("Persepolis Closed") def systemTrayPressed(self,click): if click == 3 : self.minMaxTray(click) def minMaxTray(self,menu): if self.isVisible() == False: self.minimizeAction.setText('Minimize to system tray') self.minimizeAction.setIcon(QIcon(icons + 'minimize')) self.show() else : self.hide() self.minimizeAction.setText('Show main Window') self.minimizeAction.setIcon(QIcon(icons + 'window')) def stopAllDownloads(self,menu): active_gids = [] for i in range(self.download_table.rowCount()): try: row_status = self.download_table.item(i , 1).text() if row_status == 'downloading' or row_status == 'paused' or row_status == 'waiting': row_gid = self.download_table.item(i , 8).text() active_gids.append(row_gid) except : pass for gid in active_gids: answer = download.downloadStop(gid) if answer == 'None': notifySend("Aria2 did not respond!" , "Try agian!" , 10000 , 'critical' ) sleep(0.3) def pauseAllDownloads(self,menu): #get active gid of downloads from aria active_gids = download.activeDownloads() #check that if gid is in download_list_file_active f = Open(download_list_file_active) download_list_file_active_lines = f.readlines() f.close() for i in range(len(download_list_file_active_lines)): download_list_file_active_lines[i] = download_list_file_active_lines[i].strip() for gid in active_gids : if gid in download_list_file_active_lines : answer = download.downloadPause(gid) if answer == 'None': notifySend("Aria2 did not respond!" , "Try agian!" , 10000 , 'critical' ) sleep(0.3) def openPreferences(self,menu): self.preferenceswindow = PreferencesWindow() self.preferenceswindow.show() def openAbout(self,menu): self.about_window = AboutWindow() self.about_window.show()
if dialog.exec_(): color = dialog.currentColor() clipboard.setText("rgb(%d, %d, %d)" % (color.red(), color.green(), color.blue())) def copy_color_hsv(): if dialog.exec_(): color = dialog.currentColor() clipboard.setText("hsv(%d, %d, %d)" % (color.hue(), color.saturation(), color.value())) # Create the tray tray = QSystemTrayIcon() tray.setIcon(icon) tray.setVisible(True) # Create the menu menu = QMenu() action1 = QAction("Hex") action1.triggered.connect(copy_color_hex) action2 = QAction("RGB") action2.triggered.connect(copy_color_rgb) action3 = QAction("HSV") action3.triggered.connect(copy_color_hsv) quit = QAction("Quit") quit.triggered.connect(app.quit)
class MainWindow(QMainWindow, Ui_MainWindow): def __init__(self): super().__init__( None, Qt.WindowMinimizeButtonHint | Qt.WindowCloseButtonHint | Qt.MSWindowsFixedSizeDialogHint) self.setupUi(self) self.tray = QSystemTrayIcon(self) self.tray.setIcon(QIcon(':/icon/sina.ico')) self.tray.setToolTip('ReadTheWeibo') self.tray.activated.connect(self._on_tray_activate) self.read_the_weibo = ReadTheWeibo(self) self.load_settings() self.read_the_weibo.start() def setupUi(self, main_window): super().setupUi(main_window) self.setFixedSize(self.width(), self.height()) self.speak_check.stateChanged.connect(self._on_speak_check_change) self.show_check.stateChanged.connect(self._on_show_check_change) def closeEvent(self, event): self.read_the_weibo.stop() self.read_the_weibo.save_session() self.save_settings() QCoreApplication.quit() def load_settings(self): try: with open(SETTINGS_PATH) as f: settings = json.load(f) self.speak_check.setChecked(settings['speak_post']) self.show_check.setChecked(settings['show_post']) except OSError: # 打开文件错误 pass except KeyError: pass except json.JSONDecodeError: logger.exception('读取设置时出错:') def save_settings(self): try: settings = { 'speak_post': self.speak_check.isChecked(), 'show_post': self.show_check.isChecked(), } with open(SETTINGS_PATH, 'w') as f: json.dump(settings, f, indent=4) except OSError: # 打开文件错误 pass def changeEvent(self, event): # 最小化到托盘 if (event.type() == QEvent.WindowStateChange and int(self.windowState()) & Qt.WindowMinimized): self.hide() self.tray.show() def _on_tray_activate(self, reason): # 单击托盘时恢复窗口 if reason == QSystemTrayIcon.Trigger: self.show() self.setWindowState( Qt.WindowState((int(self.windowState()) & ~Qt.WindowMinimized) | Qt.WindowActive)) self.tray.hide() def _on_speak_check_change(self, state): self.read_the_weibo.speak_post = state != Qt.Unchecked def _on_show_check_change(self, state): self.read_the_weibo.show_post = state != Qt.Unchecked