示例#1
0
    def __createLayout(self):
        """Creates the widget layout"""
        totalCalls = self.__stats.total_calls
        # The calls were not induced via recursion
        totalPrimitiveCalls = self.__stats.prim_calls
        totalTime = self.__stats.total_tt

        txt = "<b>Script:</b> " + self.__script + " " + \
              self.__params['arguments'] + "<br/>" \
              "<b>Run at:</b> " + self.__reportTime + "<br/>" + \
              str(totalCalls) + " function calls (" + \
              str(totalPrimitiveCalls) + " primitive calls) in " + \
              FLOAT_FORMAT % totalTime + " CPU seconds"
        summary = QLabel(txt)
        summary.setToolTip(txt)
        summary.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Fixed)
        summary.setStyleSheet('QLabel {' + getLabelStyle(self) + '}')

        self.__scene = QGraphicsScene()
        self.__viewer = DiagramWidget()
        self.__viewer.sigEscapePressed.connect(self.__onESC)

        vLayout = QVBoxLayout()
        vLayout.setContentsMargins(0, 0, 0, 0)
        vLayout.setSpacing(0)
        vLayout.addWidget(summary)
        vLayout.addWidget(self.__viewer)

        self.setLayout(vLayout)
示例#2
0
class FlowUIWidget(QWidget):

    """The widget which goes along with the text editor"""

    def __init__(self, editor, parent):
        QWidget.__init__(self, parent)

        # It is always not visible at the beginning because there is no
        # editor content at the start
        self.setVisible(False)

        self.__editor = editor
        self.__parentWidget = parent
        self.__connected = False
        self.__needPathUpdate = False

        self.cflowSettings = getCflowSettings(self)
        self.__displayProps = (self.cflowSettings.hidedocstrings,
                               self.cflowSettings.hidecomments,
                               self.cflowSettings.hideexcepts,
                               Settings()['smartZoom'])

        hLayout = QHBoxLayout()
        hLayout.setContentsMargins(0, 0, 0, 0)
        hLayout.setSpacing(0)

        vLayout = QVBoxLayout()
        vLayout.setContentsMargins(0, 0, 0, 0)
        vLayout.setSpacing(0)

        # Make pylint happy
        self.__toolbar = None
        self.__navBar = None
        self.__cf = None
        self.__canvas = None
        self.__validGroups = []
        self.__allGroupId = set()

        # Create the update timer
        self.__updateTimer = QTimer(self)
        self.__updateTimer.setSingleShot(True)
        self.__updateTimer.timeout.connect(self.process)

        vLayout.addWidget(self.__createNavigationBar())
        vLayout.addWidget(self.__createStackedViews())

        hLayout.addLayout(vLayout)
        hLayout.addWidget(self.__createToolbar())
        self.setLayout(hLayout)

        self.updateSettings()

        # Connect to the change file type signal
        self.__mainWindow = GlobalData().mainWindow
        editorsManager = self.__mainWindow.editorsManagerWidget.editorsManager
        editorsManager.sigFileTypeChanged.connect(self.__onFileTypeChanged)

        Settings().sigHideDocstringsChanged.connect(
            self.__onHideDocstringsChanged)
        Settings().sigHideCommentsChanged.connect(self.__onHideCommentsChanged)
        Settings().sigHideExceptsChanged.connect(self.__onHideExceptsChanged)
        Settings().sigSmartZoomChanged.connect(self.__onSmartZoomChanged)

        self.setSmartZoomLevel(Settings()['smartZoom'])

    def getParentWidget(self):
        return self.__parentWidget

    def view(self):
        """Provides a reference to the current view"""
        return self.smartViews.currentWidget()

    def scene(self):
        """Provides a reference to the current scene"""
        return self.view().scene

    def __createToolbar(self):
        """Creates the toolbar"""
        self.__toolbar = QToolBar(self)
        self.__toolbar.setOrientation(Qt.Vertical)
        self.__toolbar.setMovable(False)
        self.__toolbar.setAllowedAreas(Qt.RightToolBarArea)
        self.__toolbar.setIconSize(QSize(16, 16))
        self.__toolbar.setFixedWidth(30)
        self.__toolbar.setContentsMargins(0, 0, 0, 0)

        # Buttons
        saveAsMenu = QMenu(self)
        saveAsSVGAct = saveAsMenu.addAction(getIcon('filesvg.png'),
                                            'Save as SVG...')
        saveAsSVGAct.triggered.connect(self.onSaveAsSVG)

        saveAsPDFAct = saveAsMenu.addAction(getIcon('filepdf.png'),
                                            'Save as PDF...')
        saveAsPDFAct.triggered.connect(self.onSaveAsPDF)
        saveAsPNGAct = saveAsMenu.addAction(getIcon('filepixmap.png'),
                                            'Save as PNG...')
        saveAsPNGAct.triggered.connect(self.onSaveAsPNG)
        saveAsMenu.addSeparator()
        saveAsCopyToClipboardAct = saveAsMenu.addAction(
            getIcon('copymenu.png'), 'Copy to clipboard')
        saveAsCopyToClipboardAct.triggered.connect(self.copyToClipboard)

        self.__saveAsButton = QToolButton(self)
        self.__saveAsButton.setIcon(getIcon('saveasmenu.png'))
        self.__saveAsButton.setToolTip('Save as')
        self.__saveAsButton.setPopupMode(QToolButton.InstantPopup)
        self.__saveAsButton.setMenu(saveAsMenu)
        self.__saveAsButton.setFocusPolicy(Qt.NoFocus)

        self.__levelUpButton = QToolButton(self)
        self.__levelUpButton.setFocusPolicy(Qt.NoFocus)
        self.__levelUpButton.setIcon(getIcon('levelup.png'))
        self.__levelUpButton.setToolTip('Smart zoom level up (Shift+wheel)')
        self.__levelUpButton.clicked.connect(self.onSmartZoomLevelUp)
        self.__levelIndicator = QLabel('<b>0</b>', self)
        self.__levelIndicator.setAlignment(Qt.AlignCenter)
        self.__levelDownButton = QToolButton(self)
        self.__levelDownButton.setFocusPolicy(Qt.NoFocus)
        self.__levelDownButton.setIcon(getIcon('leveldown.png'))
        self.__levelDownButton.setToolTip('Smart zoom level down (Shift+wheel)')
        self.__levelDownButton.clicked.connect(self.onSmartZoomLevelDown)

        fixedSpacer = QWidget()
        fixedSpacer.setFixedHeight(10)

        self.__hideDocstrings = QToolButton(self)
        self.__hideDocstrings.setCheckable(True)
        self.__hideDocstrings.setIcon(getIcon('hidedocstrings.png'))
        self.__hideDocstrings.setToolTip('Show/hide docstrings')
        self.__hideDocstrings.setFocusPolicy(Qt.NoFocus)
        self.__hideDocstrings.setChecked(Settings()['hidedocstrings'])
        self.__hideDocstrings.clicked.connect(self.__onHideDocstrings)
        self.__hideComments = QToolButton(self)
        self.__hideComments.setCheckable(True)
        self.__hideComments.setIcon(getIcon('hidecomments.png'))
        self.__hideComments.setToolTip('Show/hide comments')
        self.__hideComments.setFocusPolicy(Qt.NoFocus)
        self.__hideComments.setChecked(Settings()['hidecomments'])
        self.__hideComments.clicked.connect(self.__onHideComments)
        self.__hideExcepts = QToolButton(self)
        self.__hideExcepts.setCheckable(True)
        self.__hideExcepts.setIcon(getIcon('hideexcepts.png'))
        self.__hideExcepts.setToolTip('Show/hide except blocks')
        self.__hideExcepts.setFocusPolicy(Qt.NoFocus)
        self.__hideExcepts.setChecked(Settings()['hideexcepts'])
        self.__hideExcepts.clicked.connect(self.__onHideExcepts)

        spacer = QWidget()
        spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

        self.__toolbar.addWidget(self.__saveAsButton)
        self.__toolbar.addWidget(spacer)
        self.__toolbar.addWidget(self.__levelUpButton)
        self.__toolbar.addWidget(self.__levelIndicator)
        self.__toolbar.addWidget(self.__levelDownButton)
        self.__toolbar.addWidget(fixedSpacer)
        self.__toolbar.addWidget(self.__hideDocstrings)
        self.__toolbar.addWidget(self.__hideComments)
        self.__toolbar.addWidget(self.__hideExcepts)
        return self.__toolbar

    def __createNavigationBar(self):
        """Creates the navigation bar"""
        self.__navBar = ControlFlowNavigationBar(self)
        return self.__navBar

    def __createStackedViews(self):
        """Creates the graphics view"""
        self.smartViews = QStackedWidget(self)
        self.smartViews.setContentsMargins(0, 0, 0, 0)

        self.smartViews.addWidget(CFGraphicsView(self.__navBar, self))
        self.smartViews.addWidget(CFGraphicsView(self.__navBar, self))
        return self.smartViews

    def process(self):
        """Parses the content and displays the results"""
        if not self.__connected:
            self.__connectEditorSignals()

        start = timer()
        cf = getControlFlowFromMemory(self.__editor.text)
        end = timer()
        if cf.errors:
            self.__navBar.updateInfoIcon(self.__navBar.STATE_BROKEN_UTD)
            errors = []
            for err in cf.errors:
                if err[0] == -1 and err[1] == -1:
                    errors.append(err[2])
                elif err[1] == -1:
                    errors.append('[' + str(err[0]) + ':] ' + err[2])
                elif err[0] == -1:
                    errors.append('[:' + str(err[1]) + '] ' + err[2])
                else:
                    errors.append('[' + str(err[0]) + ':' +
                                  str(err[1]) + '] ' + err[2])
            self.__navBar.setErrors(errors)
            return

        self.__cf = cf
        if self.isDebugMode():
            logging.info('Parsed file: %s', formatFlow(str(self.__cf)))
            logging.info('Parse timing: %f', end - start)

        # Collect warnings (parser + CML warnings) and valid groups
        self.__validGroups = []
        self.__allGroupId = set()
        allWarnings = self.__cf.warnings + \
                      CMLVersion.validateCMLComments(self.__cf,
                                                     self.__validGroups,
                                                     self.__allGroupId)

        # That will clear the error tooltip as well
        self.__navBar.updateInfoIcon(self.__navBar.STATE_OK_UTD)

        if allWarnings:
            warnings = []
            for warn in allWarnings:
                if warn[0] == -1 and warn[1] == -1:
                    warnings.append(warn[2])
                elif warn[1] == -1:
                    warnings.append('[' + str(warn[0]) + ':] ' + warn[2])
                elif warn[0] == -1:
                    warnings.append('[:' + str(warn[1]) + '] ' + warn[2])
                else:
                    warnings.append('[' + str(warn[0]) + ':' +
                                    str(warn[1]) + '] ' + warn[2])
            self.__navBar.setWarnings(warnings)
        else:
            self.__navBar.clearWarnings()

        self.redrawScene()

    def __cleanupCanvas(self):
        """Cleans up the canvas"""
        if self.__canvas is not None:
            self.__canvas.cleanup()
            self.__canvas = None
        for item in self.scene().items():
            item.cleanup()
        self.scene().clear()

    def redrawScene(self):
        """Redraws the scene"""
        smartZoomLevel = Settings()['smartZoom']
        self.cflowSettings = getCflowSettings(self)
        if self.dirty():
            self.__displayProps = (self.cflowSettings.hidedocstrings,
                                   self.cflowSettings.hidecomments,
                                   self.cflowSettings.hideexcepts,
                                   smartZoomLevel)
        self.cflowSettings.itemID = 0
        self.cflowSettings = tweakSmartSettings(self.cflowSettings,
                                                smartZoomLevel)

        try:
            fileName = self.__parentWidget.getFileName()
            if not fileName:
                fileName = self.__parentWidget.getShortName()
            collapsedGroups = getCollapsedGroups(fileName)

            # Top level canvas has no adress and no parent canvas
            self.__cleanupCanvas()
            self.__canvas = VirtualCanvas(self.cflowSettings, None, None,
                                          self.__validGroups, collapsedGroups,
                                          None)
            lStart = timer()
            self.__canvas.layoutModule(self.__cf)
            lEnd = timer()
            self.__canvas.setEditor(self.__editor)
            width, height = self.__canvas.render()
            rEnd = timer()
            self.scene().setSceneRect(0, 0, width, height)
            self.__canvas.draw(self.scene(), 0, 0)
            dEnd = timer()
            if self.isDebugMode():
                logging.info('Redrawing is done. Size: %d x %d', width, height)
                logging.info('Layout timing: %f', lEnd - lStart)
                logging.info('Render timing: %f', rEnd - lEnd)
                logging.info('Draw timing: %f', dEnd - rEnd)
        except Exception as exc:
            logging.error(str(exc))
            raise

    def onFlowZoomChanged(self):
        """Triggered when a flow zoom is changed"""
        if self.__cf:
            selection = self.scene().serializeSelection()
            firstOnScreen = self.scene().getFirstLogicalItem()
            self.cflowSettings.onFlowZoomChanged()
            self.redrawScene()
            self.updateNavigationToolbar('')
            self.scene().restoreSelectionByID(selection)
            self.__restoreScroll(firstOnScreen)

    def __onFileTypeChanged(self, fileName, uuid, newFileType):
        """Triggered when a buffer content type has changed"""
        if self.__parentWidget.getUUID() != uuid:
            return

        if not isPythonMime(newFileType):
            self.__disconnectEditorSignals()
            self.__updateTimer.stop()
            self.__cleanupCanvas()
            self.__cf = None
            self.__validGroups = []
            self.setVisible(False)
            self.__navBar.updateInfoIcon(self.__navBar.STATE_UNKNOWN)
            return

        # Update the bar and show it
        self.setVisible(True)
        self.process()

        # The buffer type change event comes when the content is loaded first
        # time. So this is a good point to restore the position
        _, _, _, cflowHPos, cflowVPos = getFilePosition(fileName)
        self.setScrollbarPositions(cflowHPos, cflowVPos)

    def terminate(self):
        """Called when a tab is closed"""
        if self.__updateTimer.isActive():
            self.__updateTimer.stop()
        self.__updateTimer.deleteLater()

        self.__disconnectEditorSignals()

        self.__mainWindow = GlobalData().mainWindow
        editorsManager = self.__mainWindow.editorsManagerWidget.editorsManager
        editorsManager.sigFileTypeChanged.disconnect(self.__onFileTypeChanged)

        Settings().sigHideDocstringsChanged.disconnect(
            self.__onHideDocstringsChanged)
        Settings().sigHideCommentsChanged.disconnect(self.__onHideCommentsChanged)
        Settings().sigHideExceptsChanged.disconnect(self.__onHideExceptsChanged)
        Settings().sigSmartZoomChanged.disconnect(self.__onSmartZoomChanged)

        # Helps GC to collect more
        self.__cleanupCanvas()
        for index in range(self.smartViews.count()):
            self.smartViews.widget(index).terminate()
            self.smartViews.widget(index).deleteLater()

        self.smartViews.deleteLater()
        self.__navBar.deleteLater()
        self.__cf = None

        self.__saveAsButton.menu().deleteLater()
        self.__saveAsButton.deleteLater()

        self.__levelUpButton.clicked.disconnect(self.onSmartZoomLevelUp)
        self.__levelUpButton.deleteLater()

        self.__levelDownButton.clicked.disconnect(self.onSmartZoomLevelDown)
        self.__levelDownButton.deleteLater()

        self.__hideDocstrings.clicked.disconnect(self.__onHideDocstrings)
        self.__hideDocstrings.deleteLater()

        self.__hideComments.clicked.disconnect(self.__onHideComments)
        self.__hideComments.deleteLater()

        self.__hideExcepts.clicked.disconnect(self.__onHideExcepts)
        self.__hideExcepts.deleteLater()

        self.__toolbar.deleteLater()

        self.__editor = None
        self.__parentWidget = None
        self.cflowSettings = None
        self.__displayProps = None

    def __connectEditorSignals(self):
        """When it is a python file - connect to the editor signals"""
        if not self.__connected:
            self.__editor.cursorPositionChanged.connect(
                self.__cursorPositionChanged)
            self.__editor.textChanged.connect(self.__onBufferChanged)
            self.__connected = True

    def __disconnectEditorSignals(self):
        """Disconnect the editor signals when the file is not a python one"""
        if self.__connected:
            self.__editor.cursorPositionChanged.disconnect(
                self.__cursorPositionChanged)
            self.__editor.textChanged.disconnect(self.__onBufferChanged)
            self.__connected = False

    def __cursorPositionChanged(self):
        """Cursor position changed"""
        # The timer should be reset only in case if the redrawing was delayed
        if self.__updateTimer.isActive():
            self.__updateTimer.stop()
            self.__updateTimer.start(IDLE_TIMEOUT)

    def __onBufferChanged(self):
        """Triggered to update status icon and to restart the timer"""
        self.__updateTimer.stop()
        if self.__navBar.getCurrentState() in [self.__navBar.STATE_OK_UTD,
                                               self.__navBar.STATE_OK_CHN,
                                               self.__navBar.STATE_UNKNOWN]:
            self.__navBar.updateInfoIcon(self.__navBar.STATE_OK_CHN)
        else:
            self.__navBar.updateInfoIcon(self.__navBar.STATE_BROKEN_CHN)
        self.__updateTimer.start(IDLE_TIMEOUT)

    def redrawNow(self):
        """Redraw the diagram regardless of the timer"""
        if self.__updateTimer.isActive():
            self.__updateTimer.stop()
        self.process()

    def generateNewGroupId(self):
        """Generates a new group ID (string)"""
        # It can also consider the current set of the groups: valid + invalid
        # and generate an integer id which is shorter
        for vacantGroupId in range(1000):
            groupId = str(vacantGroupId)
            if not groupId in self.__allGroupId:
                return groupId
        # Last resort
        return str(uuid.uuid1())

    def updateNavigationToolbar(self, text):
        """Updates the toolbar text"""
        if self.__needPathUpdate:
            self.__navBar.setPath(text)

    def updateSettings(self):
        """Updates settings"""
        self.__needPathUpdate = Settings()['showCFNavigationBar']
        self.__navBar.setPathVisible(self.__needPathUpdate)
        self.__navBar.setPath('')

    def highlightAtAbsPos(self, absPos, line, pos):
        """Scrolls the view to the item closest to absPos and selects it.

        line and pos are 1-based
        """
        item, _ = self.scene().getNearestItem(absPos, line, pos)
        if item:
            GlobalData().mainWindow.setFocusToFloatingRenderer()
            self.scene().clearSelection()
            item.setSelected(True)
            self.view().scrollTo(item)
            self.setFocus()

    def setFocus(self):
        """Sets the focus"""
        self.view().setFocus()

    @staticmethod
    def __getDefaultSaveDir():
        """Provides the default directory to save files to"""
        project = GlobalData().project
        if project.isLoaded():
            return project.getProjectDir()
        return QDir.currentPath()

    def __selectFile(self, extension):
        """Picks a file of a certain extension"""
        dialog = QFileDialog(self, 'Save flowchart as')
        dialog.setFileMode(QFileDialog.AnyFile)
        dialog.setLabelText(QFileDialog.Accept, "Save")
        dialog.setNameFilter(extension.upper() + " files (*." +
                             extension.lower() + ")")
        urls = []
        for dname in QDir.drives():
            urls.append(QUrl.fromLocalFile(dname.absoluteFilePath()))
        urls.append(QUrl.fromLocalFile(QDir.homePath()))
        project = GlobalData().project
        if project.isLoaded():
            urls.append(QUrl.fromLocalFile(project.getProjectDir()))
        dialog.setSidebarUrls(urls)

        suggestedFName = self.__parentWidget.getFileName()
        if '.' in suggestedFName:
            dotIndex = suggestedFName.rindex('.')
            suggestedFName = suggestedFName[:dotIndex]

        dialog.setDirectory(self.__getDefaultSaveDir())
        dialog.selectFile(suggestedFName + "." + extension.lower())
        dialog.setOption(QFileDialog.DontConfirmOverwrite, False)
        dialog.setOption(QFileDialog.DontUseNativeDialog, True)
        if dialog.exec_() != QDialog.Accepted:
            return None

        fileNames = dialog.selectedFiles()
        fileName = os.path.abspath(str(fileNames[0]))
        if os.path.isdir(fileName):
            logging.error("A file must be selected")
            return None

        if "." not in fileName:
            fileName += "." + extension.lower()

        # Check permissions to write into the file or to a directory
        if os.path.exists(fileName):
            # Check write permissions for the file
            if not os.access(fileName, os.W_OK):
                logging.error("There is no write permissions for " + fileName)
                return None
        else:
            # Check write permissions to the directory
            dirName = os.path.dirname(fileName)
            if not os.access(dirName, os.W_OK):
                logging.error("There is no write permissions for the "
                              "directory " + dirName)
                return None

        if os.path.exists(fileName):
            res = QMessageBox.warning(
                self, "Save flowchart as",
                "<p>The file <b>" + fileName + "</b> already exists.</p>",
                QMessageBox.StandardButtons(QMessageBox.Abort |
                                            QMessageBox.Save),
                QMessageBox.Abort)
            if res == QMessageBox.Abort or res == QMessageBox.Cancel:
                return None

        # All prerequisites are checked, return a file name
        return fileName

    def onSaveAsSVG(self):
        """Triggered on the 'Save as SVG' button"""
        fileName = self.__selectFile("svg")
        if fileName is None:
            return False

        try:
            self.__saveAsSVG(fileName)
        except Exception as excpt:
            logging.error(str(excpt))
            return False
        return True

    def __saveAsSVG(self, fileName):
        """Saves the flowchart as an SVG file"""
        generator = QSvgGenerator()
        generator.setFileName(fileName)
        generator.setSize(QSize(self.scene().width(), self.scene().height()))
        painter = QPainter(generator)
        self.scene().render(painter)
        painter.end()

    def onSaveAsPDF(self):
        """Triggered on the 'Save as PDF' button"""
        fileName = self.__selectFile("pdf")
        if fileName is None:
            return False

        try:
            self.__saveAsPDF(fileName)
        except Exception as excpt:
            logging.error(str(excpt))
            return False
        return True

    def __saveAsPDF(self, fileName):
        """Saves the flowchart as an PDF file"""
        printer = QPrinter()
        printer.setOutputFormat(QPrinter.PdfFormat)
        printer.setPaperSize(QSizeF(self.scene().width(),
                                    self.scene().height()), QPrinter.Point)
        printer.setFullPage(True)
        printer.setOutputFileName(fileName)

        painter = QPainter(printer)
        self.scene().render(painter)
        painter.end()

    def onSaveAsPNG(self):
        """Triggered on the 'Save as PNG' button"""
        fileName = self.__selectFile("png")
        if fileName is None:
            return False

        try:
            self.__saveAsPNG(fileName)
        except Exception as excpt:
            logging.error(str(excpt))
            return False
        return True

    def __getPNG(self):
        """Renders the scene as PNG"""
        image = QImage(self.scene().width(), self.scene().height(),
                       QImage.Format_ARGB32_Premultiplied)
        painter = QPainter(image)
        # It seems that the better results are without antialiasing
        # painter.setRenderHint( QPainter.Antialiasing )
        self.scene().render(painter)
        painter.end()
        return image

    def __saveAsPNG(self, fileName):
        """Saves the flowchart as an PNG file"""
        image = self.__getPNG()
        image.save(fileName, "PNG")

    def copyToClipboard(self):
        """Copies the rendered scene to the clipboard as an image"""
        image = self.__getPNG()
        clip = QApplication.clipboard()
        clip.setImage(image)

    def getScrollbarPositions(self):
        """Provides the scrollbar positions"""
        hScrollBar = self.view().horizontalScrollBar()
        vScrollBar = self.view().verticalScrollBar()
        return hScrollBar.value(), vScrollBar.value()

    def setScrollbarPositions(self, hPos, vPos):
        """Sets the scrollbar positions for the view"""
        self.view().horizontalScrollBar().setValue(hPos)
        self.view().verticalScrollBar().setValue(vPos)

    def __onHideDocstrings(self):
        """Triggered when a hide docstring button is pressed"""
        Settings()['hidedocstrings'] = not Settings()['hidedocstrings']

    def __onHideDocstringsChanged(self):
        """Signalled by settings"""
        selection = self.scene().serializeSelection()
        firstOnScreen = self.scene().getFirstLogicalItem()
        settings = Settings()
        self.__hideDocstrings.setChecked(settings['hidedocstrings'])
        if self.__checkNeedRedraw():
            self.scene().restoreSelectionByID(selection)
            self.__restoreScroll(firstOnScreen)

    def __onHideComments(self):
        """Triggered when a hide comments button is pressed"""
        Settings()['hidecomments'] = not Settings()['hidecomments']

    def __onHideCommentsChanged(self):
        """Signalled by settings"""
        selection = self.scene().serializeSelection()
        firstOnScreen = self.scene().getFirstLogicalItem()
        settings = Settings()
        self.__hideComments.setChecked(settings['hidecomments'])
        if self.__checkNeedRedraw():
            self.scene().restoreSelectionByID(selection)
            self.__restoreScroll(firstOnScreen)

    def __onHideExcepts(self):
        """Triggered when a hide except blocks button is pressed"""
        Settings()['hideexcepts'] = not Settings()['hideexcepts']

    def __onHideExceptsChanged(self):
        """Signalled by settings"""
        selection = self.scene().serializeSelection()
        firstOnScreen = self.scene().getFirstLogicalItem()
        settings = Settings()
        self.__hideExcepts.setChecked(settings['hideexcepts'])
        if self.__checkNeedRedraw():
            self.scene().restoreSelectionByTooltip(selection)
            self.__restoreScroll(firstOnScreen)

    def __checkNeedRedraw(self):
        """Redraws the scene if necessary when a display setting is changed"""
        editorsManager = self.__mainWindow.editorsManagerWidget.editorsManager
        if self.__parentWidget == editorsManager.currentWidget():
            self.updateNavigationToolbar('')
            self.process()
            return True
        return False

    def dirty(self):
        """True if some other tab has switched display settings"""
        settings = Settings()
        return self.__displayProps[0] != settings['hidedocstrings'] or \
            self.__displayProps[1] != settings['hidecomments'] or \
            self.__displayProps[2] != settings['hideexcepts'] or \
            self.__displayProps[3] != settings['smartZoom']

    def onSmartZoomLevelUp(self):
        """Triggered when an upper smart zoom level was requested"""
        Settings().onSmartZoomIn()

    def onSmartZoomLevelDown(self):
        """Triggered when an lower smart zoom level was requested"""
        Settings().onSmartZoomOut()

    def setSmartZoomLevel(self, smartZoomLevel):
        """Sets the new smart zoom level"""
        maxSmartZoom = Settings().MAX_SMART_ZOOM
        if smartZoomLevel < 0 or smartZoomLevel > maxSmartZoom:
            return

        self.__levelIndicator.setText('<b>' + str(smartZoomLevel) + '</b>')
        self.__levelIndicator.setToolTip(
            getSmartZoomDescription(smartZoomLevel))
        self.__levelUpButton.setEnabled(smartZoomLevel < maxSmartZoom)
        self.__levelDownButton.setEnabled(smartZoomLevel > 0)
        self.smartViews.setCurrentIndex(smartZoomLevel)

    def __onSmartZoomChanged(self):
        """Triggered when a smart zoom changed"""
        selection = self.scene().serializeSelection()
        firstOnScreen = self.scene().getFirstLogicalItem()
        self.setSmartZoomLevel(Settings()['smartZoom'])
        if self.__checkNeedRedraw():
            self.scene().restoreSelectionByTooltip(selection)
            self.__restoreScroll(firstOnScreen)

    def __restoreScroll(self, toItem):
        """Restores the view scrolling to the best possible position"""
        if toItem:
            lineRange = toItem.getLineRange()
            absPosRange = toItem.getAbsPosRange()
            item, _ = self.scene().getNearestItem(absPosRange[0],
                                                  lineRange[0], 0)
            if item:
                self.view().scrollTo(item, True)
                self.view().horizontalScrollBar().setValue(0)

    def validateCollapsedGroups(self, fileName):
        """Checks that there are no collapsed groups which are invalid"""
        if self.__navBar.getCurrentState() != self.__navBar.STATE_OK_UTD:
            return

        collapsedGroups = getCollapsedGroups(fileName)
        if collapsedGroups:
            toBeDeleted = []
            for groupId in collapsedGroups:
                for validId, start, end in self.__validGroups:
                    del start
                    del end
                    if validId == groupId:
                        break
                else:
                    toBeDeleted.append(groupId)

            if toBeDeleted:
                for groupId in toBeDeleted:
                    collapsedGroups.remove(groupId)
                setCollapsedGroups(fileName, collapsedGroups)
        else:
            setCollapsedGroups(fileName, [])

    def getDocItemByAnchor(self, anchor):
        """Provides the graphics item for the given anchor if so"""
        return self.scene().getDocItemByAnchor(anchor)

    @staticmethod
    def isDebugMode():
        """True if it is a debug mode"""
        return GlobalData().skin['debug']
示例#3
0
class ControlFlowNavigationBar(QFrame):
    """Navigation bar at the top of the flow UI widget"""

    STATE_OK_UTD = 0  # Parsed OK, control flow up to date
    STATE_OK_CHN = 1  # Parsed OK, control flow changed
    STATE_BROKEN_UTD = 2  # Parsed with errors, control flow up to date
    STATE_BROKEN_CHN = 3  # Parsed with errors, control flow changed
    STATE_UNKNOWN = 4

    def __init__(self, parent):
        QFrame.__init__(self, parent)
        self.__infoIcon = None
        self.__warningsIcon = None
        self.__layout = None
        self.__pathLabel = None
        self.__createLayout()
        self.__currentIconState = self.STATE_UNKNOWN

    def __createLayout(self):
        """Creates the layout"""
        self.__layout = QHBoxLayout(self)
        self.__layout.setContentsMargins(0, 0, 0, 0)

        # Create info icon
        self.__infoIcon = QLabel(self)
        self.__infoIcon.setPixmap(getPixmap('cfunknown.png'))
        self.__layout.addWidget(self.__infoIcon)

        self.__warningsIcon = QLabel(self)
        self.__warningsIcon.setPixmap(getPixmap('cfwarning.png'))
        self.__layout.addWidget(self.__warningsIcon)

        self.clearWarnings()

        # Create the path label
        self.__pathLabel = HeaderFitLabel(self)
        self.__pathLabel.setTextFormat(Qt.PlainText)
        self.__pathLabel.setAlignment(Qt.AlignLeft)
        self.__pathLabel.setWordWrap(False)
        self.__pathLabel.setTextInteractionFlags(Qt.NoTextInteraction)
        self.__pathLabel.setSizePolicy(QSizePolicy.Expanding,
                                       QSizePolicy.Fixed)
        self.__pathLabel.setMinimumWidth(40)
        self.__layout.addWidget(self.__pathLabel)

        self.__spacer = ToolBarExpandingSpacer(self)
        self.__spacer.setMinimumWidth(0)
        self.__layout.addWidget(self.__spacer)

        # Create the selection label
        self.__selectionLabel = HeaderLabel('', None, self)
        self.__selectionLabel.setTextFormat(Qt.PlainText)
        self.__selectionLabel.setAlignment(Qt.AlignCenter)
        self.__selectionLabel.setWordWrap(False)
        self.__selectionLabel.setTextInteractionFlags(Qt.NoTextInteraction)
        self.__selectionLabel.setSizePolicy(QSizePolicy.Fixed,
                                            QSizePolicy.Fixed)
        self.__selectionLabel.setMinimumWidth(40)
        self.__layout.addWidget(self.__selectionLabel)
        self.setSelectionLabel(0, None)

    def clearWarnings(self):
        """Clears the warnings"""
        self.__warningsIcon.setVisible(False)
        self.__warningsIcon.setToolTip("")

    def setWarnings(self, warnings):
        """Sets the warnings"""
        self.__warningsIcon.setToolTip('Control flow parser warnings:\n' +
                                       '\n'.join(warnings))
        self.__warningsIcon.setVisible(True)

    def clearErrors(self):
        """Clears all the errors"""
        self.__infoIcon.setToolTip('')

    def setErrors(self, errors):
        """Sets the errors"""
        self.__infoIcon.setToolTip('Control flow parser errors:\n' +
                                   '\n'.join(errors))

    def updateInfoIcon(self, state):
        """Updates the information icon"""
        if state == self.__currentIconState:
            return

        if state == self.STATE_OK_UTD:
            self.__infoIcon.setPixmap(getPixmap('cfokutd.png'))
            self.__infoIcon.setToolTip("Control flow is up to date")
            self.__currentIconState = self.STATE_OK_UTD
        elif state == self.STATE_OK_CHN:
            self.__infoIcon.setPixmap(getPixmap('cfokchn.png'))
            self.__infoIcon.setToolTip("Control flow is not up to date; "
                                       "will be updated on idle")
            self.__currentIconState = self.STATE_OK_CHN
        elif state == self.STATE_BROKEN_UTD:
            self.__infoIcon.setPixmap(getPixmap('cfbrokenutd.png'))
            self.__infoIcon.setToolTip("Control flow might be invalid "
                                       "due to invalid python code")
            self.__currentIconState = self.STATE_BROKEN_UTD
        elif state == self.STATE_BROKEN_CHN:
            self.__infoIcon.setPixmap(getPixmap('cfbrokenchn.png'))
            self.__infoIcon.setToolTip("Control flow might be invalid; "
                                       "will be updated on idle")
            self.__currentIconState = self.STATE_BROKEN_CHN
        else:
            # STATE_UNKNOWN
            self.__infoIcon.setPixmap(getPixmap('cfunknown.png'))
            self.__infoIcon.setToolTip("Control flow state is unknown")
            self.__currentIconState = self.STATE_UNKNOWN

    def getCurrentState(self):
        """Provides the current state"""
        return self.__currentIconState

    def setPath(self, txt):
        """Sets the path label content"""
        self.__pathLabel.setText(txt)

    def setPathVisible(self, switchOn):
        """Sets the path visible"""
        self.__pathLabel.setVisible(switchOn)
        self.__spacer.setVisible(not switchOn)

    def setSelectionLabel(self, text, tooltip):
        """Sets selection label"""
        self.__selectionLabel.setText(str(text))
        if tooltip:
            self.__selectionLabel.setToolTip("Selected items:\n" +
                                             str(tooltip))
        else:
            self.__selectionLabel.setToolTip("Number of selected items")

    def resizeEvent(self, event):
        """Editor has resized"""
        QFrame.resizeEvent(self, event)
示例#4
0
class MDTopBar(QFrame):
    """MD widget top bar at the top"""

    STATE_OK_UTD = 0  # Parsed OK, MD up to date
    STATE_OK_CHN = 1  # Parsed OK, MD changed
    STATE_BROKEN_UTD = 2  # Parsed with errors, MD up to date
    STATE_BROKEN_CHN = 3  # Parsed with errors, MD changed
    STATE_UNKNOWN = 4

    def __init__(self, parent):
        QFrame.__init__(self, parent)
        self.__infoIcon = None
        self.__warningsIcon = None
        self.__layout = None
        self.__createLayout()
        self.__currentIconState = self.STATE_UNKNOWN

    def __createLayout(self):
        """Creates the layout"""
        self.setFixedHeight(24)
        self.__layout = QHBoxLayout(self)
        self.__layout.setContentsMargins(0, 0, 0, 0)

        # Create info icon
        self.__infoIcon = QLabel()
        self.__infoIcon.setPixmap(getPixmap('cfunknown.png'))
        self.__layout.addWidget(self.__infoIcon)

        self.__warningsIcon = QLabel()
        self.__warningsIcon.setPixmap(getPixmap('cfwarning.png'))
        self.__layout.addWidget(self.__warningsIcon)

        self.clearWarnings()

        self.__spacer = QWidget()
        self.__spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        self.__spacer.setMinimumWidth(0)
        self.__layout.addWidget(self.__spacer)

    def clearWarnings(self):
        """Clears the warnings"""
        self.__warningsIcon.setVisible(False)
        self.__warningsIcon.setToolTip("")

    def setWarnings(self, warnings):
        """Sets the warnings"""
        self.__warningsIcon.setToolTip('Markdown parser warnings:\n' +
                                       '\n'.join(warnings))
        self.__warningsIcon.setVisible(True)

    def clearErrors(self):
        """Clears all the errors"""
        self.__infoIcon.setToolTip('')

    def setErrors(self, errors):
        """Sets the errors"""
        self.__infoIcon.setToolTip('Markdown parser errors:\n' +
                                   '\n'.join(errors))

    def updateInfoIcon(self, state):
        """Updates the information icon"""
        if state == self.__currentIconState:
            return

        if state == self.STATE_OK_UTD:
            self.__infoIcon.setPixmap(getPixmap('cfokutd.png'))
            self.__infoIcon.setToolTip("Markdown render is up to date")
            self.__currentIconState = self.STATE_OK_UTD
        elif state == self.STATE_OK_CHN:
            self.__infoIcon.setPixmap(getPixmap('cfokchn.png'))
            self.__infoIcon.setToolTip("Markdown render is not up to date; "
                                       "will be updated on idle")
            self.__currentIconState = self.STATE_OK_CHN
        elif state == self.STATE_BROKEN_UTD:
            self.__infoIcon.setPixmap(getPixmap('cfbrokenutd.png'))
            self.__infoIcon.setToolTip("Markdown render might be invalid "
                                       "due to invalid python code")
            self.__currentIconState = self.STATE_BROKEN_UTD
        elif state == self.STATE_BROKEN_CHN:
            self.__infoIcon.setPixmap(getPixmap('cfbrokenchn.png'))
            self.__infoIcon.setToolTip("Markdown render might be invalid; "
                                       "will be updated on idle")
            self.__currentIconState = self.STATE_BROKEN_CHN
        else:
            # STATE_UNKNOWN
            self.__infoIcon.setPixmap(getPixmap('cfunknown.png'))
            self.__infoIcon.setToolTip("Markdown render state is unknown")
            self.__currentIconState = self.STATE_UNKNOWN

    def getCurrentState(self):
        """Provides the current state"""
        return self.__currentIconState

    def resizeEvent(self, event):
        """Editor has resized"""
        QFrame.resizeEvent(self, event)
示例#5
0
class NavigationBar(QFrame):

    """Navigation bar at the top of the editor (python only)"""

    STATE_OK_UTD = 0        # Parsed OK, context up to date
    STATE_OK_CHN = 1        # Parsed OK, context changed
    STATE_BROKEN_UTD = 2    # Parsed with errors, context up to date
    STATE_BROKEN_CHN = 3    # Parsed with errors, context changed
    STATE_UNKNOWN = 4

    def __init__(self, editor, parent):
        QFrame.__init__(self, parent)
        self.__editor = editor
        self.__parentWidget = parent

        # It is always not visible at the beginning because there is no
        # editor content at the start
        self.setVisible(False)

        # There is no parser info used to display values
        self.__currentInfo = None
        self.__currentIconState = self.STATE_UNKNOWN
        self.__connected = False

        # List of PathElement starting after the global scope
        self.__path = []

        self.__createLayout()

        # Create the update timer
        self.__updateTimer = QTimer(self)
        self.__updateTimer.setSingleShot(True)
        self.__updateTimer.timeout.connect(self.updateBar)

        # Connect to the change file type signal
        mainWindow = GlobalData().mainWindow
        editorsManager = mainWindow.editorsManagerWidget.editorsManager
        editorsManager.sigFileTypeChanged.connect(self.__onFileTypeChanged)

    def getEditor(self):
        """Provides the editor"""
        return self.__editor

    def __connectEditorSignals(self):
        """When it is a python file - connect to the editor signals"""
        if not self.__connected:
            self.__editor.cursorPositionChanged.connect(
                self.__cursorPositionChanged)
            self.__editor.textChanged.connect(self.__onBufferChanged)
            self.__connected = True

    def __disconnectEditorSignals(self):
        """Disconnect the editor signals when the file is not a python one"""
        if self.__connected:
            self.__editor.cursorPositionChanged.disconnect(
                self.__cursorPositionChanged)
            self.__editor.textChanged.disconnect(self.__onBufferChanged)
            self.__connected = False

    def __createLayout(self):
        """Creates the layout"""
        self.setFixedHeight(24)
        self.__layout = QHBoxLayout(self)
        self.__layout.setContentsMargins(0, 0, 0, 0)

        # Set the background color

        # Create info icon
        self.__infoIcon = QLabel()
        self.__layout.addWidget(self.__infoIcon)

        self.__globalScopeCombo = NavBarComboBox(self)
        self.__globalScopeCombo.jumpToLine.connect(self.__onJumpToLine)
        self.__layout.addWidget(self.__globalScopeCombo)

        self.__spacer = QWidget()
        self.__spacer.setSizePolicy(QSizePolicy.Expanding,
                                    QSizePolicy.Expanding)
        self.__layout.addWidget(self.__spacer)

    def __updateInfoIcon(self, state):
        """Updates the information icon"""
        if state != self.__currentIconState:
            if state == self.STATE_OK_UTD:
                self.__infoIcon.setPixmap(getPixmap('nbokutd.png'))
                self.__infoIcon.setToolTip("Context is up to date")
                self.__currentIconState = self.STATE_OK_UTD
            elif state == self.STATE_OK_CHN:
                self.__infoIcon.setPixmap(getPixmap('nbokchn.png'))
                self.__infoIcon.setToolTip("Context is not up to date; "
                                           "will be updated on idle")
                self.__currentIconState = self.STATE_OK_CHN
            elif state == self.STATE_BROKEN_UTD:
                self.__infoIcon.setPixmap(getPixmap('nbbrokenutd.png'))
                self.__infoIcon.setToolTip("Context might be invalid "
                                           "due to invalid python code")
                self.__currentIconState = self.STATE_BROKEN_UTD
            else:
                # STATE_BROKEN_CHN
                self.__infoIcon.setPixmap(getPixmap('nbbrokenchn.png'))
                self.__infoIcon.setToolTip("Context might be invalid; "
                                           "will be updated on idle")
                self.__currentIconState = self.STATE_BROKEN_CHN

    def resizeEvent(self, event):
        """Editor has resized"""
        QFrame.resizeEvent(self, event)

    # Arguments: fileName, uuid, newFileType
    def __onFileTypeChanged(self, _, uuid, newFileType):
        """Triggered when a buffer content type has changed"""
        if self.__parentWidget.getUUID() != uuid:
            return

        if isPythonMime(newFileType) and Settings()['showNavigationBar']:
            # Update the bar and show it
            self.setVisible(True)
            self.updateBar()
        else:
            self.__disconnectEditorSignals()
            self.__updateTimer.stop()
            self.__currentInfo = None
            self.setVisible(False)
            self.__currentIconState = self.STATE_UNKNOWN

    def updateSettings(self):
        """Called when navigation bar settings have been updated"""
        textMime = self.__parentWidget.getMime()
        if Settings()['showNavigationBar'] and isPythonMime(textMime):
            self.setVisible(True)
            self.updateBar()
        else:
            self.__disconnectEditorSignals()
            self.__updateTimer.stop()
            self.__currentInfo = None
            self.setVisible(False)

    def updateBar(self):
        """Triggered when the timer is fired"""
        self.__updateTimer.stop()  # just in case

        if not isPythonMime(self.__parentWidget.getMime()):
            return

        if not self.__connected:
            self.__connectEditorSignals()

        # Parse the buffer content
        self.__currentInfo = getBriefModuleInfoFromMemory(self.__editor.text)

        # Decide what icon to use
        if self.__currentInfo.isOK:
            self.__updateInfoIcon(self.STATE_OK_UTD)
        else:
            self.__updateInfoIcon(self.STATE_BROKEN_UTD)

        # Calc the cursor context
        context = getContext(self.__editor, self.__currentInfo, True, False)

        # Display the context
        self.__populateGlobalScope()
        if context.length == 0:
            self.__globalScopeCombo.setCurrentIndex(-1)
        else:
            index = self.__globalScopeCombo.findData(
                context.levels[0][0].line)
            self.__globalScopeCombo.setCurrentIndex(index)

        usedFromStore = 0
        index = 1
        while index < context.length:
            if len(self.__path) < index:
                newPathItem = PathElement(self)
                self.__path.append(newPathItem)
                self.__layout.addWidget(newPathItem.icon)
                self.__layout.addWidget(newPathItem.combo)
                combo = newPathItem.combo
                combo.pathIndex = len(self.__path) - 1
                combo.jumpToLine.connect(self.__onJumpToLine)
            else:
                self.__path[index - 1].icon.setVisible(True)
                self.__path[index - 1].combo.setVisible(True)
                combo = self.__path[index - 1].combo
                combo.clear()

            # Populate the combo box
            self.__populateClassesAndFunctions(context.levels[index - 1][0],
                                               combo)
            combo.setCurrentIndex(combo.findData(context.levels[index][0].line))
            index += 1
            usedFromStore += 1

        # it might need to have one more level with nothing selected
        if context.length > 0:
            if len(context.levels[context.length - 1][0].functions) > 0 or \
               len(context.levels[context.length - 1][0].classes) > 0:
                # Need to add a combo
                if len(self.__path) <= usedFromStore:
                    newPathItem = PathElement(self)
                    self.__path.append(newPathItem)
                    self.__layout.addWidget(newPathItem.icon)
                    self.__layout.addWidget(newPathItem.combo)
                    combo = newPathItem.combo
                    combo.pathIndex = len(self.__path) - 1
                    combo.jumpToLine.connect(self.__onJumpToLine)
                else:
                    self.__path[index - 1].icon.setVisible(True)
                    self.__path[index - 1].combo.setVisible(True)
                    combo = self.__path[index - 1].combo
                    combo.clear()

                self.__populateClassesAndFunctions(
                    context.levels[context.length - 1][0], combo)
                combo.setCurrentIndex(-1)
                usedFromStore += 1

        # Hide extra components if so
        index = usedFromStore
        while index < len(self.__path):
            self.__path[index].icon.setVisible(False)
            self.__path[index].combo.setVisible(False)
            index += 1

        # Make sure the spacer is the last item
        self.__layout.removeWidget(self.__spacer)
        self.__layout.addWidget(self.__spacer)

    def __populateGlobalScope(self):
        """Repopulates the global scope combo box"""
        self.__globalScopeCombo.clear()

        self.__populateClassesAndFunctions(self.__currentInfo,
                                           self.__globalScopeCombo)

        if not Settings()['navbarglobalsimports']:
            return

        if len(self.__currentInfo.globals) == 0 and \
           len(self.__currentInfo.imports) == 0:
            return

        if self.__globalScopeCombo.count() != 0:
            self.__globalScopeCombo.insertSeparator(
                self.__globalScopeCombo.count())

        for glob in self.__currentInfo.globals:
            self.__globalScopeCombo.addItem(getIcon('globalvar.png'),
                                            glob.name, glob.line)
        for imp in self.__currentInfo.imports:
            self.__globalScopeCombo.addItem(getIcon('imports.png'),
                                            imp.name, imp.line)

    @staticmethod
    def __populateClassesAndFunctions(infoObj, combo):
        """Populates the combo with classes and functions from the infoObj"""
        for klass in infoObj.classes:
            combo.addItem(getIcon('class.png'), klass.name, klass.line)
        for func in infoObj.functions:
            if func.isPrivate():
                icon = getIcon('method_private.png')
            elif func.isProtected():
                icon = getIcon('method_protected.png')
            else:
                icon = getIcon('method.png')
            combo.addItem(icon, func.name, func.line)

    def __cursorPositionChanged(self):
        """Cursor position changed"""
        self.__onNeedUpdate()

    def __onBufferChanged(self):
        """Buffer changed"""
        self.__onNeedUpdate()

    def __onNeedUpdate(self):
        """Triggered to update status icon and to restart the timer"""
        self.__updateTimer.stop()
        if self.__currentInfo.isOK:
            self.__updateInfoIcon(self.STATE_OK_CHN)
        else:
            self.__updateInfoIcon(self.STATE_BROKEN_CHN)
        self.__updateTimer.start(IDLE_TIMEOUT)

    def __onJumpToLine(self, line):
        """Triggered when it needs to jump to a line"""
        self.__editor.gotoLine(line, 0)
        self.__editor.setFocus()

    def setFocusToLastCombo(self):
        """Activates the last combo"""
        if self.__currentInfo is None:
            return
        for index in range(len(self.__path) - 1, -1, -1):
            if self.__path[index].combo.isVisible():
                self.__path[index].combo.setFocus()
                self.__path[index].combo.showPopup()
                return

        self.__globalScopeCombo.setFocus()
        self.__globalScopeCombo.showPopup()

    def activateCombo(self, currentCombo, newIndex):
        """Triggered when a neighbour combo should be activated"""
        if newIndex == -1:
            if len(self.__path) > 0:
                if self.__path[0].combo.isVisible():
                    currentCombo.hidePopup()
            self.__globalScopeCombo.setFocus()
            self.__globalScopeCombo.showPopup()
            return

        if newIndex >= len(self.__path):
            # This is the most right one
            return

        if self.__path[newIndex].combo.isVisible():
            currentCombo.hidePopup()
            self.__path[newIndex].combo.setFocus()
            self.__path[newIndex].combo.showPopup()
示例#6
0
    def __init__(self,
                 scriptName,
                 params,
                 reportTime,
                 dataFile,
                 stats,
                 parent=None):
        QWidget.__init__(self, parent)

        self.__table = ProfilerTreeWidget(self)
        self.__table.sigEscapePressed.connect(self.__onEsc)

        self.__script = scriptName
        self.__stats = stats
        project = GlobalData().project
        if project.isLoaded():
            self.__projectPrefix = os.path.dirname(project.fileName)
        else:
            self.__projectPrefix = os.path.dirname(scriptName)
        if not self.__projectPrefix.endswith(os.path.sep):
            self.__projectPrefix += os.path.sep

        self.__table.setAlternatingRowColors(True)
        self.__table.setRootIsDecorated(False)
        self.__table.setItemsExpandable(False)
        self.__table.setSortingEnabled(True)
        self.__table.setItemDelegate(NoOutlineHeightDelegate(4))
        self.__table.setUniformRowHeights(True)
        self.__table.setSelectionMode(QAbstractItemView.SingleSelection)
        self.__table.setSelectionBehavior(QAbstractItemView.SelectRows)

        headerLabels = [
            "", "Calls", "Total time", "Per call", "Cum. time", "Per call",
            "File name:line", "Function", "Callers", "Callees"
        ]
        self.__table.setHeaderLabels(headerLabels)

        headerItem = self.__table.headerItem()
        headerItem.setToolTip(0, "Indication if it is an outside function")
        headerItem.setToolTip(
            1, "Actual number of calls/primitive calls "
            "(not induced via recursion)")
        headerItem.setToolTip(
            2, "Total time spent in function "
            "(excluding time made in calls "
            "to sub-functions)")
        headerItem.setToolTip(
            3, "Total time divided by number "
            "of actual calls")
        headerItem.setToolTip(
            4, "Total time spent in function and all "
            "subfunctions (from invocation till exit)")
        headerItem.setToolTip(
            5, "Cumulative time divided by number "
            "of primitive calls")
        headerItem.setToolTip(6, "Function location")
        headerItem.setToolTip(7, "Function name")
        headerItem.setToolTip(8, "Function callers")
        headerItem.setToolTip(9, "Function callees")

        self.__table.itemActivated.connect(self.__activated)

        totalCalls = self.__stats.total_calls
        # The calls were not induced via recursion
        totalPrimitiveCalls = self.__stats.prim_calls
        totalTime = self.__stats.total_tt

        txt = "<b>Script:</b> " + self.__script + " " + \
              params['arguments'] + "<br/>" \
              "<b>Run at:</b> " + reportTime + "<br/>" + \
              str(totalCalls) + " function calls (" + \
              str(totalPrimitiveCalls) + " primitive calls) in " + \
              FLOAT_FORMAT % totalTime + " CPU seconds"
        summary = QLabel(txt, self)
        summary.setToolTip(txt)
        summary.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Fixed)
        summary.setStyleSheet('QLabel {' + getLabelStyle(summary) + '}')

        vLayout = QVBoxLayout()
        vLayout.setContentsMargins(0, 0, 0, 0)
        vLayout.setSpacing(0)
        vLayout.addWidget(summary)
        vLayout.addWidget(self.__table)

        self.setLayout(vLayout)
        self.__createContextMenu()

        self.__populate(totalTime)
示例#7
0
class SVNPluginLogDialog(QDialog):
    """SVN plugin log dialog"""

    NODIFF = '<html><body bgcolor="#ffffe6"></body></html>'

    def __init__(self, plugin, client, path, logInfo, parent=None):
        QDialog.__init__(self, parent)

        self.__plugin = plugin
        self.__client = client
        self.__path = path
        self.__logInfo = logInfo

        self.__lhsSelected = None
        self.__rhsSelected = None

        self.__createLayout()
        self.setWindowTitle("SVN Log")

        lastIndex = len(self.__logInfo) - 1
        index = 0
        for log in self.__logInfo:
            newItem = LogItem(log)
            self.__logView.addTopLevelItem(newItem)

            if index != lastIndex:
                rev = log.revision.number
                nextRev = self.__logInfo[index + 1].revision.number
                diffButton = self.__createDiffButton(
                    log.revision, self.__logInfo[index + 1].revision)
                if rev is not None and nextRev is not None:
                    diffButton.setToolTip(
                        "Click to see diff to the older revision (r." +
                        str(rev) + " to r." + str(nextRev) + ")")
                else:
                    diffButton.setEnabled(False)
                    diffButton.setToolTip(
                        "Could not determine current or previous revision")
            else:
                diffButton = self.__createDiffButton(None, None)
                diffButton.setEnabled(False)
                diffButton.setToolTip(
                    "Diff to previous revision is not avalable for the first revision"
                )

            self.__logView.setItemWidget(newItem, DIFFTONEXT_COL, diffButton)
            index += 1

        self.__resizeLogView()
        self.__sortLogView()

        self.__logView.setFocus()

    def __createDiffButton(self, rev, prevRev):
        """Creates a diff button for a path"""
        button = DiffButton()
        button.rev = rev
        button.prevRev = prevRev
        self.connect(button, SIGNAL('CustomClick'), self.onDiffBetween)
        return button

    def __resizeLogView(self):
        """Resizes the plugins table"""
        self.__logView.header().setStretchLastSection(True)
        self.__logView.header().resizeSections(QHeaderView.ResizeToContents)
        self.__logView.header().resizeSection(SELECT_COL, 28)
        self.__logView.header().setResizeMode(SELECT_COL, QHeaderView.Fixed)

        self.__logView.header().resizeSection(DIFFTONEXT_COL, 24)
        self.__logView.header().setResizeMode(DIFFTONEXT_COL,
                                              QHeaderView.Fixed)

    def __sortLogView(self):
        """Sorts the log table"""
        self.__logView.sortItems(self.__logView.sortColumn(),
                                 self.__logView.header().sortIndicatorOrder())

    def __createLayout(self):
        """Creates the dialog layout"""
        self.resize(640, 480)
        self.setSizeGripEnabled(True)

        vboxLayout = QVBoxLayout(self)

        # Revisions to compare
        compareGroupbox = QGroupBox(self)
        compareGroupbox.setTitle("Revisions to compare")
        sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(
            compareGroupbox.sizePolicy().hasHeightForWidth())
        compareGroupbox.setSizePolicy(sizePolicy)

        revisionLayout = QHBoxLayout(compareGroupbox)

        self.__lhsRevisionLabel = QLabel()
        self.__lhsRevisionLabel.setFrameStyle(QFrame.StyledPanel)
        self.__lhsResetButton = QToolButton()
        self.__lhsResetButton.setIcon(
            getIcon(pluginHomeDir + 'svnclearrev.png'))
        self.__lhsResetButton.setFocusPolicy(Qt.NoFocus)
        self.__lhsResetButton.setEnabled(False)
        self.__lhsResetButton.setToolTip("Reset revision to compare")
        self.__lhsResetButton.clicked.connect(self.__onLHSReset)
        self.__rhsRevisionLabel = QLabel()
        self.__rhsRevisionLabel.setFrameStyle(QFrame.StyledPanel)
        self.__rhsResetButton = QToolButton()
        self.__rhsResetButton.setIcon(
            getIcon(pluginHomeDir + 'svnclearrev.png'))
        self.__rhsResetButton.setFocusPolicy(Qt.NoFocus)
        self.__rhsResetButton.setEnabled(False)
        self.__rhsResetButton.setToolTip("Reset revision to compare")
        self.__rhsResetButton.clicked.connect(self.__onRHSReset)

        lhsLayout = QHBoxLayout()
        lhsLayout.addWidget(self.__lhsRevisionLabel)
        lhsLayout.addWidget(self.__lhsResetButton)
        rhsLayout = QHBoxLayout()
        rhsLayout.addWidget(self.__rhsRevisionLabel)
        rhsLayout.addWidget(self.__rhsResetButton)
        bothLayout = QVBoxLayout()
        bothLayout.addLayout(lhsLayout)
        bothLayout.addLayout(rhsLayout)
        revisionLayout.addLayout(bothLayout)

        self.__diffButton = QToolButton()
        self.__diffButton.setText("Diff")
        self.__diffButton.setFocusPolicy(Qt.NoFocus)
        self.__diffButton.setEnabled(False)
        self.__diffButton.clicked.connect(self.__onDiff)
        revisionLayout.addWidget(self.__diffButton)
        vboxLayout.addWidget(compareGroupbox)

        # Log table
        logHeaderFrame = QFrame()
        logHeaderFrame.setFrameStyle(QFrame.StyledPanel)
        logHeaderFrame.setAutoFillBackground(True)
        self.__setLightPalette(logHeaderFrame)
        logHeaderFrame.setFixedHeight(24)

        logHeaderLayout = QHBoxLayout()
        logHeaderLayout.setContentsMargins(3, 0, 0, 0)
        logHeaderLayout.addWidget(QLabel("Subversion log of " + self.__path))
        logHeaderFrame.setLayout(logHeaderLayout)
        vboxLayout.addWidget(logHeaderFrame)

        self.__logView = QTreeWidget()
        self.__logView.setAlternatingRowColors(True)
        self.__logView.setRootIsDecorated(False)
        self.__logView.setItemsExpandable(False)
        self.__logView.setSortingEnabled(True)
        self.__logView.setItemDelegate(NoOutlineHeightDelegate(4))

        self.__logViewHeader = QTreeWidgetItem(
            ["", "", "Revision", "Date", "Author", "Message"])
        self.__logView.setHeaderItem(self.__logViewHeader)
        self.__logView.header().setSortIndicator(REVISION_COL,
                                                 Qt.AscendingOrder)
        self.__logView.itemChanged.connect(self.__onLogViewChanged)
        vboxLayout.addWidget(self.__logView)

        # Diff part
        diffHeaderFrame = QFrame()
        diffHeaderFrame.setFrameStyle(QFrame.StyledPanel)
        diffHeaderFrame.setAutoFillBackground(True)
        self.__setLightPalette(diffHeaderFrame)
        diffHeaderFrame.setFixedHeight(24)

        diffLabel = QLabel("Diff")
        diffExpandingSpacer = QSpacerItem(10, 10, QSizePolicy.Expanding)

        self.__showHideDiffButton = QToolButton()
        self.__showHideDiffButton.setAutoRaise(True)
        self.__showHideDiffButton.setIcon(getIcon('less.png'))
        self.__showHideDiffButton.setFixedSize(20, 20)
        self.__showHideDiffButton.setToolTip("Show diff")
        self.__showHideDiffButton.setFocusPolicy(Qt.NoFocus)
        self.__showHideDiffButton.clicked.connect(self.__onShowHideDiff)

        diffLayout = QHBoxLayout()
        diffLayout.setContentsMargins(3, 0, 0, 0)
        diffLayout.addWidget(diffLabel)
        diffLayout.addSpacerItem(diffExpandingSpacer)
        diffLayout.addWidget(self.__showHideDiffButton)
        diffHeaderFrame.setLayout(diffLayout)

        self.__diffViewer = DiffTabWidget()
        self.__diffViewer.setHTML(self.NODIFF)
        self.__diffViewer.setVisible(False)

        vboxLayout.addWidget(diffHeaderFrame)
        vboxLayout.addWidget(self.__diffViewer)

        # Buttons at the bottom
        buttonBox = QDialogButtonBox(self)
        buttonBox.setOrientation(Qt.Horizontal)
        buttonBox.setStandardButtons(QDialogButtonBox.Ok)
        buttonBox.button(QDialogButtonBox.Ok).setDefault(True)
        buttonBox.accepted.connect(self.close)
        vboxLayout.addWidget(buttonBox)

    @staticmethod
    def __setLightPalette(frame):
        """Creates a lighter palette for the widget background"""
        palette = frame.palette()
        background = palette.color(QPalette.Background)
        background.setRgb(min(background.red() + 30, 255),
                          min(background.green() + 30, 255),
                          min(background.blue() + 30, 255))
        palette.setColor(QPalette.Background, background)
        frame.setPalette(palette)

    def __onShowHideDiff(self):
        """On/off the diff section"""
        if self.__diffViewer.isVisible():
            self.__diffViewer.setVisible(False)
            self.__showHideDiffButton.setIcon(getIcon('less.png'))
            self.__showHideDiffButton.setToolTip("Show diff")
        else:
            self.__diffViewer.setVisible(True)
            self.__showHideDiffButton.setIcon(getIcon('more.png'))
            self.__showHideDiffButton.setToolTip("Hide diff")

    def onDiffBetween(self, rev, prevRev):
        """Called when diff is requested between revisions"""
        if not rev or not prevRev:
            return

        QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
        try:
            lhsContent = self.__client.cat(self.__path, prevRev)
            rhsContent = self.__client.cat(self.__path, rev)
        except Exception as exc:
            QApplication.restoreOverrideCursor()
            logging.error(str(exc))
            return
        except:
            QApplication.restoreOverrideCursor()
            logging.error("Unknown error while retrieving " + self.__path +
                          " content from the repository.")
            return
        QApplication.restoreOverrideCursor()

        diff = difflib.unified_diff(lhsContent.splitlines(),
                                    rhsContent.splitlines())
        nodiffMessage = self.__path + " has no difference from revision " + \
            str(prevRev.number) + " to revision " + str(rev.number)
        if diff is None:
            logging.info(nodiffMessage)
            return

        # There are changes, so replace the text and tell about the changes
        diffAsText = '\n'.join(list(diff))
        if diffAsText.strip() == '':
            logging.info(nodiffMessage)
            return

        lhs = "--- revision " + str(prevRev.number)
        diffAsText = diffAsText.replace("--- ", lhs, 1)
        rhs = "+++ revision " + str(rev.number)
        diffAsText = diffAsText.replace("+++ ", rhs, 1)

        self.__diffViewer.setHTML(parse_from_memory(diffAsText, False, True))
        if not self.__diffViewer.isVisible():
            self.__onShowHideDiff()

    def __onDiff(self):
        """Show diff between revisions"""
        self.onDiffBetween(self.__rhsSelected.revision,
                           self.__lhsSelected.revision)

    def __onLogViewChanged(self, item, column):
        """Revision selected for diff"""
        if item.checkState(SELECT_COL) == Qt.Checked:
            # An item has been selected
            if self.__lhsSelected is None:
                self.__lhsSelected = item.logInfo
                self.__normalizeSelected()
                return
            if self.__rhsSelected is None:
                self.__rhsSelected = item.logInfo
                self.__normalizeSelected()
                return

            # Both of the places have been occupied. Pick the one to update.
            if item.logInfo.date > self.__rhsSelected.date:
                self.__rhsSelected = item.logInfo
            else:
                self.__lhsSelected = item.logInfo
            self.__normalizeSelected()
        else:
            # An item has been de-selected
            if self.__lhsSelected is not None:
                if self.__lhsSelected.revision.number == item.logInfo.revision.number:
                    self.__lhsSelected = None
            elif self.__rhsSelected is not None:
                if self.__rhsSelected.revision.number == item.logInfo.revision.number:
                    self.__rhsSelected = None
            self.__normalizeSelected()
        return

    def __onLHSReset(self):
        """Revision removed from diff"""
        if self.__lhsSelected is not None:
            self.__deselectRevision(self.__lhsSelected.revision.number)
        self.__lhsSelected = None
        self.__lhsRevisionLabel.setText("")
        self.__diffButton.setEnabled(False)
        self.__lhsResetButton.setEnabled(False)

    def __onRHSReset(self):
        """Revision removed from diff"""
        if self.__rhsSelected is not None:
            self.__deselectRevision(self.__rhsSelected.revision.number)
        self.__rhsSelected = None
        self.__rhsRevisionLabel.setText("")
        self.__diffButton.setEnabled(False)
        self.__rhsResetButton.setEnabled(False)

    def __deselectRevision(self, revNumber):
        """Deselects a revision in the list"""
        index = 0
        while index < self.__logView.topLevelItemCount():
            item = self.__logView.topLevelItem(index)
            if item.logInfo.revision.number == revNumber:
                item.setCheckState(SELECT_COL, Qt.Unchecked)
                break
            index += 1

    def __normalizeSelected(self):
        """Puts the earliest revision first"""
        if self.__lhsSelected is not None and self.__rhsSelected is not None:
            # It might be necessary to exchange the versions
            if self.__rhsSelected.date < self.__lhsSelected.date:
                temp = self.__rhsSelected
                self.__rhsSelected = self.__lhsSelected
                self.__lhsSelected = temp
            self.__diffButton.setEnabled(True)
        else:
            self.__diffButton.setEnabled(False)

        if self.__lhsSelected is None:
            self.__lhsRevisionLabel.setText("")
            self.__lhsRevisionLabel.setToolTip("")
            self.__lhsResetButton.setEnabled(False)
        else:
            self.__lhsRevisionLabel.setText(
                str(self.__lhsSelected.revision.number) + " (" +
                timestampToString(self.__lhsSelected.date) + ")")
            self.__lhsRevisionLabel.setToolTip(str(self.__lhsSelected.message))
            self.__lhsResetButton.setEnabled(True)

        if self.__rhsSelected is None:
            self.__rhsRevisionLabel.setText("")
            self.__rhsRevisionLabel.setToolTip("")
            self.__rhsResetButton.setEnabled(False)
        else:
            self.__rhsRevisionLabel.setText(
                str(self.__rhsSelected.revision.number) + " (" +
                timestampToString(self.__rhsSelected.date) + ")")
            self.__rhsRevisionLabel.setToolTip(str(self.__rhsSelected.message))
            self.__rhsResetButton.setEnabled(True)