Example #1
0
class NetworkAPIServer(QTcpServer):

    # signal emitted whenever the server changes state
    # int argument: 0 = stopped, 1 = listening, 2 = waiting for data,
    # 3 = processing request (busy/blocking)
    status_changed = pyqtSignal(int, str)

    def emitStatusSignal(self, status, message=None):
        # fill in default paused/running messages for status 0+1
        if message == None:
            if status == 0:
                message = 'Network API disabled'
            elif status == 1:
                message = 'Listening on port ' + str(self.serverPort())
        self.log(message)
        self.status_changed.emit(status, message)

    def __init__(self, iface):
        QTcpServer.__init__(self)
        self.iface = iface

        # timer for interrupting open connections on inactivity
        self.timer = QTimer()
        self.timer.setInterval(3000)  # 3 seconds, worth making configurable?
        self.timer.setSingleShot(True)
        self.timer.timeout.connect(self.timeout)

    def log(self, message, level=QgsMessageLog.INFO):
        "Print a message to QGIS' Log Messages Panel"
        QgsMessageLog.logMessage(message, 'NetworkAPI', level=level)

    def showMessage(self, message, level=QgsMessageBar.INFO):
        "If enabled by the config, display a log message in QGIS' message bar"
        # always log
        # the message bar and log panel have different message status ranges -
        # the modulo (%) merely maps the messages' SUCCESS status to the log
        # panel's INFO
        self.log(message, level % 3)
        if NetworkAPIDialog.settings.log():
            # TODO make message timeout configurable?
            self.iface.messageBar().pushMessage('Network API', message, level,
                                                5)

    def stopServer(self):
        if self.isListening():
            self.log('Stopping to listen on port ' + str(self.serverPort()))
            # open/running requests are automatically wrapped up through their
            # 'disconnect' signal triggering finishConnection()
            self.close()
            self.emitStatusSignal(0)

    def startServer(self, port):
        if self.isListening() and port == self.serverPort():
            # already listening on that port, carry on
            return
        self.stopServer()
        # process one connection/request at a time
        self.connection = None
        self.request = None
        self.nrequests = 0

        self.newConnection.connect(self.acceptConnection)
        if self.listen(QHostAddress.Any, port):
            self.emitStatusSignal(1)
        else:
            self.showMessage('Failed to open socket on port ' + str(port),
                             QgsMessageBar.CRITICAL)
            self.emitStatusSignal(
                0, 'Error: failed to open socket on port ' + str(port))

    def acceptConnection(self):
        """Accept a new incoming connection request. If the server is still
        busy processing an earlier request, the function returns immediately and the new incoming connection will be taken care of when the current
        request is finished (see the call to hasPendingConnections() in
        finishConnection() below)"""
        if self.connection == None:
            cxn = self.nextPendingConnection()
            if not NetworkAPIDialog.settings.remote_connections(
            ) and cxn.peerAddress(
            ) != QHostAddress.LocalHost:  # FIXME .LocalHostIPv6?
                self.showMessage(
                    'Refusing remote connection from ' +
                    cxn.peerAddress().toString(), QgsMessageBar.WARNING)
                cxn.close()
                return
            self.connection = cxn
            self.nrequests = self.nrequests + 1
            self.log('Processing connection #' + str(self.nrequests) + ' (' +
                     self.connection.peerAddress().toString() + ')')
            self.connection.disconnected.connect(self.finishConnection)
            self.connection.readyRead.connect(self.readFromConnection)
            self.readFromConnection()
        else:
            self.log('Still busy with #' + str(self.nrequests) +
                     ', putting incoming connection on hold...')

    def readFromConnection(self):
        # readAll() doesn't guarantee that there isn't any more data still
        # incoming before reaching the 'end' of the input stream (which, for
        # network connections, is ill-defined anyway). in order to be able to
        # parse incoming requests incrementally, we store the parse request
        # header in a class variable.

        # (re)set connection timeout
        self.timer.start()

        if self.request == None:
            self.emitStatusSignal(2, 'Connection opened...')
            if not self.connection.canReadLine():
                self.log(
                    'readyRead() was signalled before full HTTP request line was available for reading ('
                    + str(self.connection.bytesAvailable()) +
                    ' bytes in buffer)', QgsMessageLog.INFO)
                return
            # new request, parse header
            try:
                self.request = NetworkAPIRequest(self.connection)
            except ValueError:
                # malformed request line
                self.sendErrorAndDisconnect(400)
                return
        else:
            # additional data for previous request, append to payload. the loop
            # is necessary to work around a race condition where data is added
            # after readAll() but, because the present function is still busy,
            # readyRead is not emitted again.
            while True:
                self.request.headers.set_payload(
                    self.request.headers.get_payload() +
                    self.connection.readAll())
                if not self.connection.waitForReadyRead(0):
                    break
        # respond to request or -- if data is still incomplete -- do nothing
        self.processRequest()

    def processRequest(self):
        if NetworkAPIDialog.settings.security():
            if request.headers[
                    'Authorization'] != NetworkAPIDialog.settings.auth():
                # TODO log/message?
                self.sendErrorAndDisconnect(401)
                return

        # find+call function corresponding to given path
        qgis_call = Registry.get(self.request.path)

        if qgis_call == None:
            self.sendErrorAndDisconnect(404)
            return

        # if some path that only processes GET requests was submitted with a
        # POST body, we receive + accept it anyway instead of throwing a 405...
        if self.request.command == 'POST':
            self.emitStatusSignal(
                2, 'Processing request: received ' +
                str(len(self.request.headers.get_payload())) + ' of ' +
                str(self.request.content_length))

            if len(self.request.headers.get_payload()
                   ) < self.request.content_length:
                # request body incomplete, wait for more data to arrive --
                # timeout is controlled by the lower-level readFromConnection()
                return

        # looks like we have all the data, execute call
        self.timer.stop()

        # the only payload processing done in the server directly: if the
        # POST content-type is set to JSON, parse
        if self.request.headers.get('Content-Type') == 'application/json':
            try:
                self.request.headers.set_payload(
                    loads(self.request.headers.get_payload()))
            except ValueError:
                # problem parsing JSON body
                self.sendErrorAndDisconnect(400)
                return

        self.emitStatusSignal(3, 'Executing request...')
        # response is written and connection closed in executeRequest()
        self.executeRequest(qgis_call)

    def sendResponse(self):
        # pass output generated by BaseHTTPRequestHandler on to the QTcpSocket
        self.connection.write(self.request.wfile.getvalue())
        # flush response and close connection (i.e. no persistent connections)
        # calling this will cause a signal to trigger finishConnection() for
        # actual connection cleanup
        self.connection.disconnectFromHost()

    def sendErrorAndDisconnect(self, status):
        self.timer.stop()
        self.request.send_http_error(status)
        self.sendResponse()

    def timeout(self):
        if self.connection:
            self.showMessage(
                'Connection timed out after ' + str(self.timer.interval()) +
                'ms', QgsMessageBar.WARNING)
            # can't make use of the BaseHTTPRequestHandler's send_error code if
            # we haven't even parsed/received a HTTP request line yet...
            #            self.sendErrorAndDisconnect(408)
            self.connection.disconnectFromHost()

    def finishConnection(self):
        """Gracefully disconnect a peer and clean up the network connection.
        Never called directly, always triggered by the connection's 'disconnected' signal."""
        self.log('Disconnecting #' + str(self.nrequests) + ' (' +
                 self.connection.peerAddress().toString() + ')')
        self.connection.readyRead.disconnect(self.readFromConnection)
        self.connection.disconnected.disconnect(self.finishConnection)
        self.request = None
        self.connection = None
        # process waiting connections
        if self.hasPendingConnections():
            self.log('Found pending connection, processing..')
            self.acceptConnection()
        else:
            # back to listening
            self.emitStatusSignal(1)

    def executeRequest(self, qgis_call):
        """Execute a command previously retrieved from the registry"""
        try:
            # all implemented functions take two arguments
            result = qgis_call(self.iface, self.request)
            self.request.send_response(result.status)

            # extract xml document from nodes and set appropriate content-type
            if isinstance(result.body, QDomDocument):
                result.body = result.body.toString()
                result.content_type = 'text/xml; charset=utf-8'

            # result content-type was set explicitly
            if result.content_type:
                self.request.send_header('Content-Type', result.content_type)
                self.request.end_headers()
                self.request.wfile.write(result.body)
            else:
                # autoconvert python classes using the JSONEncoder found at
                # the bottom of registry.py (note that GeoJSON results are NOT
                # handled here but by the if branch above!)
                self.request.send_header('Content-Type', 'application/json')
                self.request.end_headers()
                dump(result.body,
                     self.request.wfile,
                     cls=QGISJSONEncoder,
                     indent=2)
            # connection will be closed by parent function
        except Exception as e:
            self.request.send_http_error(500, str(e))
            # TODO if request failed, add link to docs at /api?path=... ?
        self.showMessage('Executed request #' + str(self.nrequests) + ': ' +
                         self.request.log_string)  #, QgsMessageBar.SUCCESS)
        self.sendResponse()
class QSteamParser(QObject):
    steamDataReady = pyqtSignal(dict)
    timerStart = pyqtSignal(int)
    timerStop = pyqtSignal()
    timerTimeout = pyqtSignal(int)
    timer = None

    def __init__(self, username, password, data_path):
        super(QSteamParser, self).__init__()
        self.logger = logging.getLogger('.'.join((__name__, self.__class__.__name__)))

        # Setup SteamWebBrowser etc.
        self.logger.debug('Init QSteamWebBrowser')
        swb = QSteamWebBrowser(
                username=username,
                password=password,
                parent=self
        )
        self.logger.debug('Using data path: "%s"', data_path)
        self.sbb = SteamBadges(swb, data_path)

    @property
    def settings(self):
        return QSettings(QSettings.IniFormat, QSettings.UserScope, 'jayme-github', 'SteamIdle')

    @pyqtSlot(int)
    def startTimer(self, interval):
        # Interval should never exceed maxrefreshtime
        maxrefreshtime = self.settings.value('maxrefreshtime', 15, type=int)*60*1000
        newInterval = maxrefreshtime if interval > maxrefreshtime else interval
        self.logger.debug('Requested %dmsec timer, setting up timer for %dmsec',
            interval,
            newInterval,
        )
        self.stopTimer()
        self.timer = QTimer()
        self.timer.timeout.connect(self.on_timer_timeout)
        self.timer.start(newInterval)
        self.timerStart.emit(newInterval)

    @pyqtSlot()
    def startDefaultTimer(self):
        self.startTimer(self.settings.value('maxrefreshtime', 15, type=int)*60*1000)

    @pyqtSlot()
    def stopTimer(self):
        if self.timer:
            self.logger.debug('Stopping timer')
            self.timer.stop()
            self.timer = None
            self.timerStop.emit()

    @pyqtSlot()
    def on_timer_timeout(self):
        self.logger.debug(self.timer.interval())
        self.timerTimeout.emit(self.timer.interval())
        self.updateApps()

    @pyqtSlot()
    def updateApps(self):
        self.logger.info('Updating apps from steam')
        apps = self.sbb.get_apps()
        self.logger.debug('ParseApps: %d apps', len(apps))
        self.steamDataReady.emit(apps)
Example #3
0
class RecordingKeyboardMonitor(AbstractKeyboardMonitor):
    """
    Monitor the keyboard by recording X11 protocol data.
    """

    def __init__(self, parent=None):
        AbstractKeyboardMonitor.__init__(self, parent)
        self.display = Display.from_qt()
        # this timer is started on every keyboard event, its timeout signals,
        # that the keyboard is to be considered inactive again
        self._idle_timer = QTimer(self)
        self._idle_timer.setInterval(self.DEFAULT_IDLETIME)
        self._idle_timer.timeout.connect(self.typingStopped)
        self._idle_timer.setSingleShot(True)
        # this object records events
        self._recorder = EventRecorder(self)
        self._recorder.keyPressed.connect(self._key_pressed)
        self._recorder.keyReleased.connect(self._key_released)
        self._recorder.started.connect(self.started)
        self._recorder.finished.connect(self.stopped)
        # a set of all known modifier keycodes
        modifier_mapping = xlib.get_modifier_mapping(self.display)
        self._modifiers = frozenset(keycode for modifiers in modifier_mapping
                                    for keycode in modifiers if keycode != 0)
        # a set holding all pressed, but not yet released modifier keys
        self._pressed_modifiers = set()
        # the value of keys to ignore
        self._keys_to_ignore = self.IGNORE_NO_KEYS

    def _is_ignored_modifier(self, keycode):
        """
        Return ``True``, if ``keycode`` as a modifier key, which has to be
        ignored, ``False`` otherwise.
        """
        return (self._keys_to_ignore >= self.IGNORE_MODIFIER_KEYS and
                keycode in self._modifiers)

    def _is_ignored_modifier_combo(self):
        """
        Return ``True``, if the current key event occurred in combination
        with a modifier, which has to be ignored, ``False`` otherwise.
        """
        return (self._keys_to_ignore == self.IGNORE_MODIFIER_COMBOS and
                self._pressed_modifiers)

    def _is_ignored(self, keycode):
        """
        Return ``True``, if the given ``keycode`` has to be ignored,
        ``False`` otherwise.
        """
        return (self._is_ignored_modifier(keycode) or
                self._is_ignored_modifier_combo())

    def _key_pressed(self, keycode):
        if keycode in self._modifiers:
            self._pressed_modifiers.add(keycode)
        if not self._is_ignored(keycode):
            if not self.keyboard_active:
                self.typingStarted.emit()
            # reset the idle timeout
            self._idle_timer.start()

    def _key_released(self, keycode):
        self._pressed_modifiers.discard(keycode)
        if not self._is_ignored(keycode):
            if not self.keyboard_active:
                self.typingStarted.emit()
            # reset the idle timeout
            self._idle_timer.start()

    @property
    def is_running(self):
        return self._recorder.isRunning()

    def start(self):
        self._recorder.start()

    def stop(self):
        AbstractKeyboardMonitor.stop(self)
        self._idle_timer.stop()
        self._recorder.stop()

    @property
    def keys_to_ignore(self):
        return self._keys_to_ignore

    @keys_to_ignore.setter
    def keys_to_ignore(self, value):
        if not (self.IGNORE_NO_KEYS <= value <= self.IGNORE_MODIFIER_COMBOS):
            raise ValueError('unknown constant for keys_to_ignore')
        self._keys_to_ignore = value

    @property
    def idle_time(self):
        return self._idle_timer.interval() / 1000

    @idle_time.setter
    def idle_time(self, value):
        self._idle_timer.setInterval(int(value * 1000))

    @property
    def keyboard_active(self):
        return self._idle_timer.isActive()
class E4SideBar(QWidget):
    """
    Class implementing a sidebar with a widget area, that is hidden or shown, if the
    current tab is clicked again.
    """
    Version = 1
    
    North = 0
    East  = 1
    South = 2
    West  = 3
    
    def __init__(self, orientation = None, delay = 200, parent = None):
        """
        Constructor
        
        @param orientation orientation of the sidebar widget (North, East, South, West)
        @param delay value for the expand/shrink delay in milliseconds (integer)
        @param parent parent widget (QWidget)
        """
        QWidget.__init__(self, parent)
        
        self.__tabBar = QTabBar()
        self.__tabBar.setDrawBase(True)
        self.__tabBar.setShape(QTabBar.RoundedNorth)
        self.__tabBar.setUsesScrollButtons(True)
        self.__tabBar.setDrawBase(False)
        self.__stackedWidget = QStackedWidget(self)
        self.__stackedWidget.setContentsMargins(0, 0, 0, 0)
        self.__autoHideButton = QToolButton()
        self.__autoHideButton.setCheckable(True)
        self.__autoHideButton.setIcon(UI.PixmapCache.getIcon("autoHideOff.png"))
        self.__autoHideButton.setChecked(True)
        self.__autoHideButton.setToolTip(
            self.trUtf8("Deselect to activate automatic collapsing"))
        self.barLayout = QBoxLayout(QBoxLayout.LeftToRight)
        self.barLayout.setMargin(0)
        self.layout = QBoxLayout(QBoxLayout.TopToBottom)
        self.layout.setMargin(0)
        self.layout.setSpacing(0)
        self.barLayout.addWidget(self.__autoHideButton)
        self.barLayout.addWidget(self.__tabBar)
        self.layout.addLayout(self.barLayout)
        self.layout.addWidget(self.__stackedWidget)
        self.setLayout(self.layout)
        
        # initialize the delay timer
        self.__actionMethod = None
        self.__delayTimer = QTimer(self)
        self.__delayTimer.setSingleShot(True)
        self.__delayTimer.setInterval(delay)
        self.connect(self.__delayTimer, SIGNAL("timeout()"), self.__delayedAction)
        
        self.__minimized = False
        self.__minSize = 0
        self.__maxSize = 0
        self.__bigSize = QSize()
        
        self.splitter = None
        self.splitterSizes = []
        
        self.__hasFocus = False # flag storing if this widget or any child has the focus
        self.__autoHide = False
        
        self.__tabBar.installEventFilter(self)
        
        self.__orientation = E4SideBar.North
        if orientation is None:
            orientation = E4SideBar.North
        self.setOrientation(orientation)
        
        self.connect(self.__tabBar, SIGNAL("currentChanged(int)"), 
                     self.__stackedWidget, SLOT("setCurrentIndex(int)"))
        self.connect(e4App(), SIGNAL("focusChanged(QWidget*, QWidget*)"), 
                     self.__appFocusChanged)
        self.connect(self.__autoHideButton, SIGNAL("toggled(bool)"), 
                     self.__autoHideToggled)
    
    def setSplitter(self, splitter):
        """
        Public method to set the splitter managing the sidebar.
        
        @param splitter reference to the splitter (QSplitter)
        """
        self.splitter = splitter
        self.connect(self.splitter, SIGNAL("splitterMoved(int, int)"), 
                     self.__splitterMoved)
        self.splitter.setChildrenCollapsible(False)
        index = self.splitter.indexOf(self)
        self.splitter.setCollapsible(index, False)
    
    def __splitterMoved(self, pos, index):
        """
        Private slot to react on splitter moves.
        
        @param pos new position of the splitter handle (integer)
        @param index index of the splitter handle (integer)
        """
        if self.splitter:
            self.splitterSizes = self.splitter.sizes()
    
    def __delayedAction(self):
        """
        Private slot to handle the firing of the delay timer.
        """
        if self.__actionMethod is not None:
            self.__actionMethod()
    
    def setDelay(self, delay):
        """
        Public method to set the delay value for the expand/shrink delay in milliseconds.
        
        @param delay value for the expand/shrink delay in milliseconds (integer)
        """
        self.__delayTimer.setInterval(delay)
    
    def delay(self):
        """
        Public method to get the delay value for the expand/shrink delay in milliseconds.
        
        @return value for the expand/shrink delay in milliseconds (integer)
        """
        return self.__delayTimer.interval()
    
    def __cancelDelayTimer(self):
        """
        Private method to cancel the current delay timer.
        """
        self.__delayTimer.stop()
        self.__actionMethod = None
    
    def shrink(self):
        """
        Public method to record a shrink request.
        """
        self.__delayTimer.stop()
        self.__actionMethod = self.__shrinkIt
        self.__delayTimer.start()
   
    def __shrinkIt(self):
        """
        Private method to shrink the sidebar.
        """
        self.__minimized = True
        self.__bigSize = self.size()
        if self.__orientation in [E4SideBar.North, E4SideBar.South]:
            self.__minSize = self.minimumSizeHint().height()
            self.__maxSize = self.maximumHeight()
        else:
            self.__minSize = self.minimumSizeHint().width()
            self.__maxSize = self.maximumWidth()
        if self.splitter:
            self.splitterSizes = self.splitter.sizes()
        
        self.__stackedWidget.hide()
        
        if self.__orientation in [E4SideBar.North, E4SideBar.South]:
            self.setFixedHeight(self.__tabBar.minimumSizeHint().height())
        else:
            self.setFixedWidth(self.__tabBar.minimumSizeHint().width())
        
        self.__actionMethod = None
    
    def expand(self):
        """
        Private method to record a expand request.
        """
        self.__delayTimer.stop()
        self.__actionMethod = self.__expandIt
        self.__delayTimer.start()
    
    def __expandIt(self):
        """
        Public method to expand the sidebar.
        """
        self.__minimized = False
        self.__stackedWidget.show()
        self.resize(self.__bigSize)
        if self.__orientation in [E4SideBar.North, E4SideBar.South]:
            minSize = max(self.__minSize, self.minimumSizeHint().height())
            self.setMinimumHeight(minSize)
            self.setMaximumHeight(self.__maxSize)
        else:
            minSize = max(self.__minSize, self.minimumSizeHint().width())
            self.setMinimumWidth(minSize)
            self.setMaximumWidth(self.__maxSize)
        if self.splitter:
            self.splitter.setSizes(self.splitterSizes)
        
        self.__actionMethod = None
    
    def isMinimized(self):
        """
        Public method to check the minimized state.
        
        @return flag indicating the minimized state (boolean)
        """
        return self.__minimized
    
    def isAutoHiding(self):
        """
        Public method to check, if the auto hide function is active.
        
        @return flag indicating the state of auto hiding (boolean)
        """
        return self.__autoHide
    
    def eventFilter(self, obj, evt):
        """
        Protected method to handle some events for the tabbar.
        
        @param obj reference to the object (QObject)
        @param evt reference to the event object (QEvent)
        @return flag indicating, if the event was handled (boolean)
        """
        if obj == self.__tabBar:
            if evt.type() == QEvent.MouseButtonPress:
                pos = evt.pos()
                for i in range(self.__tabBar.count()):
                    if self.__tabBar.tabRect(i).contains(pos):
                        break
                
                if i == self.__tabBar.currentIndex():
                    if self.isMinimized():
                        self.expand()
                    else:
                        self.shrink()
                    return True
                elif self.isMinimized():
                    self.expand()
            elif evt.type() == QEvent.Wheel and not self.__stackedWidget.isHidden():
                if evt.delta() > 0:
                    self.prevTab()
                else:
                    self.nextTab()
                return True
        
        return QWidget.eventFilter(self, obj, evt)
    
    def addTab(self, widget, iconOrLabel, label = None):
        """
        Public method to add a tab to the sidebar.
        
        @param widget reference to the widget to add (QWidget)
        @param iconOrLabel reference to the icon or the labeltext of the tab
            (QIcon, string or QString)
        @param label the labeltext of the tab (string or QString) (only to be
            used, if the second parameter is a QIcon)
        """
        if label:
            index = self.__tabBar.addTab(iconOrLabel, label)
            self.__tabBar.setTabToolTip(index, label)
        else:
            index = self.__tabBar.addTab(iconOrLabel)
            self.__tabBar.setTabToolTip(index, iconOrLabel)
        self.__stackedWidget.addWidget(widget)
        if self.__orientation in [E4SideBar.North, E4SideBar.South]:
            self.__minSize = self.minimumSizeHint().height()
        else:
            self.__minSize = self.minimumSizeHint().width()
    
    def insertTab(self, index, widget, iconOrLabel, label = None):
        """
        Public method to insert a tab into the sidebar.
        
        @param index the index to insert the tab at (integer)
        @param widget reference to the widget to insert (QWidget)
        @param iconOrLabel reference to the icon or the labeltext of the tab
            (QIcon, string or QString)
        @param label the labeltext of the tab (string or QString) (only to be
            used, if the second parameter is a QIcon)
        """
        if label:
            index = self.__tabBar.insertTab(index, iconOrLabel, label)
            self.__tabBar.setTabToolTip(index, label)
        else:
            index = self.__tabBar.insertTab(index, iconOrLabel)
            self.__tabBar.setTabToolTip(index, iconOrLabel)
        self.__stackedWidget.insertWidget(index, widget)
        if self.__orientation in [E4SideBar.North, E4SideBar.South]:
            self.__minSize = self.minimumSizeHint().height()
        else:
            self.__minSize = self.minimumSizeHint().width()
    
    def removeTab(self, index):
        """
        Public method to remove a tab.
        
        @param index the index of the tab to remove (integer)
        """
        self.__stackedWidget.removeWidget(self.__stackedWidget.widget(index))
        self.__tabBar.removeTab(index)
        if self.__orientation in [E4SideBar.North, E4SideBar.South]:
            self.__minSize = self.minimumSizeHint().height()
        else:
            self.__minSize = self.minimumSizeHint().width()
    
    def clear(self):
        """
        Public method to remove all tabs.
        """
        while self.count() > 0:
            self.removeTab(0)
    
    def prevTab(self):
        """
        Public slot used to show the previous tab.
        """
        ind = self.currentIndex() - 1
        if ind == -1:
            ind = self.count() - 1
            
        self.setCurrentIndex(ind)
        self.currentWidget().setFocus()
    
    def nextTab(self):
        """
        Public slot used to show the next tab.
        """
        ind = self.currentIndex() + 1
        if ind == self.count():
            ind = 0
            
        self.setCurrentIndex(ind)
        self.currentWidget().setFocus()
    
    def count(self):
        """
        Public method to get the number of tabs.
        
        @return number of tabs in the sidebar (integer)
        """
        return self.__tabBar.count()
    
    def currentIndex(self):
        """
        Public method to get the index of the current tab.
        
        @return index of the current tab (integer)
        """
        return self.__stackedWidget.currentIndex()
    
    def setCurrentIndex(self, index):
        """
        Public slot to set the current index.
        
        @param index the index to set as the current index (integer)
        """
        self.__tabBar.setCurrentIndex(index)
        self.__stackedWidget.setCurrentIndex(index)
        if self.isMinimized():
            self.expand()
    
    def currentWidget(self):
        """
        Public method to get a reference to the current widget.
        
        @return reference to the current widget (QWidget)
        """
        return self.__stackedWidget.currentWidget()
    
    def setCurrentWidget(self, widget):
        """
        Public slot to set the current widget.
        
        @param widget reference to the widget to become the current widget (QWidget)
        """
        self.__stackedWidget.setCurrentWidget(widget)
        self.__tabBar.setCurrentIndex(self.__stackedWidget.currentIndex())
        if self.isMinimized():
            self.expand()
    
    def indexOf(self, widget):
        """
        Public method to get the index of the given widget.
        
        @param widget reference to the widget to get the index of (QWidget)
        @return index of the given widget (integer)
        """
        return self.__stackedWidget.indexOf(widget)
    
    def isTabEnabled(self, index):
        """
        Public method to check, if a tab is enabled.
        
        @param index index of the tab to check (integer)
        @return flag indicating the enabled state (boolean)
        """
        return self.__tabBar.isTabEnabled(index)
    
    def setTabEnabled(self, index, enabled):
        """
        Public method to set the enabled state of a tab.
        
        @param index index of the tab to set (integer)
        @param enabled enabled state to set (boolean)
        """
        self.__tabBar.setTabEnabled(index, enabled)
    
    def orientation(self):
        """
        Public method to get the orientation of the sidebar.
        
        @return orientation of the sidebar (North, East, South, West)
        """
        return self.__orientation
    
    def setOrientation(self, orient):
        """
        Public method to set the orientation of the sidebar.
        @param orient orientation of the sidebar (North, East, South, West)
        """
        if orient == E4SideBar.North:
            self.__tabBar.setShape(QTabBar.RoundedNorth)
            self.__tabBar.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
            self.barLayout.setDirection(QBoxLayout.LeftToRight)
            self.layout.setDirection(QBoxLayout.TopToBottom)
            self.layout.setAlignment(self.barLayout, Qt.AlignLeft)
        elif orient == E4SideBar.East:
            self.__tabBar.setShape(QTabBar.RoundedEast)
            self.__tabBar.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding)
            self.barLayout.setDirection(QBoxLayout.TopToBottom)
            self.layout.setDirection(QBoxLayout.RightToLeft)
            self.layout.setAlignment(self.barLayout, Qt.AlignTop)
        elif orient == E4SideBar.South:
            self.__tabBar.setShape(QTabBar.RoundedSouth)
            self.__tabBar.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
            self.barLayout.setDirection(QBoxLayout.LeftToRight)
            self.layout.setDirection(QBoxLayout.BottomToTop)
            self.layout.setAlignment(self.barLayout, Qt.AlignLeft)
        elif orient == E4SideBar.West:
            self.__tabBar.setShape(QTabBar.RoundedWest)
            self.__tabBar.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding)
            self.barLayout.setDirection(QBoxLayout.TopToBottom)
            self.layout.setDirection(QBoxLayout.LeftToRight)
            self.layout.setAlignment(self.barLayout, Qt.AlignTop)
        self.__orientation = orient
    
    def tabIcon(self, index):
        """
        Public method to get the icon of a tab.
        
        @param index index of the tab (integer)
        @return icon of the tab (QIcon)
        """
        return self.__tabBar.tabIcon(index)
    
    def setTabIcon(self, index, icon):
        """
        Public method to set the icon of a tab.
        
        @param index index of the tab (integer)
        @param icon icon to be set (QIcon)
        """
        self.__tabBar.setTabIcon(index, icon)
    
    def tabText(self, index):
        """
        Public method to get the text of a tab.
        
        @param index index of the tab (integer)
        @return text of the tab (QString)
        """
        return self.__tabBar.tabText(index)
    
    def setTabText(self, index, text):
        """
        Public method to set the text of a tab.
        
        @param index index of the tab (integer)
        @param text text to set (QString)
        """
        self.__tabBar.setTabText(index, text)
    
    def tabToolTip(self, index):
        """
        Public method to get the tooltip text of a tab.
        
        @param index index of the tab (integer)
        @return tooltip text of the tab (QString)
        """
        return self.__tabBar.tabToolTip(index)
    
    def setTabToolTip(self, index, tip):
        """
        Public method to set the tooltip text of a tab.
        
        @param index index of the tab (integer)
        @param tooltip text text to set (QString)
        """
        self.__tabBar.setTabToolTip(index, tip)
    
    def tabWhatsThis(self, index):
        """
        Public method to get the WhatsThis text of a tab.
        
        @param index index of the tab (integer)
        @return WhatsThis text of the tab (QString)
        """
        return self.__tabBar.tabWhatsThis(index)
    
    def setTabWhatsThis(self, index, text):
        """
        Public method to set the WhatsThis text of a tab.
        
        @param index index of the tab (integer)
        @param WhatsThis text text to set (QString)
        """
        self.__tabBar.setTabWhatsThis(index, text)
    
    def widget(self, index):
        """
        Public method to get a reference to the widget associated with a tab.
        
        @param index index of the tab (integer)
        @return reference to the widget (QWidget)
        """
        return self.__stackedWidget.widget(index)
    
    def saveState(self):
        """
        Public method to save the state of the sidebar.
        
        @return saved state as a byte array (QByteArray)
        """
        if len(self.splitterSizes) == 0:
            if self.splitter:
                self.splitterSizes = self.splitter.sizes()
            self.__bigSize = self.size()
            if self.__orientation in [E4SideBar.North, E4SideBar.South]:
                self.__minSize = self.minimumSizeHint().height()
                self.__maxSize = self.maximumHeight()
            else:
                self.__minSize = self.minimumSizeHint().width()
                self.__maxSize = self.maximumWidth()
        
        data = QByteArray()
        stream = QDataStream(data, QIODevice.WriteOnly)
        
        stream.writeUInt16(self.Version)
        stream.writeBool(self.__minimized)
        stream << self.__bigSize
        stream.writeUInt16(self.__minSize)
        stream.writeUInt16(self.__maxSize)
        stream.writeUInt16(len(self.splitterSizes))
        for size in self.splitterSizes:
            stream.writeUInt16(size)
        stream.writeBool(self.__autoHide)
        
        return data
    
    def restoreState(self, state):
        """
        Public method to restore the state of the sidebar.
        
        @param state byte array containing the saved state (QByteArray)
        @return flag indicating success (boolean)
        """
        if state.isEmpty():
            return False
        
        if self.__orientation in [E4SideBar.North, E4SideBar.South]:
            minSize = self.layout.minimumSize().height()
            maxSize = self.maximumHeight()
        else:
            minSize = self.layout.minimumSize().width()
            maxSize = self.maximumWidth()
        
        data = QByteArray(state)
        stream = QDataStream(data, QIODevice.ReadOnly)
        stream.readUInt16()  # version
        minimized = stream.readBool()
        
        if minimized:
            self.shrink()
        
        stream >> self.__bigSize
        self.__minSize = max(stream.readUInt16(), minSize)
        self.__maxSize = max(stream.readUInt16(), maxSize)
        count = stream.readUInt16()
        self.splitterSizes = []
        for i in range(count):
            self.splitterSizes.append(stream.readUInt16())
        
        self.__autoHide = stream.readBool()
        self.__autoHideButton.setChecked(not self.__autoHide)
        
        if not minimized:
            self.expand()
        
        return True
    
    #######################################################################
    ## methods below implement the autohide functionality
    #######################################################################
    
    def __autoHideToggled(self, checked):
        """
        Private slot to handle the toggling of the autohide button.
        
        @param checked flag indicating the checked state of the button (boolean)
        """
        self.__autoHide = not checked
        if self.__autoHide:
            self.__autoHideButton.setIcon(UI.PixmapCache.getIcon("autoHideOn.png"))
        else:
            self.__autoHideButton.setIcon(UI.PixmapCache.getIcon("autoHideOff.png"))
    
    def __appFocusChanged(self, old, now):
        """
        Private slot to handle a change of the focus.
        
        @param old reference to the widget, that lost focus (QWidget or None)
        @param now reference to the widget having the focus (QWidget or None)
        """
        self.__hasFocus = self.isAncestorOf(now)
        if self.__autoHide and not self.__hasFocus and not self.isMinimized():
            self.shrink()
        elif self.__autoHide and self.__hasFocus and self.isMinimized():
            self.expand()
    
    def enterEvent(self, event):
        """
        Protected method to handle the mouse entering this widget.
        
        @param event reference to the event (QEvent)
        """
        if self.__autoHide and self.isMinimized():
            self.expand()
        else:
            self.__cancelDelayTimer()
    
    def leaveEvent(self, event):
        """
        Protected method to handle the mouse leaving this widget.
        
        @param event reference to the event (QEvent)
        """
        if self.__autoHide and not self.__hasFocus and not self.isMinimized():
            self.shrink()
        else:
            self.__cancelDelayTimer()
    
    def shutdown(self):
        """
        Public method to shut down the object.
        
        This method does some preparations so the object can be deleted properly.
        It disconnects from the focusChanged signal in order to avoid trouble later
        on.
        """
        self.disconnect(e4App(), SIGNAL("focusChanged(QWidget*, QWidget*)"), 
                        self.__appFocusChanged)
Example #5
0
class Visualizer:

    def __init__(self, scene):

        self.scene = scene

        # Pool of items to grab from when needed.
        self.taskItemPool = list()

        # The items that are active and to be updated.
        self.activeItems = list()

        # Dictionary of PIDs and an associated item to represent the process.
        self.taskItemPids = dict()

        # Scales the visualization dimension for cpu%.
        self.cpuScale = 2.0

        # Setup memory axis visualization.
        self.memAxisLen = 1000
        self.memXOffset = -10
        self._memTickInterval = 0.1  # As a percentage of the memScale.
        self.memAxis = None
        self.setup_mem_axis()

        # Set the color codes for each task type.
        self.userTaskColor = Qt.red
        self.rootTaskColor = Qt.darkGray
        self.otherTaskColor = Qt.cyan

        # Spacing between each task graphical item
        self.taskItemSpacing = 20

        self.showRootTasks = False

        # Set up the timers to update the visualization.
        self.updateProcessDataTimer = QTimer()
        self.updateProcessDataTimer.timeout.connect(self.update_processes_data)
        self.updateItemsTimer = QTimer()
        self.updateItemsTimer.timeout.connect(self.update_items)

        # Fill the pool of task items.
        for i in range(0, 200):
            self.add_new_task_graphics_item(False)

        self.updateProcessDataTimer.start(1000.0)
        self.updateItemsTimer.start(50.0)

    def update_processes_data(self):

        # Reset USED flag in order to determine which task items are unused. This
        # will happen when an item's process does not exist anymore.
        for item in self.activeItems:
            item.used = False

        processes = StateManager.get_processes_data()

        # Update the graphical representations
        x = 0
        for p in processes:

            if p[USER_INDEX] == ROOT_NAME and not self.showRootTasks:
                continue

            pid = p[PID_INDEX]

            # Pid still exists.
            if pid in self.taskItemPids:

                item = self.taskItemPids[pid]

                # Check if PID returned after it ended. This means that the mapped
                # item MAY be associated with another process.
                if item.pid != pid:
                    item = self.fetch_task_item()
                    self.taskItemPids[pid] = item
                    item.pid = pid

                # Remove from pool and add to active.
                elif item in self.taskItemPool:
                    self.taskItemPool.remove(item)
                    self.activeItems.append(item)

                # ELSE: reuse item, the process returned continuously.
                item.setVisible(True)
                item.used = True

            # Pid not in dictionary, Add task item.
            else:
                item = self.fetch_task_item()
                self.taskItemPids[pid] = item
                item.pid = pid
                item.used = True

            # CPU %
            new_diameter = p[CPU_INDEX] * self.cpuScale + 2

            # MEM %
            y = p[MEM_INDEX] / 100.0 * self.memAxisLen

            # Center around on y-component.
            y -= new_diameter / 2.0

            pos = item.rect().topLeft()
            diameter = item.rect().width()

            # Set position bounds.
            item.startPos = pos
            item.endPos = QPointF(x, y)

            # Setup diameter bounds.
            item.startDiameter = diameter
            item.endDiameter = new_diameter

            x += new_diameter + self.taskItemSpacing

            # Setup the name of the task.
            item.set_name(p[NAME_INDEX])

            if p[USER_INDEX] == USER_NAME:
                item.setBrush(QBrush(self.userTaskColor))
                item.textItem.setDefaultTextColor(Qt.white)

            elif p[USER_INDEX] == ROOT_NAME:
                item.setBrush(QBrush(self.rootTaskColor))
                item.textItem.setDefaultTextColor(self.rootTaskColor)

            else:
                item.setBrush(QBrush(self.otherTaskColor))
                item.textItem.setDefaultTextColor(Qt.white)

        self.recycle_unused_items()

        # Update the scene so it can repaint properly
        self.scene.update()

    def update_items(self):

        time_step = self.updateItemsTimer.interval() / float(self.updateProcessDataTimer.interval())

        for item in self.activeItems:
            item.update(time_step)

    def fetch_task_item(self):

        # Add more graphic items if there isn't enough in the pool.
        if len(self.taskItemPool) == 0:
            self.add_new_task_graphics_item()

        item = self.taskItemPool.pop()
        item.setVisible(True)
        self.activeItems.append(item)

        return item

    def recycle_task_item(self, item):
        item.setVisible(False)
        self.taskItemPool.append(item)

    # Items without an active process are recycled
    def recycle_unused_items(self):

        # Recycle unused.
        for item in self.activeItems:
            if not item.used:
                self.recycle_task_item(item)

        # Only keep the used items.
        self.activeItems = [item for item in self.activeItems if item.used]

    def add_new_task_graphics_item(self, visible=True):
        item = TaskGraphicsItem(QRectF(0, 0, 1, 1))
        item.setVisible(visible)
        self.taskItemPool.append(item)
        self.scene.addItem(item)

    def setup_mem_axis(self):
        self.memAxis = QGraphicsLineItem(self.memXOffset, 0, self.memXOffset, self.memAxisLen, None, self.scene)
        self.memAxis.setPen(QPen(Qt.white))

        step = int(self._memTickInterval * self.memAxisLen)

        pen = QPen(Qt.CustomDashLine)
        pen.setDashPattern([2, 20])
        pen.setColor(Qt.lightGray)

        for y in range(step, self.memAxisLen + step, step):
            self.scene.addLine(self.memXOffset, y, 2500.0, y, pen)

    def update_mem_axis(self):

        # Set Length of line to the x value of the last active task item.
        pass
class AppWatcher(QObject):
    # Signals
    error = pyqtSignal(str)

    check_phrases = (
        'Some licenses cannot be used',
        'Some of the pages have not been',
        'Process failed'
    )

    def __init__(self, proc, parent=None):
        super(AppWatcher, self).__init__(parent)
        self.proc = proc
        self.pid = proc.pid
        self.polls_without_dialog = 0
        self.current_temp_path = None
        print 'PID:', self.pid

    def start(self):
        print 'Starting watcher'
        self.abbyy_app = pywinauto.Application().connect(process=self.pid)
        """@type : Application"""
        self.abbyy_dialog = self.abbyy_app.window_(class_name='#32770')

        print QThread.currentThread()
        self.polling_timer = QTimer()
        self.polling_timer.setInterval(500)
        self.polling_timer.timeout.connect(self.poll)
        self.polling_timer.start()

    def poll(self):
        if not self.current_temp_path:
            self.current_temp_path = get_abbyy_temp_folder(self.pid)
            print 'Temp path set to', self.current_temp_path
        if not self.proc.poll() is None:
            # Application seems to have exited.
            print 'Abbyy quit?'
            self.error.emit('Abbyy exited before being able to process the file.')
            return
        try:
            dialog_exists = self.abbyy_dialog.Exists()
        except pywinauto.WindowAmbiguousError:
            dialog_exists = True
        if dialog_exists:
            # We have a dialog. Read it!
            static_texts = ''
            try:  # Wrap in a try block in case the pesky window vanishes while we're reading...
                try:
                    static_texts = ' '.join([
                        ' '.join(self.abbyy_dialog.Static.Texts()),
                        ' '.join(self.abbyy_dialog.Static2.Texts())
                    ]).strip()
                except (MemoryError, OverflowError):
                    print 'Memory error when attempting to read text'
                except pywinauto.findwindows.WindowAmbiguousError:
                    # More than one dialog.
                    try:
                        static_texts = ' '.join(' '.join(c.Texts()) for c in chain.from_iterable(
                            x.Children() for x in self.abbyy_app.windows_(class_name='#32770')))
                    except (MemoryError, OverflowError):
                        print 'Memory error when attempting to read text'
                        static_texts = ''
                except TypeError:
                    # Bug in pywinauto, possibly for when there is no window text.
                    static_texts = ''
                if static_texts:
                    for phrase in self.check_phrases:
                        if phrase in static_texts:
                            self.error.emit(phrase)
                            self.abbyy_app.kill_()
                            break
            except pywinauto.findwindows.WindowNotFoundError:
                print 'Window went away. No biggy.'
                return
        else:
            self.polls_without_dialog += 1
            print '{0:.1f} seconds without activity...'.format(
                (self.polls_without_dialog * self.polling_timer.interval()) / 1000.0)
            if self.polls_without_dialog >= 20:
                # Abbyy is running but it doesn't look like it's doing anything. Kill it.
                self.error.emit('Abbyy was idle for too long without a dialog.')
                self.abbyy_app.kill_()
                return
class BBClient(QObject):

    deviceOffline = pyqtSignal()
    tracksReady = pyqtSignal(list)
    error = pyqtSignal(str)
    unsupportedBBVersion = pyqtSignal(str)


    uploadStatus = pyqtSignal(str)
    stravaCredentialsNeeded = pyqtSignal()
    stravaUploadStarted = pyqtSignal(list)
    stravaUploadProgress = pyqtSignal(list)
    stravaUploadFinished = pyqtSignal(list)



    def __init__(self, parent=None, strava_username=None,
                 strava_password=None, bb_url=BASE_BB_URL):

        super(BBClient, self).__init__(parent)
        self._bb_url = bb_url

        self._connected = False
        self._first_run = True
        self._status_timer = None

        self._strava_username = strava_username
        self._strava_password = strava_password

        self._tracks = []
        self._exp_tracks = []


    def _onThreadStart(self):

        self._status_timer = QTimer(self)
        self._status_timer.timeout.connect(self._checkStatus)
        self._strava = StravaUploader()

        self.error.connect(self._onError)


    def _onError(self):
        self._status_timer.stop()
        self._connected = False
        self._first_run = True
        self._tracks = []
        self._exp_tracks = []


    def onAbortUpload(self):

        self._exp_tracks = []




    def onUploadTracks(self, track_ids):

        self._track_ids = track_ids

        if not self._exp_tracks:
            self.uploadStatus.emit('Exporting tracks')
            self._exp_tracks = self._exportTracks(track_ids)


        if not self._strava.authenticated:

            self.uploadStatus.emit('Authenticating to Strava')
            if not self._strava_username:
                self.stravaCredentialsNeeded.emit()
                return

            try:
                self._strava.authenticate(self._strava_username,
                                          self._strava_password)
            except StravaError as e:
                self._strava_username = ''
                self.uploadStatus.emit('Retrying authenticating to Strava')
                self.stravaCredentialsNeeded.emit()
                return


        tracks = self._exp_tracks
        self._exp_tracks = []

        self.uploadStatus.emit(
            'Uploading to strava<br>(Can sometimes be a little slow)')
        try:
            status = self._strava.upload(tracks)
        except StravaError as e:
            self.error.emit(e.reason)
            return

        self.stravaUploadStarted.emit(status.uploads)

        while True:

            time.sleep(2)

            finished, progress = status.check_progress()

            if not finished:
                self.stravaUploadProgress.emit(progress)
            else:
                self.stravaUploadFinished.emit(progress)
                return




    def onStravaCredentials(self, username, password):
        self._strava_username = username
        self._strava_password = password

        self.onUploadTracks(self._track_ids)

    def onClearStravaCredentials(self):
        self._strava_username = None
        self._strava_password = None
        if self._strava.authenticated:
            self._strava = StravaUploader()


    def onStart(self):
        if self._status_timer is not None:
            self._status_timer.start(1000)
        else:
            QTimer.singleShot(1000, self.onStart)



    def _checkStatus(self):

        info = self._bbRequest('/device/info')
        if info is None:
            return

        if info['connected']:
            if not self._connected and 'Device' in info:
                self._connected = True
                self._status_timer.setInterval(6000)
                self.tracksReady.emit(info['Device']['tracks'])
                self._tracks = info['Device']['tracks']

                if 'BB' in info and 'version' in info['BB']:
                    if info['BB']['version'] not in SUPPORTED_VERSIONS:
                        self.unsupportedBBVersion.emit(info['BB']['version'])

        else:
            if self._connected or self._first_run:
                self._connected = False
                self._status_timer.setInterval(3000)
                self.deviceOffline.emit()


        if self._first_run and self._status_timer.interval() == 1000:
            self._status_timer.setInterval(3000)

        self._first_run = False



    def _exportTracks(self, ids):

        if not self._tracks:
            return []

        tmp_path = tempfile.mkdtemp()

        resp = self._bbRequest('/device/do/export', fmt='tcx',
                               list=','.join(map(str, ids)),
                               num=len(self._tracks), dest=tmp_path)

        content = []

        if resp is not None and 'ok' in resp and resp['ok']:

            names = os.listdir(tmp_path)

            if len(names) == len(ids):

                matches = self._matchNames(ids, names)

                if matches is None:
                    self.error.emit('Failed to export tracks')
                else:
                    for name in matches:
                        with open(os.path.join(tmp_path, name)) as f:
                            content.append((name, f.read()))

            else:
                self.error.emit('Failed to export tracks')

        shutil.rmtree(tmp_path)

        return content




    def _matchNames(self, ids, filenames):

        ret = []

        for i, fname in zip(ids, filenames):

            name = self._tracks[i]

            name = name.replace('/', '').replace(' ', '').replace(':', '')

            if fname.startswith(name):
                ret.append(fname)

        if len(ret) == len(filenames):
            return ret

        ret = []

        fnames = filenames[:]

        for i in ids:

            name = self._tracks[i]

            name = name.replace('/', '').replace(' ', '').replace(':', '')

            for fname in fnames:
                if fname.startswith(name):
                    ret.append(fname)
                    fnames.remove(fname)
                    break

        if len(ret) == len(filenames):
            return ret

        return None


    def _bbRequest(self, path, **args):

        url = urlparse.urljoin(self._bb_url, path)

        if args:
            url += '?' + urllib.urlencode(args)

        error = 'Unknown network error'
        try:
            req = urllib2.urlopen(url)
            return json.loads(req.read())

        except urllib2.URLError as e:
            if e.reason.errno == errno.ECONNREFUSED:
                error = 'Failed to connect to BrytonBridge'
        except urllib.HTTPError as e:
            if e.reason == 'Not Found':
                error = 'Unknown network error'
        else:
            pass

        self.error.emit(error)