Beispiel #1
0
class Icon(QWidget):
    def __init__(self, app, newEntry, listWin):
        super().__init__()
        self.newEntry = newEntry
        self.listWin = listWin
        self.app = app
        self.initUI()

    def initUI(self):
        
        menu = QMenu()
        Ajouter = QAction(QIcon(''), '&Ajouter un tag', menu)
        Ajouter.triggered.connect(self.newEntry.show)
        menu.addAction(Ajouter)

        ouvrir = QAction(QIcon(''), '&Ouvrir', menu)
        ouvrir.triggered.connect(self.listWin.show)
        menu.addAction(ouvrir)

        Quitter = QAction(QIcon(''), '&Quitter', menu)
        Quitter.triggered.connect(self.app.exit)
        menu.addAction(Quitter)

        self.icon = QSystemTrayIcon()
        self.icon.setIcon(QIcon('./icone.png'))
        self.icon.setContextMenu(menu)
        self.icon.show()
Beispiel #2
0
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()
Beispiel #3
0
 def _createTray(self):
     from PyQt5.QtWidgets import QSystemTrayIcon
     from PyQt5.QtGui import QIcon
     from piony.common.system import expand_pj
     tray = QSystemTrayIcon()
     tray.setIcon(QIcon(expand_pj(":/res/tray-normal.png")))
     tray.show()
     return tray
Beispiel #4
0
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()
Beispiel #5
0
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()
Beispiel #6
0
class SystemTrayIcon(QMainWindow):
    def __init__(self, parent=None):
        super(SystemTrayIcon, self).__init__(parent)
        self.setAttribute(Qt.WA_DeleteOnClose)
        self.settings = QSettings()
        self.language = self.settings.value('Language') or ''
        self.temp_decimal_bool = self.settings.value('Decimal') or False
        # initialize the tray icon type in case of first run: issue#42
        self.tray_type = self.settings.value('TrayType') or 'icon&temp'
        cond = conditions.WeatherConditions()
        self.temporary_city_status = False
        self.conditions = cond.trans
        self.clouds = cond.clouds
        self.wind = cond.wind
        self.wind_dir = cond.wind_direction
        self.wind_codes = cond.wind_codes
        self.inerror = False
        self.tentatives = 0
        self.baseurl = 'http://api.openweathermap.org/data/2.5/weather?id='
        self.accurate_url = 'http://api.openweathermap.org/data/2.5/find?q='
        self.forecast_url = ('http://api.openweathermap.org/data/2.5/forecast/'
                             'daily?id=')
        self.day_forecast_url = ('http://api.openweathermap.org/data/2.5/'
                                 'forecast?id=')
        self.wIconUrl = 'http://openweathermap.org/img/w/'
        apikey = self.settings.value('APPID') or ''
        self.appid = '&APPID=' + apikey
        self.forecast_icon_url = self.wIconUrl
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.refresh)
        self.menu = QMenu()
        self.citiesMenu = QMenu(self.tr('Cities'))
        self.tempCityAction = QAction(self.tr('&Temporary city'), self)
        self.refreshAction = QAction(self.tr('&Update'), self)
        self.settingsAction = QAction(self.tr('&Settings'), self)
        self.aboutAction = QAction(self.tr('&About'), self)
        self.exitAction = QAction(self.tr('Exit'), self)
        self.exitAction.setIcon(QIcon(':/exit'))
        self.aboutAction.setIcon(QIcon(':/info'))
        self.refreshAction.setIcon(QIcon(':/refresh'))
        self.settingsAction.setIcon(QIcon(':/configure'))
        self.tempCityAction.setIcon(QIcon(':/tempcity'))
        self.citiesMenu.setIcon(QIcon(':/bookmarks'))
        self.menu.addAction(self.settingsAction)
        self.menu.addAction(self.refreshAction)
        self.menu.addMenu(self.citiesMenu)
        self.menu.addAction(self.tempCityAction)
        self.menu.addAction(self.aboutAction)
        self.menu.addAction(self.exitAction)
        self.settingsAction.triggered.connect(self.config)
        self.exitAction.triggered.connect(qApp.quit)
        self.refreshAction.triggered.connect(self.manual_refresh)
        self.aboutAction.triggered.connect(self.about)
        self.tempCityAction.triggered.connect(self.tempcity)
        self.systray = QSystemTrayIcon()
        self.systray.setContextMenu(self.menu)
        self.systray.activated.connect(self.activate)
        self.systray.setIcon(QIcon(':/noicon'))
        self.systray.setToolTip(self.tr('Searching weather data...'))
        self.notification = ''
        self.notification_temp = 0
        self.notifications_id = ''
        self.systray.show()
        # The dictionnary has to be intialized here. If there is an error
        # the program couldn't become functionnal if the dictionnary is
        # reinitialized in the weatherdata method
        self.weatherDataDico = {}
        # The traycolor has to be initialized here for the case when we cannot
        # reach the tray method (case: set the color at first time usage)
        self.traycolor = ''
        self.refresh()

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

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

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

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

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

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

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

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

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

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

    def forecast(self, data):
        self.forecast_data = data

    def dayforecast(self, data):
        self.dayforecast_data = data

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

    def remove_object(self):
        del self.overviewcity

    def done(self, done):
        if done == 0:
            self.inerror = False
        elif done == 1:
            self.inerror = True
            logging.debug('Trying to retrieve data ...')
            self.timer.singleShot(10000, self.try_again)
            return
        if hasattr(self, 'updateicon'):
            # Keep a reference of the image to update the icon in overview
            self.wIcon = self.updateicon
        if hasattr(self, 'forecast_data'):
            if hasattr(self, 'overviewcity'):
                # Update also the overview dialog if open
                if self.overviewcity.isVisible():
                    # delete dialog to prevent memory leak
                    self.overviewcity.close()
                    self.instance_overviewcity()
                    self.overview()
            elif self.window_visible is True:
                self.instance_overviewcity()
                self.overview()
            else:
                self.inerror = True
                self.try_create_overview()
        else:
            self.try_again()

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

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

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

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

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

    def weatherdata(self, tree):
        if self.inerror:
            return
        self.tempFloat = tree[1].get('value')
        self.temp = ' ' + str(round(float(self.tempFloat))) + '°'
        self.temp_decimal = '{0:.1f}'.format(float(self.tempFloat)) + '°'
        self.meteo = tree[8].get('value')
        meteo_condition = tree[8].get('number')
        try:
            self.meteo = self.conditions[meteo_condition]
        except:
            logging.debug('Cannot find localisation string for'
                          'meteo_condition:' + str(meteo_condition))
            pass
        clouds = tree[5].get('name')
        clouds_percent = tree[5].get('value') + '%'
        try:
            clouds = self.clouds[clouds]
            clouds = self.conditions[clouds]
        except:
            logging.debug('Cannot find localisation string for clouds:' +
                          str(clouds))
            pass
        wind = tree[4][0].get('name').lower()
        try:
            wind = self.wind[wind]
            wind = self.conditions[wind]
        except:
            logging.debug('Cannot find localisation string for wind:' +
                          str(wind))
            pass
        wind_codes = tree[4][2].get('code')
        try:
            wind_codes = self.wind_codes[wind_codes]
        except:
            logging.debug('Cannot find localisation string for wind_codes:' +
                          str(wind_codes))
            pass
        wind_dir = tree[4][2].get('name')
        try:
            wind_dir = self.wind_dir[tree[4][2].get('code')]
        except:
            logging.debug('Cannot find localisation string for wind_dir:' +
                          str(wind_dir))
            pass
        self.city_weather_info = (self.city + ' ' + self.country + ' ' +
                                  self.temp_decimal + ' ' + self.meteo)
        self.tooltip_weather()
        self.notification = self.city_weather_info
        self.weatherDataDico['City'] = self.city
        self.weatherDataDico['Country'] = self.country
        self.weatherDataDico['Temp'] = self.tempFloat + '°'
        self.weatherDataDico['Meteo'] = self.meteo
        self.weatherDataDico['Humidity'] = (tree[2].get('value'),
                                            tree[2].get('unit'))
        self.weatherDataDico['Wind'] = (
            tree[4][0].get('value'), wind, str(int(float(tree[4][2].get('value')))),
            wind_codes, wind_dir)
        self.weatherDataDico['Clouds'] = (clouds_percent + ' ' + clouds)
        self.weatherDataDico['Pressure'] = (tree[3].get('value'),
                                            tree[3].get('unit'))
        self.weatherDataDico['Humidity'] = (tree[2].get('value'),
                                            tree[2].get('unit'))
        self.weatherDataDico['Sunrise'] = tree[0][2].get('rise')
        self.weatherDataDico['Sunset'] = tree[0][2].get('set')
        rain_value = tree[7].get('value')
        if rain_value == None:
            rain_value = ''
        self.weatherDataDico['Precipitation'] = (tree[7].get('mode'), rain_value)

    def tooltip_weather(self):
        self.systray.setToolTip(self.city_weather_info)

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

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

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

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

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

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

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

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

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

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

        dialog = about_dlg.AboutDialog(title, text, image, contributors, self)
        dialog.exec_()
Beispiel #7
0
class Coropata(QLabel):
    clicked = pyqtSignal()

    def __init__(self): # Really not meant to have a parent yet
        QLabel.__init__(self, None)

        self.quitAction = None
        self.trayIconMenu = None
        self.trayIcon = None
        self.createTrayIcon()

        self.setWindowFlags(Qt.FramelessWindowHint |
                            Qt.WindowStaysOnTopHint |
                            Qt.BypassWindowManagerHint)
        self.setAttribute(Qt.WA_TranslucentBackground)

        self.m_hoverbox = Hoverbox()
        self.m_hoverbox.show()

        self.m_animation_timer = QTimer(self)
        self.m_animation_timer.setInterval(100)

        self.m_physics_timer = QTimer(self)
        self.m_physics_timer.setInterval(30)
        self.m_physics_timer.timeout.connect(self.physicsTimerEvent)

        self.screen_width = QDesktopWidget().screenGeometry().width()
        self.screen_height = QDesktopWidget().screenGeometry().height()

        # Initialize properties
        self.m_velocity = QPoint(0, 0)
        self.m_pos = QPoint(self.screen_width / 2, (5.0 / 6.0) * self.screen_height)
        self.m_offset = QPoint(0, 0)
        self.m_size = QSize(0, 0)

        self._velocity = self.m_velocity
        self._pos = self.m_pos
        self._offset = self.m_offset
        self._size = self.m_size

        self.stateMachine = xmlToStateMachine(self, 'coropata.xml')
        self.stateMachine.start()

        self.m_animation_timer.start()
        self.m_physics_timer.start()

    # Properties ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    @pyqtProperty(QSize)
    def _size(self):
        return self.m_size

    @_size.write
    def _size(self, value):
        self.m_size = value
        self.setFixedSize(value)

    @pyqtProperty(QPoint)
    def _offset(self):
        return self.m_offset

    @_offset.write
    def _offset(self, value):
        self.m_offset = value
        self.move(self.m_pos + self.m_offset)

    @pyqtProperty(QPoint)
    def _pos(self):
        return self.m_pos

    @_pos.write
    def _pos(self, value):
        self.m_pos = value
        self.move(self.m_pos + self.m_offset)
        self.m_hoverbox.move(self.m_pos - QPoint(60, 70))

    @pyqtProperty(QPoint)
    def _velocity(self):
        return self.m_velocity

    @_velocity.write
    def _velocity(self, value):
        self.m_velocity = value
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    def mousePressEvent(self, e):
        self.clicked.emit()

    def closeEvent(self, e):
        QApplication.instance().closeAllWindows()

    def keyPressEvent(self, e):
        if e.key() == Qt.Key_Q or e.key() == Qt.Key_Escape:
            QApplication.instance().closeAllWindows()

    def physicsTimerEvent(self):
        p = self.m_pos + self.m_velocity

        if p.x() < -self._size.width():
            p.setX(self.screen_width)
        elif p.x() > self.screen_width + 10:
            p.setX(-self._size.width())

        self._pos = p

    def createTrayIcon(self):
        self.quitAction = QAction('Quit', self)
        self.quitAction.triggered.connect(self.closeEvent)
        self.trayIconMenu = QMenu(self)
        self.trayIconMenu.addAction(self.quitAction)
        self.trayIcon = QSystemTrayIcon(self)
        self.trayIcon.setContextMenu(self.trayIconMenu)
        self.trayIcon.setIcon(QIcon(os.path.join('images', 'flower.png')))
        self.trayIcon.show()
class Qt4SysTrayIcon:
    def __init__(self):
        self.snapshots = snapshots.Snapshots()
        self.config = self.snapshots.config
        self.decode = None

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        self.ppid = os.getppid()

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

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

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

        self.qapp.processEvents()

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

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

        self.qapp.exec_()

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

        self.prepairExit()

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

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

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

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

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

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


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

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

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

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

    def onBtnStop(self):
        os.kill(self.snapshots.pid(), signal.SIGKILL)
        self.btnStop.setEnabled(False)
        self.btnPause.setEnabled(False)
        self.btnResume.setEnabled(False)
        self.snapshots.setTakeSnapshotMessage(0, 'Snapshot terminated')
Beispiel #9
0
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()
Beispiel #10
0
class Parse99(QApplication):

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

        # Tray Icon
        self._system_tray = QSystemTrayIcon()
        self._system_tray.setIcon(QIcon('ui/icon.png'))
        self._system_tray.setToolTip("Parse99")
        self._system_tray.show()

        # Settings
        self.settings = settings.Settings("parse99")

        # Plugins
        self._plugins = {'maps': Maps(self.settings)}

        # Timer
        self._timer = QTimer()
        self._timer.timeout.connect(self._parse)

        # Thread
        self._thread = None

        # Menu
        self._system_tray.setContextMenu(self._get_menu())

        # File
        self._log_file = ""
        self._file_size = 0
        self._last_line_read = 0
        self._log_new_lines = []

        # Start
        self.toggle('on')

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

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

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

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

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

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

    def edit_settings(self, **kwargs):
        try:
            if not self.settings.gui.isVisible():
                self.toggle('off')
                self.settings.gui.set_show_tab(kwargs.get('tab', None))
                self.settings.gui.exec()
                self.toggle('on')
        except Exception as e:
            print("parse99.edit_settings():", e)
Beispiel #11
0
class Window(QMainWindow):
    """
    Main GUI class for application
    """

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

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

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

        self.users = {}

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

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

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

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

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

        self.send_IAI()

        self.setup_tray_menu()

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

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

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

    def setup_tray_menu(self):

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

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

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

        # tray menu
        tray_menu = QMenu()
        tray_menu.addAction(show_action)
        tray_menu.addAction(hide_action)
        tray_menu.addAction(quit_action)
        self.tray_icon.setContextMenu(tray_menu)
        self.tray_icon.show()

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

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

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

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

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

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

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

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

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

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

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

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

        msg = self.teMsg.toPlainText()

        receiver_ip = self.users[receiver_host]

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

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

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

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

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

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

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

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

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

        self.teMsgsList.clear()

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

    def show_msgbox(self, title, text):
        """
        Function for showing error/info message box
        """
        msg = QMessageBox()
        msg.setIcon(QMessageBox.Information)
        msg.setText(text)
        msg.setWindowTitle(title)
        msg.setStandardButtons(QMessageBox.Ok)
        retval = msg.exec_()
Beispiel #12
0
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()
Beispiel #13
0
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
Beispiel #14
0
class Window(QMainWindow):
    """
    Main GUI class for application
    """
    def __init__(self):
        QWidget.__init__(self)

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

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

        self.users = {}

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

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

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

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

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

        self.send_IAI()

        self.setup_tray_menu()

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

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

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

    def setup_tray_menu(self):

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

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

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

        # tray menu
        tray_menu = QMenu()
        tray_menu.addAction(show_action)
        tray_menu.addAction(hide_action)
        tray_menu.addAction(quit_action)
        self.tray_icon.setContextMenu(tray_menu)
        self.tray_icon.show()

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

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

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

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

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

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

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

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

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

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

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

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

        msg = self.teMsg.toPlainText()

        receiver_ip = self.users[receiver_host]

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

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

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

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

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

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

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

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

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

        self.teMsgsList.clear()

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

    def show_msgbox(self, title, text):
        """
        Function for showing error/info message box
        """
        msg = QMessageBox()
        msg.setIcon(QMessageBox.Information)
        msg.setText(text)
        msg.setWindowTitle(title)
        msg.setStandardButtons(QMessageBox.Ok)
        retval = msg.exec_()
Beispiel #15
0
class TriblerWindow(QMainWindow):
    resize_event = pyqtSignal()
    escape_pressed = pyqtSignal()
    received_search_completions = pyqtSignal(object)

    def on_exception(self, *exc_info):
        if self.exception_handler_called:
            # We only show one feedback dialog, even when there are two consecutive exceptions.
            return

        self.exception_handler_called = True

        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()
Beispiel #16
0
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)
Beispiel #17
0
class MainWindow(QMainWindow):
    """
         Сheckbox and system tray icons.
         Will initialize in the constructor.
    """
    check_box = None
    tray_icon = None

    # Override the class constructor
    def __init__(self):
        # 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)
Beispiel #18
0
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)
Beispiel #19
0
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)
Beispiel #20
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)
Beispiel #21
0
class PymodoroGUI(QWidget):
    """
        GUI for Pymodoro
    """
    def __init__(self):
        """
            Initializer for the Pomodoro GUI class
        """
        super(PymodoroGUI, self).__init__()

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        self.setLayout(grid)
        self.show()

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

    def update_timer_text(self, number):
        """
            Method run when setting the number of minutes in the timer
        """
        try:
            self.pom.set_timer_minutes(int(number))
            self.errortext.hide()
        except ValueError:
            self.errortext.setText("Please input a number")
            self.errortext.show()
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)
Beispiel #23
0
class ElectrumGui(PrintError):

    @profiler
    def __init__(self, config, daemon, plugins):
        set_language(config.get('language', get_default_language()))
        # Uncomment this call to verify objects are being properly
        # GC-ed when windows are closed
        #network.add_jobs([DebugMem([Abstract_Wallet, SPV, Synchronizer,
        #                            ElectrumWindow], interval=5)])
        QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads)
        if hasattr(QtCore.Qt, "AA_ShareOpenGLContexts"):
            QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_ShareOpenGLContexts)
        if hasattr(QGuiApplication, 'setDesktopFileName'):
            QGuiApplication.setDesktopFileName('electrum.desktop')
        self.gui_thread = threading.current_thread()
        self.config = config
        self.daemon = daemon
        self.plugins = plugins
        self.windows = []
        self.efilter = OpenFileEventFilter(self.windows)
        self.app = QElectrumApplication(sys.argv)
        self.app.installEventFilter(self.efilter)
        self.app.setWindowIcon(read_QIcon("electrum.png"))
        # timer
        self.timer = QTimer(self.app)
        self.timer.setSingleShot(False)
        self.timer.setInterval(500)  # msec

        self.nd = None
        self.network_updated_signal_obj = QNetworkUpdatedSignalObject()
        self._num_wizards_in_progress = 0
        self._num_wizards_lock = threading.Lock()
        # init tray
        self.dark_icon = self.config.get("dark_icon", False)
        self.tray = QSystemTrayIcon(self.tray_icon(), None)
        self.tray.setToolTip('Electrum')
        self.tray.activated.connect(self.tray_activated)
        self.build_tray_menu()
        self.tray.show()
        self.app.new_window_signal.connect(self.start_new_window)
        self.set_dark_theme_if_needed()
        run_hook('init_qt', self)

    def set_dark_theme_if_needed(self):
        use_dark_theme = self.config.get('qt_gui_color_theme', 'default') == 'dark'
        if use_dark_theme:
            try:
                import qdarkstyle
                self.app.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5())
            except BaseException as e:
                use_dark_theme = False
                self.print_error('Error setting dark theme: {}'.format(repr(e)))
        # Even if we ourselves don't set the dark theme,
        # the OS/window manager/etc might set *a dark theme*.
        # Hence, try to choose colors accordingly:
        ColorScheme.update_from_widget(QWidget(), force_dark=use_dark_theme)

    def build_tray_menu(self):
        # Avoid immediate GC of old menu when window closed via its action
        if self.tray.contextMenu() is None:
            m = QMenu()
            self.tray.setContextMenu(m)
        else:
            m = self.tray.contextMenu()
            m.clear()
        for window in self.windows:
            submenu = m.addMenu(window.wallet.basename())
            submenu.addAction(_("Show/Hide"), window.show_or_hide)
            submenu.addAction(_("Close"), window.close)
        m.addAction(_("Dark/Light"), self.toggle_tray_icon)
        m.addSeparator()
        m.addAction(_("Exit Electrum"), self.close)

    def tray_icon(self):
        if self.dark_icon:
            return read_QIcon('electrum_dark_icon.png')
        else:
            return read_QIcon('electrum_light_icon.png')

    def toggle_tray_icon(self):
        self.dark_icon = not self.dark_icon
        self.config.set_key("dark_icon", self.dark_icon, True)
        self.tray.setIcon(self.tray_icon())

    def tray_activated(self, reason):
        if reason == QSystemTrayIcon.DoubleClick:
            if all([w.is_hidden() for w in self.windows]):
                for w in self.windows:
                    w.bring_to_top()
            else:
                for w in self.windows:
                    w.hide()

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

    def new_window(self, path, uri=None):
        # Use a signal as can be called from daemon thread
        self.app.new_window_signal.emit(path, uri)

    def show_network_dialog(self, parent):
        if not self.daemon.network:
            parent.show_warning(_('You are using Electrum in offline mode; restart Electrum if you want to get connected'), title=_('Offline'))
            return
        if self.nd:
            self.nd.on_update()
            self.nd.show()
            self.nd.raise_()
            return
        self.nd = NetworkDialog(self.daemon.network, self.config,
                                self.network_updated_signal_obj)
        self.nd.show()

    def _create_window_for_wallet(self, wallet):
        w = ElectrumWindow(self, wallet)
        self.windows.append(w)
        self.build_tray_menu()
        # FIXME: Remove in favour of the load_wallet hook
        run_hook('on_new_window', w)
        w.warn_if_watching_only()
        return w

    def count_wizards_in_progress(func):
        def wrapper(self: 'ElectrumGui', *args, **kwargs):
            with self._num_wizards_lock:
                self._num_wizards_in_progress += 1
            try:
                return func(self, *args, **kwargs)
            finally:
                with self._num_wizards_lock:
                    self._num_wizards_in_progress -= 1
        return wrapper

    @count_wizards_in_progress
    def start_new_window(self, path, uri, *, app_is_starting=False):
        '''Raises the window for the wallet if it is open.  Otherwise
        opens the wallet and creates a new window for it'''
        wallet = None
        try:
            wallet = self.daemon.load_wallet(path, None)
        except BaseException as e:
            traceback.print_exc(file=sys.stdout)
            QMessageBox.warning(None, _('Error'),
                                _('Cannot load wallet') + ' (1):\n' + str(e))
            # if app is starting, still let wizard to appear
            if not app_is_starting:
                return
        if not wallet:
            wallet = self._start_wizard_to_select_or_create_wallet(path)
        if not wallet:
            return
        # create or raise window
        try:
            for window in self.windows:
                if window.wallet.storage.path == wallet.storage.path:
                    break
            else:
                window = self._create_window_for_wallet(wallet)
        except BaseException as e:
            traceback.print_exc(file=sys.stdout)
            QMessageBox.warning(None, _('Error'),
                                _('Cannot create window for wallet') + ':\n' + str(e))
            if app_is_starting:
                wallet_dir = os.path.dirname(path)
                path = os.path.join(wallet_dir, get_new_wallet_name(wallet_dir))
                self.start_new_window(path, uri)
            return
        if uri:
            window.pay_to_URI(uri)
        window.bring_to_top()
        window.setWindowState(window.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive)

        window.activateWindow()
        return window

    def _start_wizard_to_select_or_create_wallet(self, path) -> Optional[Abstract_Wallet]:
        wizard = InstallWizard(self.config, self.app, self.plugins)
        try:
            path, storage = wizard.select_storage(path, self.daemon.get_wallet)
            # storage is None if file does not exist
            if storage is None:
                wizard.path = path  # needed by trustedcoin plugin
                wizard.run('new')
                storage = wizard.create_storage(path)
            else:
                wizard.run_upgrades(storage)
        except (UserCancelled, GoBack):
            return
        except WalletAlreadyOpenInMemory as e:
            return e.wallet
        except (WalletFileException, BitcoinException) as e:
            traceback.print_exc(file=sys.stderr)
            QMessageBox.warning(None, _('Error'),
                                _('Cannot load wallet') + ' (2):\n' + str(e))
            return
        finally:
            wizard.terminate()
        # return if wallet creation is not complete
        if storage is None or storage.get_action():
            return
        wallet = Wallet(storage)
        wallet.start_network(self.daemon.network)
        self.daemon.add_wallet(wallet)
        return wallet

    def close_window(self, window):
        if window in self.windows:
           self.windows.remove(window)
        self.build_tray_menu()
        # save wallet path of last open window
        if not self.windows:
            self.config.save_last_wallet(window.wallet)
        run_hook('on_close_window', window)
        self.daemon.stop_wallet(window.wallet.storage.path)

    def init_network(self):
        # Show network dialog if config does not exist
        if self.daemon.network:
            if self.config.get('auto_connect') is None:
                wizard = InstallWizard(self.config, self.app, self.plugins)
                wizard.init_network(self.daemon.network)
                wizard.terminate()

    def main(self):
        try:
            self.init_network()
        except UserCancelled:
            return
        except GoBack:
            return
        except BaseException as e:
            traceback.print_exc(file=sys.stdout)
            return
        self.timer.start()
        self.config.open_last_wallet()
        path = self.config.get_wallet_path()
        if not self.start_new_window(path, self.config.get('url'), app_is_starting=True):
            return
        signal.signal(signal.SIGINT, lambda *args: self.app.quit())

        def quit_after_last_window():
            # keep daemon running after close
            if self.config.get('daemon'):
                return
            # check if a wizard is in progress
            with self._num_wizards_lock:
                if self._num_wizards_in_progress > 0 or len(self.windows) > 0:
                    return
            self.app.quit()
        self.app.setQuitOnLastWindowClosed(False)  # so _we_ can decide whether to quit
        self.app.lastWindowClosed.connect(quit_after_last_window)

        def clean_up():
            # Shut down the timer cleanly
            self.timer.stop()
            # clipboard persistence. see http://www.mail-archive.com/[email protected]/msg17328.html
            event = QtCore.QEvent(QtCore.QEvent.Clipboard)
            self.app.sendEvent(self.app.clipboard(), event)
            self.tray.hide()
        self.app.aboutToQuit.connect(clean_up)

        # main loop
        self.app.exec_()
        # on some platforms the exec_ call may not return, so use clean_up()

    def stop(self):
        self.print_error('closing GUI')
        self.app.quit()
Beispiel #24
0
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
Beispiel #25
0
class MyApp(QtWidgets.QMainWindow):
	mouseLeaveTimer=0

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

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

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

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

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

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

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

		#创建房间的Button和 输入框

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


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

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



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

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

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

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

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

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

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

	


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

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

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

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


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

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

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

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

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

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


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

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

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

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



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

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


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

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

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

	def exitApp(self):
		self.trayIcon.hide()
		sys.exit()
Beispiel #26
0
    @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_())
Beispiel #27
0
class MarksTimeTracker(QMainWindow, Ui_MainWindow):
    runningEvent = None

    def __init__(self, parent=None):
        super(MarksTimeTracker, self).__init__(parent)
        self.setupUi(self)
        self.tabWidget.tabBar().hide()
        self.setupStatusIcon()

        # config
        self.config_path = os.path.join(os.path.expanduser('~'), '.config', 'markstimetracker')
        dir_util.mkpath(self.config_path)
        self.readConfig()

        # Setup DB
        engine = create_engine('sqlite:///' + os.path.join(self.config_path, 'markstimetracker.db'))
        init_db(engine)
        self.db = sessionmaker(bind=engine)()

        self.updateTaskList()
        self.updateTasksComboBox()
        self.checkForRunningTask()

        # Timers
        timer = QTimer(self)
        timer.timeout.connect(self.updateTimeSpent)
        timer.start(1000)

        self.idleTimeTimer = QTimer()
        self.idleTimeTimer.timeout.connect(self.detectIdleTime)
        self.checkIdleTime()

        self.remindTimer = QTimer()
        self.remindTimer.timeout.connect(self.remindTracking)
        self.checkRemind()

        self.redmineSyncTimer = QTimer()
        self.redmineSyncTimer.timeout.connect(self.doRedmineSync)
        self.checkRedmineSync()

        # Events
        self.startButton.clicked.connect(self.toggleEventButton)
        self.eventsPeriodComboBox.currentIndexChanged.connect(self.eventsPeriodChanged)
        self.editDurationSpinBox.valueChanged.connect(self.updateEditStartEndTime)
        self.editStartDateTimeEdit.dateTimeChanged.connect(self.updateDurationSpinBoxEndTime)
        self.editEndDateTimeEdit.dateTimeChanged.connect(self.updateDurationSpinBox)
        self.editButtonBox.accepted.connect(self.saveEvent)
        self.editButtonBox.rejected.connect(lambda: self.tabWidget.setCurrentIndex(TAB_MAIN))
        self.settingsButtonBox.accepted.connect(self.saveSettings)
        self.settingsButtonBox.rejected.connect(lambda: self.tabWidget.setCurrentIndex(TAB_MAIN))
        self.settingsPushButton.clicked.connect(
            lambda: self.tabWidget.setCurrentIndex(TAB_SETTINGS))
        self.redmineSyncPushButton.clicked.connect(lambda: self.doRedmineSync(check=False))
        self.addEventPushButton.clicked.connect(self.addEvent)

        self.setupDbus()

    def setupStatusIcon(self):
        icon = QIcon()
        icon.addPixmap(QPixmap(":/clock.svg"), QIcon.Normal, QIcon.Off)
        self.statusIcon = QSystemTrayIcon(self)
        self.statusIcon.setIcon(icon)
        self.statusIcon.activated.connect(lambda: self.hide()
                                          if self.isVisible()
                                          else self.show())
        self.statusIcon.setToolTip("Mark's Time Tracker")
        self.statusIcon.show()

    def setupDbus(self):
        dbus_loop = DBusQtMainLoop(set_as_default=True)
        self.bus = dbus.SessionBus(mainloop=dbus_loop)
        signals = [('org.freedesktop.ScreenSaver', '/org/freedesktop/ScreenSaver', 'ActiveChanged'),
                   ('com.canonical.Unity', '/com/canonical/Unity/Session', 'Locked')]
        for org, path, event in signals:
            screensaver = self.bus.get_object(org, path)
            screensaver.connect_to_signal(event, self.checkLockScreen)

    def updateTasksComboBox(self):
        self.tasksComboBox.clear()
        self.editTaskListComboBox.clear()
        self.tasksComboBox.addItem('')
        self.tasksComboBox.lineEdit().setPlaceholderText("What are you going to do?")
        for task in self.db.query(Task).all():
            if task.active:
                self.tasksComboBox.addItem(task.description)
            self.editTaskListComboBox.addItem(task.description)

    def updateTimeSpent(self):
        if self.runningEvent:
            spent_time = self.runningEvent.spent_time
            m, s = divmod(spent_time, 60)
            h, m = divmod(m, 60)
            self.timeLabel.setText("{h:02d}:{m:02d}:{s:02d}".format(h=h, m=m, s=s))

            period = self.eventsPeriodComboBox.currentText()
            start, end = self.getStartEndForPeriod(period)
            total = Event.get_spent_time_period(self.db, start, end)
            self.totalTimeLabel.setText("{}h".format(total))

    def getStartEndForPeriod(self, period):
        if period == "Today":
            start = datetime.datetime.now().replace(hour=0, minute=0)
            end = start + relativedelta.relativedelta(days=1)
        elif period == "Yesterday":
            end = datetime.datetime.now().replace(hour=0, minute=0)
            start = end - relativedelta.relativedelta(days=1)
        elif period == "This week":
            today = datetime.datetime.now().replace(hour=0, minute=0)
            start = today - relativedelta.relativedelta(days=today.weekday())
            end = today + relativedelta.relativedelta(days=6 - today.weekday())
        else:
            raise Exception("Don't know this period {}".format(period))

        return start, end

    def updateTaskList(self):
        while self.timeEntriesLayout.count() > 0:
            self.timeEntriesLayout.takeAt(0).widget().deleteLater()

        period = self.eventsPeriodComboBox.currentText()
        start, end = self.getStartEndForPeriod(period)

        events = self.db.query(Event).filter(Event.start.between(start, end))\
            .order_by(Event.start.desc())

        for event in events:
            if not event.end:
                continue
            widget = EventWidget(event.id, event.task.description, event.spent_time, parent=self)
            widget.clicked.connect(self.eventClicked)
            widget.show()
            self.timeEntriesLayout.addWidget(widget)

    def updateEditStartEndTime(self):
        hours = self.editDurationSpinBox.value()
        startTime = self.editStartDateTimeEdit.dateTime().toPyDateTime()
        newEndTime = startTime + relativedelta.relativedelta(hours=hours)
        self.editEndDateTimeEdit.setDateTime(newEndTime)

    def updateDurationSpinBox(self):
        seconds = float((self.editEndDateTimeEdit.dateTime().toPyDateTime() -
                         self.editStartDateTimeEdit.dateTime().toPyDateTime()).seconds)
        hours = seconds / 3600
        self.editDurationSpinBox.setValue(hours)

    def updateDurationSpinBoxEndTime(self):
        self.updateDurationSpinBox()
        self.updateEditStartEndTime()

    def checkForRunningTask(self):
        self.runningEvent = self.db.query(Event).filter(Event.end == None).first()
        if self.runningEvent:
            self.tasksComboBox.setCurrentIndex(
                [self.tasksComboBox.itemText(x) for x in range(self.tasksComboBox.count())]
                .index(self.runningEvent.task.description))
            self.startButton.setText("Stop")
            self.tasksComboBox.setEnabled(False)

    def toggleEventButton(self):
        if self.runningEvent:
            self.stopEvent()
        else:
            self.startEvent()

    def eventsPeriodChanged(self):
        self.updateTaskList()

    def eventClicked(self, event_id):
        event = self.db.query(Event).get(event_id)
        self.editTaskListComboBox.setCurrentIndex(
            [self.editTaskListComboBox.itemText(x)
             for x in range(self.editTaskListComboBox.count())]
            .index(event.task.description))

        self.editDurationSpinBox.setValue(float(event.spent_time) / 3600)
        self.editStartDateTimeEdit.setDateTime(event.start_date)
        self.editEndDateTimeEdit.setDateTime(event.end_date)

        self.tabWidget.setCurrentIndex(TAB_EDIT_EVENT)

        self.editingEvent = event

    def startEvent(self, event=None):
        if not event:
            event = self.tasksComboBox.currentText()

        self.tasksComboBox.setEnabled(False)

        self.startButton.setText("Stop")

        if not event:
            return

        if re.match(r'\d+ - .+', event):
            tracker_id, name = re.findall(r'(\d+) - (.+)', event)[0]
        else:
            tracker_id = None
            name = event

        # Update DB
        task = Task.get_or_create(self.db, task_id=tracker_id, name=name, parent=None)

        if self.runningEvent:
            self.runningEvent.end = datetime.datetime.now()

        self.runningEvent = Event(task_id=task.task_id, comment="", start=datetime.datetime.now())
        self.db.add(self.runningEvent)
        self.db.commit()

        self.tasksComboBox.lineEdit().setText(self.runningEvent.task.description)

        self.checkForRunningTask()

    def addEvent(self):
        self.editDurationSpinBox.setValue(1)
        self.editStartDateTimeEdit.setDateTime(datetime.datetime.now())
        self.editEndDateTimeEdit.setDateTime(datetime.datetime.now() +
                                             relativedelta.relativedelta(hours=1))

        self.tabWidget.setCurrentIndex(TAB_EDIT_EVENT)
        self.editingEvent = Event()
        self.db.add(self.editingEvent)

    def stopEvent(self):
        self.tasksComboBox.setEnabled(True)

        self.runningEvent.end = datetime.datetime.now()
        self.db.commit()

        self.runningEvent = None

        self.updateTaskList()

        self.startButton.setText("Start")
        self.timeLabel.setText("00:00:00")

        self.updateTasksComboBox()

    def saveEvent(self):

        self.editingEvent.task_id = self.editTaskListComboBox.currentText().split(' - ')[0]
        self.editingEvent.start = self.editStartDateTimeEdit.dateTime().toPyDateTime()
        self.editingEvent.end = self.editEndDateTimeEdit.dateTime().toPyDateTime()

        self.db.commit()

        self.tabWidget.setCurrentIndex(TAB_MAIN)
        self.updateTaskList()

    def saveSettings(self):
        self.config = {'enable_detect_idle_time': self.detectIdleTimecheckBox.checkState(),
                       'detect_idle_time': self.detectIdleTimeSpinBox.value(),
                       'enable_remind': self.remindCheckBox.checkState(),
                       'remind_time': self.remindSpinBox.value(),
                       'stop_on_lock_screen': self.stopLockScreencheckBox.checkState(),
                       'enabled_redmine_sync': self.syncRedmineCheckBox.checkState(),
                       'redmine_sync_time': self.redmineSyncTimeSpinBox.value(),
                       'redmine_apikey': self.redmineApikeyLineEdit.text(),
                       'redmine_url': self.redmineUrlLineEdit.text(),
                       'redmine_user': self.redmineUserLineEdit.text()}
        self.writeConfig()

    def readConfig(self):
        config_file_path = os.path.join(self.config_path, 'config.json')
        if os.path.exists(config_file_path):
            with open(config_file_path, 'r') as f:
                self.config = json.loads(f.read())
        else:
            self.config = {}

        self.detectIdleTimecheckBox.setCheckState(self.config.get('enable_detect_idle_time', True))
        self.detectIdleTimeSpinBox.setValue(self.config.get('detect_idle_time'))
        self.remindCheckBox.setCheckState(self.config.get('enable_remind', True))
        self.remindSpinBox.setValue(self.config.get('remind_time'))
        self.stopLockScreencheckBox.setCheckState(self.config.get('stop_on_lock_screen', True))
        self.syncRedmineCheckBox.setCheckState(self.config.get('enabled_redmine_sync'))
        self.redmineSyncTimeSpinBox.setValue(self.config.get('redmine_sync_time'))
        self.redmineApikeyLineEdit.setText(self.config.get('redmine_apikey'))
        self.redmineUrlLineEdit.setText(self.config.get('redmine_url'))
        self.redmineUserLineEdit.setText(self.config.get('redmine_user'))

    def writeConfig(self):
        with open(os.path.join(self.config_path, 'config.json'), 'w') as f:
            f.write(json.dumps(self.config))
        self.tabWidget.setCurrentIndex(TAB_MAIN)

        self.checkIdleTime()
        self.checkRemind()
        self.checkRedmineSync()

    def checkIdleTime(self):
        self.idleTimeTimer.stop()
        if self.config.get("enable_detect_idle_time", True):
            self.idleTimeTimer.start(self.config.get("detect_idle_time", 5) * 60000)

    def detectIdleTime(self):

        # do something

        self.checkIdleTime()

    def checkRemind(self):
        self.remindTimer.stop()
        if self.config.get("enable_remind", True):
            self.remindTimer.start(self.config.get("remind_time", 5) * 60000)

    def remindTracking(self):

        # do something

        self.checkRemind()

    def checkRedmineSync(self):
        self.redmineSyncTimer.stop()
        if self.config.get("enabled_redmine_sync"):
            self.redmineSyncTimer.start(self.config.get("redmine_sync_time", 5) * 60000)

        self.redmineSyncPushButton.setVisible(self.config.get("enabled_redmine_sync", False))

    def doRedmineSync(self, check=True):
        logging.info("Doing redmine sync")
        thread = RedmineSyncThread(self.config,
                                   'sqlite:///' +
                                   os.path.join(self.config_path, 'markstimetracker.db'))

        def updateTaskWidgets():
            self.updateTaskList()
            self.updateTasksComboBox()
            self.checkForRunningTask()

        thread.finished.connect(updateTaskWidgets)
        thread.start()

        self.checkRedmineSync()

    def checkLockScreen(self, is_locked=True):
        if is_locked and self.config.get("stop_on_lock_screen"):
            self.stopEvent()
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")
Beispiel #29
0
class TrayIcon(QObject):

    def __init__(self):
        super().__init__()
        self._supported = QSystemTrayIcon.isSystemTrayAvailable()
        self._context_menu = None
        self._enabled = False
        self._trayicon = None
        self._state_icons = {}
        for state in (
            'disconnected',
            'disabled',
            'enabled',
        ):
            icon = QIcon(':/state-%s.svg' % state)
            if hasattr(icon, 'setIsMask'):
                icon.setIsMask(True)
            self._state_icons[state] = icon
        self._machine = None
        self._machine_state = 'disconnected'
        self._is_running = False
        self._update_state()

    def set_menu(self, menu):
        self._context_menu = menu
        if self._enabled:
            self._trayicon.setContextMenu(menu)

    def log(self, level, message):
        if self._enabled:
            if level <= log.INFO:
                icon = QSystemTrayIcon.Information
                timeout = 10
            elif level <= log.WARNING:
                icon = QSystemTrayIcon.Warning
                timeout = 15
            else:
                icon = QSystemTrayIcon.Critical
                timeout = 25
            self._trayicon.showMessage(__software_name__.capitalize(),
                                       message, icon, timeout * 1000)
        else:
            if level <= log.INFO:
                icon = QMessageBox.Information
            elif level <= log.WARNING:
                icon = QMessageBox.Warning
            else:
                icon = QMessageBox.Critical
            msgbox = QMessageBox()
            msgbox.setText(message)
            msgbox.setIcon(icon)
            msgbox.exec_()

    def is_supported(self):
        return self._supported

    def enable(self):
        if not self._supported:
            return
        self._trayicon = QSystemTrayIcon()
        # On OS X, the context menu is activated with either mouse buttons,
        # and activation messages are still sent, so ignore those...
        if not sys.platform.startswith('darwin'):
            self._trayicon.activated.connect(self._on_activated)
        if self._context_menu is not None:
            self._trayicon.setContextMenu(self._context_menu)
        self._enabled = True
        self._update_state()
        self._trayicon.show()

    def disable(self):
        if not self._enabled:
            return
        self._trayicon.hide()
        self._trayicon = None
        self._enabled = False

    def is_enabled(self):
        return self._enabled

    def update_machine_state(self, machine, state):
        self._machine = machine
        self._machine_state = state
        self._update_state()

    def update_output(self, enabled):
        self._is_running = enabled
        self._update_state()

    clicked = pyqtSignal()

    def _update_state(self):
        if self._machine_state not in ('initializing', 'connected'):
            state = 'disconnected'
        else:
            state = 'enabled' if self._is_running else 'disabled'
        icon = self._state_icons[state]
        if not self._enabled:
            return
        machine_state = _('{machine} is {state}').format(
            machine=_(self._machine),
            state=_(self._machine_state),
        )
        if self._is_running:
            output_state = _('output is enabled')
        else:
            output_state = _('output is disabled')
        self._trayicon.setIcon(icon)
        self._trayicon.setToolTip(
            'Plover:\n- %s\n- %s' % (output_state, machine_state)
        )

    def _on_activated(self, reason):
        if reason == QSystemTrayIcon.Trigger:
            self.clicked.emit()
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)
Beispiel #31
0
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)
Beispiel #32
0
    # 关闭所有窗口,也不关闭应用程序
    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)



Beispiel #33
0
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
Beispiel #34
0
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()
Beispiel #35
0
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)
Beispiel #36
0
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)
Beispiel #37
0
class SystemTray(QDialog):
    tray_alert_signal = pyqtSignal((str,))

    def __init__(self):
        super().__init__()
        self.create_actions()
        self.create_tray_icon()
        self.containing_folder = ""

    def create_actions(self):
        # menu items
        self.open_osf_folder_action = QAction("Open OSF Folder", self)
        self.launch_osf_action = QAction("Launch OSF", self)
        self.sync_now_action = QAction("Sync Now", self)
        self.currently_synching_action = QAction("Up to date", self)
        self.currently_synching_action.setDisabled(True)
        self.preferences_action = QAction("Settings", self)
        self.about_action = QAction("&About", self)
        self.quit_action = QAction("&Quit", self)

    def create_tray_icon(self):
        self.tray_icon_menu = QMenu(self)
        self.tray_icon_menu.addAction(self.open_osf_folder_action)
        self.tray_icon_menu.addAction(self.launch_osf_action)
        self.tray_icon_menu.addSeparator()
        self.tray_icon_menu.addAction(self.sync_now_action)
        self.tray_icon_menu.addAction(self.currently_synching_action)
        self.tray_icon_menu.addSeparator()
        self.tray_icon_menu.addAction(self.preferences_action)
        self.tray_icon_menu.addAction(self.about_action)
        self.tray_icon_menu.addSeparator()
        self.tray_icon_menu.addAction(self.quit_action)
        self.tray_icon = QSystemTrayIcon(self)
        self.tray_icon.setContextMenu(self.tray_icon_menu)

        # todo: do we have a better icon for use with desktop apps?
        icon = QIcon(":/cos_logo_backup.png")
        self.tray_icon.setIcon(icon)

    def start(self):
        self.tray_icon.show()

    def update_currently_synching(self, val):
        self.currently_synching_action.setText(str(val))
        self.tray_icon.show()

    def set_containing_folder(self, new_containing_folder):
        logging.debug("setting new containing folder is :{}".format(self.containing_folder))
        self.containing_folder = new_containing_folder

    def start_osf(self):
        url = "http://osf.io"
        webbrowser.open_new_tab(url)

    def open_osf_folder(self):
        # fixme: containing folder not being updated.
        import logging

        logging.debug("containing folder is :{}".format(self.containing_folder))
        if validate_containing_folder(self.containing_folder):
            if sys.platform == "win32":
                os.startfile(self.containing_folder)
            elif sys.platform == "darwin":
                subprocess.Popen(["open", self.containing_folder])
            else:
                try:
                    subprocess.Popen(["xdg-open", self.containing_folder])
                except OSError:
                    raise NotImplementedError
        else:
            AlertHandler.warn("osf folder is not set")
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
Beispiel #40
0
class SVApplication(QApplication):

    # Signals need to be on a QObject
    create_new_window_signal = pyqtSignal(str, object)
    cosigner_received_signal = pyqtSignal(object, object)
    labels_changed_signal = pyqtSignal(object, object, object)
    window_opened_signal = pyqtSignal(object)
    window_closed_signal = pyqtSignal(object)
    # Async tasks
    async_tasks_done = pyqtSignal()
    # Logging
    new_category = pyqtSignal(str)
    new_log = pyqtSignal(object)
    # Preferences updates
    fiat_ccy_changed = pyqtSignal()
    custom_fee_changed = pyqtSignal()
    op_return_enabled_changed = pyqtSignal()
    num_zeros_changed = pyqtSignal()
    base_unit_changed = pyqtSignal()
    fiat_history_changed = pyqtSignal()
    fiat_balance_changed = pyqtSignal()
    update_check_signal = pyqtSignal(bool, object)
    # Contact events
    contact_added_signal = pyqtSignal(object, object)
    contact_removed_signal = pyqtSignal(object)
    identity_added_signal = pyqtSignal(object, object)
    identity_removed_signal = pyqtSignal(object, object)
    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)
Beispiel #41
0
class MainWindow(QMainWindow):
	"""The main GUI application."""
	def __init__(self, config):
		"""Initializer for the GUI widgets. Pass in an instance of Config class, so that it may interact with the config."""
		super().__init__()

		self.config = config

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

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

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

		self.setup_control_widgets()
		self.update_colors()

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

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

		self.close_override = False
		self.show_hide_systray()

		self.check_and_do_database_migration()

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

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

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

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

		self.systray.setContextMenu(main_menu)

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

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

			upgrade_is_mandatory = current_version < MANDATORY_DBVERSION

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

		event.accept()

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

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

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

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

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

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

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

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

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

	def cmd_edit_streamer(self):
		raise NotImplementedException()

	def cmd_add_streamer(self):
		raise NotImplementedException()

	def cmd_delete_streamer(self):
		raise NotImplementedException()

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

		self.display_loaded_streams(streams)

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

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

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

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

	def handle_livestreamer_thread_finished_signal(self):
		self.livestreamer_thread = None

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

	def insertText(self, msg, add_newline=True, timestamp=True):
		"""Helper method for outputting text to the text box."""
		text = ""
		if timestamp and self.timestamp_format is not None:
			timestamp = format(datetime.now().strftime(self.timestamp_format))
			text = "{} ".format(timestamp)
		text += msg
		self.log_widget.moveCursor(QTextCursor.End)
		self.log_widget.insertPlainText(text)
		if add_newline:
			self.log_widget.insertPlainText("\n")
		self.log_widget.update()
Beispiel #42
0
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_())
Beispiel #43
0
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)
Beispiel #44
0
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.')
Beispiel #45
0
class ZhaoChaFrame(QWidget):
    game_hwnd = 0  # 游戏的窗体句柄
    bgpixmap = None
    pixmap = None
    my_visible = False

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

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

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

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


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

    clock = 0

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

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

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

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

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

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

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

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

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

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

        self.HideMe()


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

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

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

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

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


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

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

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

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


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


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


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


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


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


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

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

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


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

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

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

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

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


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

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

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

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

        return diff_red, diff_green, diff_blue

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

        if self.clock > 0:
            print
            time.clock() - self.clock, text
        self.clock = time.clock()
Beispiel #46
0
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()
Beispiel #48
0
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
Beispiel #49
0
class DemoImpl(QDialog):
	
	
    def __init__(self, *args):
        super(DemoImpl, self).__init__(*args)

        loadUi('dict2.ui',self)
		
        self.setLayout(self.verticalLayout)
        self.plainTextEdit.setReadOnly(True)
        self.setWindowFlags(self.windowFlags() |
                            Qt.WindowSystemMenuHint |
                            Qt.WindowMinMaxButtonsHint)
        self.trayicon = QSystemTrayIcon()
        self.traymenu = QMenu()

        self.quitAction = QAction('GQuit', self)
        self.quitAction.triggered.connect(self.close)
        self.quitAction.setShortcut(QKeySequence('Ctrl+q'))
        self.addAction(self.quitAction)

        self.traymenu.addAction('&Normal', self.showNormal, QKeySequence('Ctrl+n'))
        self.traymenu.addAction('Mi&nimize', self.showMinimized, QKeySequence('Ctrl+i'))
        self.traymenu.addAction('&Maximum', self.showMaximized, QKeySequence('Ctrl+m'))
        self.traymenu.addAction('&Quit',self.close, QKeySequence('Ctrl+q'))

        self.trayicon.setContextMenu(self.traymenu)

        self.ticon = QIcon('icon_dict2.ico')
        self.trayicon.setIcon(self.ticon)
        self.trayicon.setToolTip('YYDict')
        self.trayicon.activated.connect(self.on_systemTrayIcon_activated)
        self.traymsg_firstshow = True

        self.button1.clicked.connect(self.searchword)
        self.comboBox.activated.connect(self.searchword)

    def changeEvent(self, event):
        if event.type() == QEvent.WindowStateChange:
            if self.isMinimized():
                self.hide()
                self.trayicon.show()
                if self.traymsg_firstshow:
                    self.trayicon.showMessage('', 'YYDict is running', QSystemTrayIcon.Information, 2000)
                    self.traymsg_firstshow = False
            else:
                self.trayicon.hide()
				
    def closeEvent(self, event):
        self.trayicon.hide()

    @pyqtSlot(QSystemTrayIcon.ActivationReason)
    def on_systemTrayIcon_activated(self, reason):
        if reason == QSystemTrayIcon.DoubleClick:
            self.activateWindow()	
            self.showNormal()



    @pyqtSlot()
    def searchword(self):
        word = self.lineEdit.text().strip()
        if not len(word):
            self.plainTextEdit.setPlainText('')
            self.lineEdit.setFocus(Qt.MouseFocusReason)
        else:
            self.workThread = WorkThread(word, self.comboBox.currentIndex())
            self.workThread.received.connect(self.updateResult)
            self.workThread.start()
            self.workThread.wait()


    @pyqtSlot('QString')
    def updateResult(self, rt):
        self.plainTextEdit.setPlainText(rt)
        self.lineEdit.selectAll()
        self.lineEdit.setFocus(Qt.MouseFocusReason)
Beispiel #50
0
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()
Beispiel #51
0
class Magneto(MagnetoCore):

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

    def __init__(self):
        self._app = QApplication([sys.argv[0]])

        from dbus.mainloop.pyqt5 import DBusQtMainLoop
        super(Magneto, self).__init__(main_loop_class = DBusQtMainLoop)

        self._window = QSystemTrayIcon(self._app)
        icon_name = self.icons.get("okay")
        self._window.setIcon(QIcon.fromTheme(icon_name))
        self._window.activated.connect(self._applet_activated)

        self._menu = QMenu(_("Magneto Entropy Updates Applet"))
        self._window.setContextMenu(self._menu)

        self._menu_items = {}
        for item in self._menu_item_list:
            if item is None:
                self._menu.addSeparator()
                continue

            myid, _unused, mytxt, myslot_func = item
            name = self.get_menu_image(myid)
            action_icon = QIcon.fromTheme(name)

            w = QAction(action_icon, mytxt, self._window,
                        triggered=myslot_func)
            self._menu_items[myid] = w
            self._menu.addAction(w)

        self._menu.hide()

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

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

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

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

        self._window.show()

        # Enter main loop
        self._app.exec_()

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

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

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

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

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

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

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

        def _action_activate_cb(action_num):
            if not buttons:
                return

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

            _action_id, _button_name, button_callback = action_info
            button_callback()

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

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

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

        QTimer.singleShot(0, do_show)

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

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

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

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

    def show_notice_window(self):

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

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

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

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

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

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

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

        self._notice_window.populate(packages, critical_msg)

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

    def gui(self):
        self.systray = QSystemTrayIcon()
        self.icon = QIcon(':/sansimera.png')
        self.systray.setIcon(self.icon)
        self.systray.setToolTip('Σαν σήμερα...')
        self.menu = QMenu()
        self.exitAction = QAction('&Έξοδος', self)
        self.refreshAction = QAction('&Ανανέωση', self)
        self.aboutAction = QAction('&Σχετικά', self)
        self.notification_interval = QAction('Ει&δοποίηση εορταζόντων', self)
        self.menu.addAction(self.notification_interval)
        self.menu.addAction(self.refreshAction)
        self.menu.addAction(self.aboutAction)
        self.menu.addAction(self.exitAction)
        self.systray.setContextMenu(self.menu)
        self.notification_interval.triggered.connect(self.interval_namedays)
        self.exitAction.triggered.connect(exit)
        self.refreshAction.triggered.connect(self.refresh)
        self.aboutAction.triggered.connect(self.about)
        self.browser = QTextBrowser()
        self.browser.setOpenExternalLinks(True)
        self.setGeometry(600, 500, 400, 300)
        self.setWindowIcon(self.icon)
        self.setWindowTitle('Σαν σήμερα...')
        self.setCentralWidget(self.browser)
        self.systray.show()
        self.systray.activated.connect(self.activate)
        self.browser.append('Λήψη...')
        nicon = QIcon(':/next')
        picon = QIcon(':/previous')
        ricon = QIcon(':/refresh')
        iicon = QIcon(':/info')
        qicon = QIcon(':/exit')
        inicon = QIcon(':/notifications')
        self.nextAction = QAction('Επόμενο', self)
        self.nextAction.setIcon(nicon)
        self.previousAction = QAction('Προηγούμενο', self)
        self.refreshAction.triggered.connect(self.refresh)
        self.nextAction.triggered.connect(self.nextItem)
        self.previousAction.triggered.connect(self.previousItem)
        self.previousAction.setIcon(picon)
        self.refreshAction.setIcon(ricon)
        self.exitAction.setIcon(qicon)
        self.aboutAction.setIcon(iicon)
        self.notification_interval.setIcon(inicon)
        controls = QToolBar()
        self.addToolBar(Qt.BottomToolBarArea, controls)
        controls.setObjectName('Controls')
        controls.addAction(self.previousAction)
        controls.addAction(self.nextAction)
        controls.addAction(self.refreshAction)
        self.restoreState(self.settings.value("MainWindow/State", QByteArray()))
        self.refresh()

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    @profiler
    def __init__(self, config: 'SimpleConfig', daemon: 'Daemon', plugins: 'Plugins'):
        set_language(config.get('language', get_default_language()))
        Logger.__init__(self)
        self.logger.info(f"Qt GUI starting up... Qt={QtCore.QT_VERSION_STR}, PyQt={QtCore.PYQT_VERSION_STR}")
        # Uncomment this call to verify objects are being properly
        # GC-ed when windows are closed
        #network.add_jobs([DebugMem([Abstract_Wallet, SPV, Synchronizer,
        #                            ElectrumWindow], interval=5)])
        QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads)
        if hasattr(QtCore.Qt, "AA_ShareOpenGLContexts"):
            QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_ShareOpenGLContexts)
        if hasattr(QGuiApplication, 'setDesktopFileName'):
            QGuiApplication.setDesktopFileName('electrum-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()
Beispiel #55
0
class MainWindow(QMainWindow):
    """Voice Changer main window."""
    def __init__(self, parent=None):
        super(MainWindow, self).__init__()
        self.statusBar().showMessage("Move Dial to Deform Microphone Voice !.")
        self.setWindowTitle(__doc__)
        self.setMinimumSize(240, 240)
        self.setMaximumSize(480, 480)
        self.resize(self.minimumSize())
        self.setWindowIcon(QIcon.fromTheme("audio-input-microphone"))
        self.tray = QSystemTrayIcon(self)
        self.center()
        QShortcut("Ctrl+q", self, activated=lambda: self.close())
        self.menuBar().addMenu("&File").addAction("Quit", lambda: exit())
        self.menuBar().addMenu("Sound").addAction(
            "STOP !", lambda: call('killall rec', shell=True))
        windowMenu = self.menuBar().addMenu("&Window")
        windowMenu.addAction("Hide", lambda: self.hide())
        windowMenu.addAction("Minimize", lambda: self.showMinimized())
        windowMenu.addAction("Maximize", lambda: self.showMaximized())
        windowMenu.addAction("Restore", lambda: self.showNormal())
        windowMenu.addAction("FullScreen", lambda: self.showFullScreen())
        windowMenu.addAction("Center", lambda: self.center())
        windowMenu.addAction("Top-Left", lambda: self.move(0, 0))
        windowMenu.addAction("To Mouse", lambda: self.move_to_mouse_position())
        # widgets
        group0 = QGroupBox("Voice Deformation")
        self.setCentralWidget(group0)
        self.process = QProcess(self)
        self.process.error.connect(
            lambda: self.statusBar().showMessage("Info: Process Killed", 5000))
        self.control = QDial()
        self.control.setRange(-10, 20)
        self.control.setSingleStep(5)
        self.control.setValue(0)
        self.control.setCursor(QCursor(Qt.OpenHandCursor))
        self.control.sliderPressed.connect(
            lambda: self.control.setCursor(QCursor(Qt.ClosedHandCursor)))
        self.control.sliderReleased.connect(
            lambda: self.control.setCursor(QCursor(Qt.OpenHandCursor)))
        self.control.valueChanged.connect(
            lambda: self.control.setToolTip(f"<b>{self.control.value()}"))
        self.control.valueChanged.connect(lambda: self.statusBar().showMessage(
            f"Voice deformation: {self.control.value()}", 5000))
        self.control.valueChanged.connect(self.run)
        self.control.valueChanged.connect(lambda: self.process.kill())
        # Graphic effect
        self.glow = QGraphicsDropShadowEffect(self)
        self.glow.setOffset(0)
        self.glow.setBlurRadius(99)
        self.glow.setColor(QColor(99, 255, 255))
        self.control.setGraphicsEffect(self.glow)
        self.glow.setEnabled(False)
        # Timer to start
        self.slider_timer = QTimer(self)
        self.slider_timer.setSingleShot(True)
        self.slider_timer.timeout.connect(self.on_slider_timer_timeout)
        # an icon and set focus
        QLabel(self.control).setPixmap(
            QIcon.fromTheme("audio-input-microphone").pixmap(32))
        self.control.setFocus()
        QVBoxLayout(group0).addWidget(self.control)
        self.menu = QMenu(__doc__)
        self.menu.addAction(__doc__).setDisabled(True)
        self.menu.setIcon(self.windowIcon())
        self.menu.addSeparator()
        self.menu.addAction(
            "Show / Hide", lambda: self.hide()
            if self.isVisible() else self.showNormal())
        self.menu.addAction("STOP !", lambda: call('killall rec', shell=True))
        self.menu.addSeparator()
        self.menu.addAction("Quit", lambda: exit())
        self.tray.setContextMenu(self.menu)
        self.make_trayicon()

    def run(self):
        """Run/Stop the QTimer."""
        if self.slider_timer.isActive():
            self.slider_timer.stop()
        self.glow.setEnabled(True)
        call('killall rec ; killall play', shell=True)
        self.slider_timer.start(3000)

    def on_slider_timer_timeout(self):
        """Run subprocess to deform voice."""
        self.glow.setEnabled(False)
        value = int(self.control.value()) * 100
        command = '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()
Beispiel #56
0
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)
Beispiel #57
0
	def initUI(self):
		# 获取电脑屏幕宽高 让主界面初始化后处于屏幕中间
		wh = QApplication.desktop().screenGeometry()
		self.screen_w , self.screen_h = wh.width() ,wh.height()
		self.setGeometry(int((self.screen_w-300)/2),int((self.screen_h-600)/2),300,600)
		# self.setWindowOpacity(0.97); 

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

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

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

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

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

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

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


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

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


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

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

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

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

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

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

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

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

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

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

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

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

		#设置托盘图标
		tray = QSystemTrayIcon(self)
		tray.setIcon(QIcon('image/tray.png'))
		self.trayIconMenu = QMenu(self)
		self.trayIconMenu.setStyleSheet(qss_tray)
		showAction = QAction(QIcon('image/tray.png'),u"显示主面板", self,triggered=self.show)
		self.trayIconMenu.addAction(showAction)
		# self.trayIconMenu.addAction(preAction)
		# self.trayIconMenu.addAction(pauseAction)
		# self.trayIconMenu.addAction(nextAction)
		# self.trayIconMenu.addAction(quitAction)
		tray.setContextMenu(self.trayIconMenu)
		tray.show()
		tray.activated.connect(self.dbclick_tray)
Beispiel #58
0
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()
Beispiel #59
0
    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)
Beispiel #60
0
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