Example #1
0
 def __init__(self, navBar, parent=None):
     QGraphicsScene.__init__(self, parent)
     CFSceneContextMenuMixin.__init__(self)
     CFSceneMouseMixin.__init__(self)
     CFSceneKeyboardMixin.__init__(self)
     self.__navBar = navBar
     self.selectionChanged.connect(self.selChanged)
Example #2
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 = HeaderFitLabel(self)
        summary.setText(txt)
        summary.setToolTip(txt)
        summary.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Fixed)
        summary.setMinimumWidth(10)

        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)
Example #3
0
    def __init__(self, what, options, path="", buf="", parent=None):
        QDialog.__init__(self, parent)
        self.__cancelRequest = False
        self.__inProgress = False

        self.__what = what
        self.__options = options
        self.__path = path  # could be a dir or a file
        self.__buf = buf  # content in case of a modified file

        # Working process data
        self.__participantFiles = []  # Collected list of files
        self.__projectImportDirs = []
        self.__projectImportsCache = {}  # utils.settings -> /full/path/to.py
        self.__dirsToImportsCache = {}  # /dir/path -> { my.mod: path.py, ... }

        self.dataModel = ImportDiagramModel()
        self.scene = QGraphicsScene()

        # Avoid pylint complains
        self.progressBar = None
        self.infoLabel = None

        self.__createLayout()
        self.setWindowTitle('Imports/dependencies diagram generator')
        QTimer.singleShot(0, self.__process)
Example #4
0
 def keyPressEvent(self, event):
     """Handles the key press event"""
     key = event.key()
     modifiers = int(event.modifiers())
     if modifiers in self.__hotKeys:
         if key in self.__hotKeys[modifiers]:
             self.__hotKeys[modifiers][key]()
             event.accept()
             return
     QGraphicsScene.keyPressEvent(self, event)
Example #5
0
 def mouseMoveEvent(self, event):
     """Handles mouse movement"""
     if self.lmbOrigin and self.rubberBand:
         # Draw the rubber band selection rectangle
         rect = QRect(self.lmbOrigin, event.scenePos().toPoint())
         self.rubberBand.setGeometry(rect.normalized())
         if not self.__isRubberBandVisible():
             if abs(rect.left() - rect.right()) >= RUBBER_BAND_MIN_SIZE or \
                abs(rect.top() - rect.bottom()) >= RUBBER_BAND_MIN_SIZE:
                 self.rubberBand.show()
     QGraphicsScene.mouseMoveEvent(self, event)
Example #6
0
    def __createLayout(self):
        """Creates the dialog layout"""
        self.setMinimumWidth(300)
        self.setMinimumHeight(250)
        self.resize(400, 300)
        self.setSizeGripEnabled(True)

        verticalLayout = QVBoxLayout(self)
        verticalLayout.setContentsMargins(5, 5, 5, 5)
        gridLayout = QGridLayout()

        bgLabel = QLabel('Select background color:', self)
        gridLayout.addWidget(bgLabel, 0, 0, 1, 1)
        self.__bgColorButton = ColorButton('', self)
        gridLayout.addWidget(self.__bgColorButton, 0, 1, 1, 1)

        fgLabel = QLabel('Select foreground color:', self)
        gridLayout.addWidget(fgLabel, 1, 0, 1, 1)
        self.__fgColorButton = ColorButton('', self)
        gridLayout.addWidget(self.__fgColorButton, 1, 1, 1, 1)

        borderLabel = QLabel('Select border color (*):', self)
        gridLayout.addWidget(borderLabel, 2, 0, 1, 1)
        self.__borderColorButton = ColorButton('', self)
        gridLayout.addWidget(self.__borderColorButton, 2, 1, 1, 1)

        verticalLayout.addLayout(gridLayout)
        verticalLayout.addWidget(
            QLabel('(*): docstrings use it only when shown as badges'))

        # Sample area
        self.__scene = QGraphicsScene()
        self.__view = QGraphicsView()
        self.__view.setScene(self.__scene)
        verticalLayout.addWidget(self.__view)

        # Buttons at the bottom
        buttonBox = QDialogButtonBox(self)
        buttonBox.setOrientation(Qt.Horizontal)
        buttonBox.setStandardButtons(QDialogButtonBox.Cancel
                                     | QDialogButtonBox.Ok)
        verticalLayout.addWidget(buttonBox)

        buttonBox.accepted.connect(self.accept)
        buttonBox.rejected.connect(self.reject)
Example #7
0
class ImportsDiagramProgress(QDialog):
    """Progress of the diagram generator"""
    def __init__(self, what, options, path="", buf="", parent=None):
        QDialog.__init__(self, parent)
        self.__cancelRequest = False
        self.__inProgress = False

        self.__what = what
        self.__options = options
        self.__path = path  # could be a dir or a file
        self.__buf = buf  # content in case of a modified file

        # Working process data
        self.__participantFiles = []  # Collected list of files
        self.__projectImportDirs = []
        self.__projectImportsCache = {}  # utils.settings -> /full/path/to.py
        self.__dirsToImportsCache = {}  # /dir/path -> { my.mod: path.py, ... }

        self.dataModel = ImportDiagramModel()
        self.scene = QGraphicsScene()

        # Avoid pylint complains
        self.progressBar = None
        self.infoLabel = None

        self.__createLayout()
        self.setWindowTitle('Imports/dependencies diagram generator')
        QTimer.singleShot(0, self.__process)

    def keyPressEvent(self, event):
        """Processes the ESC key specifically"""
        if event.key() == Qt.Key_Escape:
            self.__onClose()
        else:
            QDialog.keyPressEvent(self, event)

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

        verticalLayout = QVBoxLayout(self)

        # Info label
        self.infoLabel = QLabel(self)
        verticalLayout.addWidget(self.infoLabel)

        # Progress bar
        self.progressBar = QProgressBar(self)
        self.progressBar.setValue(0)
        self.progressBar.setOrientation(Qt.Horizontal)
        verticalLayout.addWidget(self.progressBar)

        # Buttons
        buttonBox = QDialogButtonBox(self)
        buttonBox.setOrientation(Qt.Horizontal)
        buttonBox.setStandardButtons(QDialogButtonBox.Close)
        verticalLayout.addWidget(buttonBox)

        buttonBox.rejected.connect(self.__onClose)

    def __onClose(self):
        """triggered when the close button is clicked"""
        self.__cancelRequest = True
        if not self.__inProgress:
            self.close()

    def __buildParticipants(self):
        """Builds a list of participating files and dirs"""
        if self.__what in [
                ImportsDiagramDialog.SingleBuffer,
                ImportsDiagramDialog.SingleFile
        ]:
            # File exists but could be modified
            self.__path = os.path.realpath(self.__path)
            self.__participantFiles.append(self.__path)
            return

        if self.__what == ImportsDiagramDialog.ProjectFiles:
            self.__scanProjectDirs()
            return

        # This is a recursive directory
        self.__path = os.path.realpath(self.__path)
        self.__scanDirForPythonFiles(self.__path + os.path.sep)

    def __scanDirForPythonFiles(self, path):
        """Scans the directory for the python files recursively"""
        for item in os.listdir(path):
            if item in [".svn", ".cvs", '.git', '.hg']:
                continue
            if os.path.isdir(path + item):
                self.__scanDirForPythonFiles(path + item + os.path.sep)
                continue
            if isPythonFile(path + item):
                self.__participantFiles.append(os.path.realpath(path + item))

    def __scanProjectDirs(self):
        """Populates participant lists from the project files"""
        for fName in GlobalData().project.filesList:
            if isPythonFile(fName):
                self.__participantFiles.append(fName)

    def __addBoxInfo(self, box, info):
        """Adds information to the given box if so configured"""
        if info.docstring is not None:
            box.docstring = info.docstring.text

        if self.__options.includeClasses:
            for klass in info.classes:
                box.classes.append(klass)

        if self.__options.includeFuncs:
            for func in info.functions:
                box.funcs.append(func)

        if self.__options.includeGlobs:
            for glob in info.globals:
                box.globs.append(glob)

        if self.__options.includeConnText:
            for imp in info.imports:
                box.imports.append(imp)

    def __addDocstringBox(self, info, fName, modBoxName):
        """Adds a docstring box if needed"""
        if self.__options.includeDocs:
            if info.docstring is not None:
                docBox = DgmDocstring()
                docBox.docstring = info.docstring
                docBox.refFile = fName

                # Add the box and its connection
                docBoxName = self.dataModel.addDocstringBox(docBox)

                conn = DgmConnection()
                conn.kind = DgmConnection.ModuleDoc
                conn.source = modBoxName
                conn.target = docBoxName
                self.dataModel.addConnection(conn)

                # Add rank for better layout
                rank = DgmRank()
                rank.firstObj = modBoxName
                rank.secondObj = docBoxName
                self.dataModel.addRank(rank)

    def __getSytemWideImportDocstring(self, path):
        """Provides the system wide module docstring"""
        if isPythonFile(path):
            try:
                info = GlobalData().briefModinfoCache.get(path)
                if info.docstring is not None:
                    return info.docstring.text
            except:
                pass
        return ''

    @staticmethod
    def __getModuleTitle(fName):
        """Extracts a module name out of the file name"""
        baseTitle = os.path.basename(fName).split('.')[0]
        if baseTitle != "__init__":
            return baseTitle

        # __init__ is not very descriptive. Add a top level dir.
        dirName = os.path.dirname(fName)
        topDir = os.path.basename(dirName)
        return topDir + "(" + baseTitle + ")"

    @staticmethod
    def __isLocalOrProject(fName, resolvedPath):
        """True if the module is a project one or is in the nested dirs"""
        if resolvedPath is None:
            return False
        if not os.path.isabs(resolvedPath):
            return False
        if GlobalData().project.isProjectFile(resolvedPath):
            return True

        resolvedDir = os.path.dirname(resolvedPath)
        baseDir = os.path.dirname(fName)
        return resolvedDir.startswith(baseDir)

    def __addSingleFileToDataModel(self, info, fName):
        """Adds a single file to the data model"""
        if fName.endswith('__init__.py'):
            if not info.classes and not info.functions and \
               not info.globals and not info.imports:
                # Skip dummy init files
                return

        modBox = DgmModule()
        modBox.refFile = fName

        modBox.kind = DgmModule.ModuleOfInterest
        modBox.title = self.__getModuleTitle(fName)

        self.__addBoxInfo(modBox, info)
        modBoxName = self.dataModel.addModule(modBox)
        self.__addDocstringBox(info, fName, modBoxName)

        # Analyze what was imported
        resolvedImports, errors = resolveImports(fName, info.imports)
        if errors:
            message = 'Errors while analyzing ' + fName + ':'
            for err in errors:
                message += '\n    ' + err
            logging.warning(message)

        for item in resolvedImports:
            importName = item[0]  # from name
            resolvedPath = item[1]  # 'built-in', None or absolute path
            importedNames = item[2]  # list of strings

            impBox = DgmModule()
            impBox.title = importName

            if self.__isLocalOrProject(fName, resolvedPath):
                impBox.kind = DgmModule.OtherProjectModule
                impBox.refFile = resolvedPath
                if isPythonFile(resolvedPath):
                    otherInfo = GlobalData().briefModinfoCache.get(
                        resolvedPath)
                    self.__addBoxInfo(impBox, otherInfo)
            else:
                if resolvedPath is None:
                    # e.g. 'import sys' will have None for the path
                    impBox.kind = DgmModule.UnknownModule
                elif os.path.isabs(resolvedPath):
                    impBox.kind = DgmModule.SystemWideModule
                    impBox.refFile = resolvedPath
                    impBox.docstring = \
                        self.__getSytemWideImportDocstring(resolvedPath)
                else:
                    # e.g. 'import time' will have 'built-in' in the path
                    impBox.kind = DgmModule.BuiltInModule

            impBoxName = self.dataModel.addModule(impBox)

            impConn = DgmConnection()
            impConn.kind = DgmConnection.ModuleDependency
            impConn.source = modBoxName
            impConn.target = impBoxName

            if self.__options.includeConnText:
                for impWhat in importedNames:
                    if impWhat:
                        impConn.labels.append(impWhat)
            self.dataModel.addConnection(impConn)

    def __process(self):
        """Accumulation process"""
        # Intermediate working data
        self.__participantFiles = []
        self.__projectImportDirs = []
        self.__projectImportsCache = {}

        self.dataModel.clear()
        self.__inProgress = True

        try:
            self.infoLabel.setText('Building the list of files to analyze...')
            QApplication.processEvents()

            # Build the list of participating python files
            self.__buildParticipants()
            self.__projectImportDirs = \
                GlobalData().project.getImportDirsAsAbsolutePaths()

            QApplication.processEvents()
            if self.__cancelRequest:
                QApplication.restoreOverrideCursor()
                self.close()
                return

            self.progressBar.setRange(0, len(self.__participantFiles))
            index = 1

            # Now, parse the files and build the diagram data model
            if self.__what == ImportsDiagramDialog.SingleBuffer:
                info = getBriefModuleInfoFromMemory(str(self.__buf))
                self.__addSingleFileToDataModel(info, self.__path)
            else:
                infoSrc = GlobalData().briefModinfoCache
                for fName in self.__participantFiles:
                    self.progressBar.setValue(index)
                    self.infoLabel.setText('Analyzing ' + fName + "...")
                    QApplication.processEvents()
                    if self.__cancelRequest:
                        QApplication.restoreOverrideCursor()
                        self.dataModel.clear()
                        self.close()
                        return
                    info = infoSrc.get(fName)
                    self.__addSingleFileToDataModel(info, fName)
                    index += 1

            # The import caches and other working data are not needed anymore
            self.__participantFiles = None
            self.__projectImportDirs = None
            self.__projectImportsCache = None

            # Generating the graphviz layout
            self.infoLabel.setText('Generating layout using graphviz...')
            QApplication.processEvents()

            graph = getGraphFromDescriptionData(self.dataModel.toGraphviz())
            graph.normalize(self.physicalDpiX(), self.physicalDpiY())
            QApplication.processEvents()
            if self.__cancelRequest:
                QApplication.restoreOverrideCursor()
                self.dataModel.clear()
                self.close()
                return

            # Generate graphics scene
            self.infoLabel.setText('Generating graphics scene...')
            QApplication.processEvents()
            self.__buildGraphicsScene(graph)

            # Clear the data model
            self.dataModel = None
        except Exception as exc:
            QApplication.restoreOverrideCursor()
            logging.error(str(exc))
            self.__inProgress = False
            self.__onClose()
            return

        QApplication.restoreOverrideCursor()
        self.infoLabel.setText('Done')
        QApplication.processEvents()
        self.__inProgress = False

        self.accept()

    def __buildGraphicsScene(self, graph):
        """Builds the QT graphics scene"""
        self.scene.clear()
        self.scene.setSceneRect(0, 0, graph.width, graph.height)

        for edge in graph.edges:
            # self.scene.addItem( GraphicsEdge( edge, self ) )
            dataModelObj = self.dataModel.findConnection(edge.tail, edge.head)
            if dataModelObj is None:
                raise Exception("Cannot find the following connection: " +
                                edge.tail + " -> " + edge.head)

            if dataModelObj.kind == DgmConnection.ModuleDoc:
                modObj = self.dataModel.findModule(dataModelObj.source)
                if modObj is None:
                    raise Exception("Cannot find module object: " +
                                    dataModelObj.source)
                self.scene.addItem(ImportsDgmDocConn(edge, modObj))
                continue
            if dataModelObj.kind == DgmConnection.ModuleDependency:
                # Find the source module object first
                modObj = self.dataModel.findModule(dataModelObj.source)
                if modObj is None:
                    raise Exception("Cannot find module object: " +
                                    dataModelObj.source)
                self.scene.addItem(
                    ImportsDgmDependConn(edge, modObj, dataModelObj))

                if edge.label != "":
                    self.scene.addItem(ImportsDgmEdgeLabel(edge, modObj))
                continue

            raise Exception("Unexpected type of connection: " +
                            str(dataModelObj.kind))

        for node in graph.nodes:
            dataModelObj = self.dataModel.findModule(node.name)
            if dataModelObj is None:
                dataModelObj = self.dataModel.findDocstring(node.name)
            if dataModelObj is None:
                raise Exception("Cannot find object " + node.name)

            if isinstance(dataModelObj, DgmDocstring):
                self.scene.addItem(
                    ImportsDgmDocNote(node, dataModelObj.refFile,
                                      dataModelObj.docstring))
                continue

            # OK, this is a module rectangle. Switch by type of the module.
            if dataModelObj.kind == DgmModule.ModuleOfInterest:
                self.scene.addItem(
                    ImportsDgmModuleOfInterest(node, dataModelObj.refFile,
                                               dataModelObj,
                                               self.physicalDpiX()))
            elif dataModelObj.kind == DgmModule.OtherProjectModule:
                self.scene.addItem(
                    ImportsDgmOtherPrjModule(node, dataModelObj.refFile,
                                             dataModelObj,
                                             self.physicalDpiX()))
            elif dataModelObj.kind == DgmModule.SystemWideModule:
                self.scene.addItem(
                    ImportsDgmSystemWideModule(node, dataModelObj.refFile,
                                               dataModelObj.docstring))
            elif dataModelObj.kind == DgmModule.BuiltInModule:
                self.scene.addItem(ImportsDgmBuiltInModule(node))
            elif dataModelObj.kind == DgmModule.UnknownModule:
                self.scene.addItem(ImportsDgmUnknownModule(node))
            else:
                raise Exception("Unexpected type of module: " +
                                str(dataModelObj.kind))

            tooltip = dataModelObj.getTooltip()
            if tooltip:
                pixmap = getPixmap('diagramdoc.png')
                docItem = QGraphicsPixmapItem(pixmap)
                docItem.setToolTip(tooltip)
                posX = node.posX + node.width / 2.0 - pixmap.width() / 2.0
                posY = node.posY - node.height / 2.0 - pixmap.height() / 2.0
                docItem.setPos(posX, posY)
                self.scene.addItem(docItem)
Example #8
0
 def mouseMoveEvent(self, event):
     if self.origin is not None:
         if self.rubberBand:
             rect = QRect(self.origin, event.scenePos().toPoint())
             self.rubberBand.setGeometry(rect.normalized())
     QGraphicsScene.mouseMoveEvent(self, event)
Example #9
0
    def createGraphicsView(self):
        """Creates the central widget"""
        self.scene = QGraphicsScene(self)

        self.view = CFGraphicsView(self)
        self.view.setScene(self.scene)
Example #10
0
class MainWindow(QMainWindow):
    """Main application window"""
    def __init__(self, verbose, fName, warning):
        QMainWindow.__init__(self)

        self.logWidget = None
        self.view = None
        self.scene = None
        self.fName = fName
        self.verbose = verbose
        self.cFlow = None

        self.resize(1400, 800)

        self.updateWindowTitle()
        self.statusBar()
        self.createToolbar()
        self.createLogWindow()
        self.createGraphicsView()

        self.setCentralWidget(self.view)

        if verbose:
            self.logMessage("Using cdmcfparser version " + VERSION)

        if warning:
            self.logMessage(warning)

        if fName:
            # To yeld the main message processing loop
            kickOffTimer = QTimer()
            kickOffTimer.singleShot(200, self.proceedWithFile)

    def createToolbar(self):
        """There are a few buttons on the main window toolbar.

           They are: open, reload, zoom out, zoom in, debug, clear log
        """
        openButton = QAction(QIcon('icons/open.png'), 'Open (Ctrl+O)', self)
        openButton.setShortcut('Ctrl+O')
        openButton.setStatusTip('Open python file')
        openButton.triggered.connect(self.openButtonClicked)

        reloadButton = QAction(QIcon('icons/reload.png'), 'Reload (F5)', self)
        reloadButton.setShortcut('F5')
        reloadButton.setStatusTip('Reload python file')
        reloadButton.triggered.connect(self.reloadButtonClicked)

        zoomoutButton = QAction(QIcon('icons/zoomOut.png'),
                                'Zoom Out (Ctrl+-)', self)
        zoomoutButton.setShortcut('Ctrl+-')
        zoomoutButton.setStatusTip('Zoom Out')
        zoomoutButton.triggered.connect(self.zoomOut)

        zoominButton = QAction(QIcon('icons/zoomIn.png'), 'Zoom In (Ctrl++)',
                               self)
        zoominButton.setShortcut('Ctrl++')
        zoominButton.setStatusTip('Zoom In')
        zoominButton.triggered.connect(self.zoomIn)

        clearLogButton = QAction(QIcon('icons/clear.png'),
                                 'Clear log (Ctrl+R)', self)
        clearLogButton.setShortcut('Ctrl+R')
        clearLogButton.setStatusTip('Clear log')
        clearLogButton.triggered.connect(self.clearButtonClicked)

        # A few separators
        separator = QAction(self)
        separator.setSeparator(True)
        separator1 = QAction(self)
        separator1.setSeparator(True)

        toolbar = self.addToolBar('Toolbar')
        toolbar.setIconSize(QSize(48, 48))
        toolbar.addAction(openButton)
        toolbar.addAction(reloadButton)
        toolbar.addAction(separator)
        toolbar.addAction(zoomoutButton)
        toolbar.addAction(zoominButton)
        toolbar.addAction(separator1)
        toolbar.addAction(clearLogButton)

    def createLogWindow(self):
        """Creates a dockable RO editor for logging"""
        self.logWidget = QTextEdit()
        self.logWidget.setReadOnly(True)
        self.logWidget.setFontFamily("Courier")
        self.logWidget.setFontPointSize(12.0)

        logDockWidget = QDockWidget("Log", self)
        logDockWidget.setObjectName("LogDockWidget")
        logDockWidget.setAllowedAreas(Qt.BottomDockWidgetArea)
        logDockWidget.setWidget(self.logWidget)

        self.addDockWidget(Qt.BottomDockWidgetArea, logDockWidget)

    def zoomIn(self):
        """zoom in the main window"""
        self.view.zoomIn()

    def zoomOut(self):
        """zoom out the main window"""
        self.view.zoomOut()

    def reloadButtonClicked(self):
        """reload button has been clicked"""
        self.proceedWithFile()

    def clearButtonClicked(self):
        """Deletes all the messages from the log window"""
        self.logWidget.clear()

    def createGraphicsView(self):
        """Creates the central widget"""
        self.scene = QGraphicsScene(self)

        self.view = CFGraphicsView(self)
        self.view.setScene(self.scene)

    def updateWindowTitle(self):
        """updates the main window title with the current so file"""
        if self.fName:
            self.setWindowTitle('Control flow for: ' + self.fName)
        else:
            self.setWindowTitle('Control flow for: no file selected')

    def logMessage(self, message):
        """Makes a log message visible in the user interface"""
        timestamp = datetime.datetime.now().strftime('%m-%d-%y %H:%M:%S.%f')
        self.logWidget.append(timestamp + " " + message)
        self.logWidget.update()

    def openButtonClicked(self):
        """Brings up an open dialogue"""
        # By some unknown reasons the following simple way of getting a file is
        # not working:
        # fileName = QFileDialog.getOpenFileName(self, 'Open file',
        #                                        QDir.currentPath())
        #
        # There is however a workaround. Here it is:
        dialog = QFileDialog(self)
        if dialog.exec_() != QDialog.Accepted:
            return

        fileNames = dialog.selectedFiles()
        fileName = str(fileNames[0])

        if not os.path.exists(fileName):
            QMessageBox.critical(
                self, 'Error',
                'The selected file (' + fileName + ') does not exist')
            return

        # Check that the file is a python one
        warning = isPythonFile(fileName)
        if warning is not None:
            QMessageBox.critical(self, 'Error', warning)
            return

        # set the new file name
        self.fName = fileName
        self.updateWindowTitle()

        # initiate the process
        self.proceedWithFile()

    def proceedWithFile(self, needToParse=True):
        """Taks the file from settings and processes it"""
        if needToParse:
            if self.verbose:
                self.logMessage("Parsing file " + self.fName)
            self.cFlow = getControlFlowFromFile(self.fName)
            if self.verbose:
                self.logMessage("Parsed file:")
                self.logMessage(formatFlow(str(self.cFlow)))

            if len(self.cFlow.errors) != 0:
                self.logMessage("No drawing due to parsing errors")
                return

            if len(self.cFlow.warnings) != 0:
                self.logMessage("Parser warnings: ")
                for warn in self.cFlow.warnings:
                    self.logMessage(str(warn[0]) + ": " + warn[1])
        else:
            if self.cFlow is None:
                self.logMessage("No control flow object")
                return
            if len(self.cFlow.errors) != 0:
                self.logMessage("No drawing due to parsing errors")
                return

        self.scene.clear()

        if self.verbose:
            self.logMessage("Layouting ...")
        try:
            # To pick up possibly changed settings
            importlib.reload(cflowsettings)
            cflowSettings = cflowsettings.getDefaultCflowSettings(self)
            if DEBUG:
                cflowSettings.debug = True

            # Top level canvas has no adress and no parent canvas
            canvas = vcanvas.VirtualCanvas(cflowSettings, None, None, None)
            canvas.layout(self.cFlow, CellElement.FILE_SCOPE)
            if self.verbose:
                self.logMessage("Layout is done:")
                self.logMessage(str(canvas))
                self.logMessage("Rendering ...")

            width, height = canvas.render()
            if self.verbose:
                self.logMessage("Rendering is done. Scene size: " +
                                str(width) + "x" + str(height) +
                                ". Drawing ...")

            self.scene.setSceneRect(0, 0, width, height)
            canvas.draw(self.scene, 0, 0)
        except Exception as exc:
            self.logMessage("Exception:\n" + str(exc))
            raise

        if self.verbose:
            self.logMessage("Drawing is done.")
Example #11
0
class ProfileGraphViewer(QWidget):
    """Profiling results as a graph"""

    sigEscapePressed = pyqtSignal()

    def __init__(self,
                 scriptName,
                 params,
                 reportTime,
                 dataFile,
                 stats,
                 parent=None):
        QWidget.__init__(self, parent)

        self.__dataFile = dataFile
        self.__script = scriptName
        self.__reportTime = reportTime
        self.__params = params
        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.__createLayout()
        self.__getDiagramLayout()

        self.__viewer.setScene(self.__scene)

    def setFocus(self):
        """Sets the focus properly"""
        self.__viewer.setFocus()

    def __isOutsideItem(self, fileName):
        """Detects if the record should be shown as an outside one"""
        return not fileName.startswith(self.__projectPrefix)

    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 = HeaderFitLabel(self)
        summary.setText(txt)
        summary.setToolTip(txt)
        summary.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Fixed)
        summary.setMinimumWidth(10)

        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)

    @staticmethod
    def __getDotFont(parts):
        """Provides a QFont object if a font spec is found"""
        for part in parts:
            if 'fontname=' in part:
                fontName = part.replace('fontname=', '')
                fontName = fontName.replace('[', '')
                fontName = fontName.replace(']', '')
                fontName = fontName.replace(',', '')
                return QFont(fontName)
        return None

    def __postprocessFullDotSpec(self, dotSpec):
        """Removes the arrow size, extracts tooltips, extracts font info"""
        nodeFont = None
        edgeFont = None
        tooltips = {}
        processed = []

        for line in dotSpec.splitlines():
            parts = line.split()
            lineModified = False
            if parts:
                if parts[0] == 'node':
                    # need to extract the fontname
                    nodeFont = self.__getDotFont(parts)
                elif parts[0] == 'edge':
                    # need to extract the fontname
                    edgeFont = self.__getDotFont(parts)
                elif parts[0].isdigit():
                    if parts[1] == '->':
                        # certain edge spec: replace arrowsize and font size
                        for index, part in enumerate(parts):
                            if part.startswith('[arrowsize='):
                                modified = parts[:]
                                modified[index] = '[arrowsize="0.0",'
                                processed.append(' '.join(modified))
                            elif part.startswith('fontsize='):
                                size = float(part.split('"')[1])
                                if edgeFont:
                                    edgeFont.setPointSize(size)
                        lineModified = True
                    elif parts[1].startswith('['):
                        # certain node spec: pick the tooltip and font size
                        lineno = None
                        for part in parts:
                            if part.startswith('tooltip='):
                                nodePath = part.split('"')[1]
                                pathLine = nodePath + ':' + str(lineno)
                                tooltips[int(parts[0])] = pathLine
                            elif part.startswith('fontsize='):
                                size = float(part.split('"')[1])
                                if nodeFont:
                                    nodeFont.setPointSize(size)
                            elif part.startswith('label='):
                                try:
                                    lineno = int(part.split(':')[1])
                                except:
                                    pass
            if not lineModified:
                processed.append(line)

        return '\n'.join(processed), tooltips, nodeFont, edgeFont

    def __rungprof2dot(self):
        """Runs gprof2dot which produces a full dot spec"""
        nodeLimit = Settings().getProfilerSettings().nodeLimit
        edgeLimit = Settings().getProfilerSettings().edgeLimit
        with io.StringIO() as buf:
            gprofParser = gprof2dot.PstatsParser(self.__dataFile)
            profileData = gprofParser.parse()
            profileData.prune(nodeLimit / 100.0, edgeLimit / 100.0, False,
                              False)

            dot = gprof2dot.DotWriter(buf)
            dot.strip = False
            dot.wrap = False
            dot.graph(profileData, gprof2dot.TEMPERATURE_COLORMAP)

            output = buf.getvalue()
        return self.__postprocessFullDotSpec(output)

    def __getDiagramLayout(self):
        """Runs external tools to get the diagram layout"""
        fullDotSpec, tooltips, nodeFont, edgeFont = self.__rungprof2dot()

        dotProc = Popen(["dot", "-Tplain"], stdin=PIPE, stdout=PIPE, bufsize=1)
        graphDescr = dotProc.communicate(
            fullDotSpec.encode('utf-8'))[0].decode('utf-8')

        graph = getGraphFromPlainDotData(graphDescr)
        graph.normalize(self.physicalDpiX(), self.physicalDpiY())

        self.__scene.clear()
        self.__scene.setSceneRect(0, 0, graph.width, graph.height)

        for edge in graph.edges:
            self.__scene.addItem(FuncConnection(edge))
            if edge.label != "":
                self.__scene.addItem(FuncConnectionLabel(edge, edgeFont))

        for node in graph.nodes:
            fileName = ""
            lineNumber = 0

            try:
                nodeNameAsInt = int(node.name)
                if nodeNameAsInt in tooltips:
                    parts = tooltips[nodeNameAsInt].rsplit(':', 1)
                    fileName = parts[0]
                    if parts[1].isdigit():
                        lineNumber = int(parts[1])
            except:
                pass

            self.__scene.addItem(
                Function(node, fileName, lineNumber,
                         self.__isOutsideItem(fileName), nodeFont))

    def __onESC(self):
        """Triggered when ESC is clicked"""
        self.sigEscapePressed.emit()

    def onCopy(self):
        """Copies the diagram to the exchange buffer"""
        self.__viewer.onCopy()

    def onSaveAs(self, fileName):
        """Saves the diagram to a file"""
        self.__viewer.onSaveAs(fileName)

    def zoomIn(self):
        """Triggered on the 'zoom in' button"""
        self.__viewer.zoomIn()

    def zoomOut(self):
        """Triggered on the 'zoom out' button"""
        self.__viewer.zoomOut()

    def resetZoom(self):
        """Triggered on the 'zoom reset' button"""
        self.__viewer.resetZoom()
Example #12
0
class CustomColorsDialog(QDialog):
    """Custom colors dialog implementation"""
    def __init__(self, bgcolor, fgcolor, bordercolor, parent=None):
        """colors are instances of QColor"""
        QDialog.__init__(self, parent)
        self.setWindowTitle('Custom colors')

        self.__createLayout()
        self.__bgColorButton.setColor(bgcolor)
        self.__bgColorButton.sigColorChanged.connect(self.__onColorChanged)
        self.__fgColorButton.setColor(fgcolor)
        self.__fgColorButton.sigColorChanged.connect(self.__onColorChanged)
        self.__borderColorButton.setColor(bordercolor)
        self.__borderColorButton.sigColorChanged.connect(self.__onColorChanged)

        QTimer.singleShot(1, self.__onColorChanged)

    def __createLayout(self):
        """Creates the dialog layout"""
        self.setMinimumWidth(300)
        self.setMinimumHeight(250)
        self.resize(400, 300)
        self.setSizeGripEnabled(True)

        verticalLayout = QVBoxLayout(self)
        verticalLayout.setContentsMargins(5, 5, 5, 5)
        gridLayout = QGridLayout()

        bgLabel = QLabel('Select background color:', self)
        gridLayout.addWidget(bgLabel, 0, 0, 1, 1)
        self.__bgColorButton = ColorButton('', self)
        gridLayout.addWidget(self.__bgColorButton, 0, 1, 1, 1)

        fgLabel = QLabel('Select foreground color:', self)
        gridLayout.addWidget(fgLabel, 1, 0, 1, 1)
        self.__fgColorButton = ColorButton('', self)
        gridLayout.addWidget(self.__fgColorButton, 1, 1, 1, 1)

        borderLabel = QLabel('Select border color (*):', self)
        gridLayout.addWidget(borderLabel, 2, 0, 1, 1)
        self.__borderColorButton = ColorButton('', self)
        gridLayout.addWidget(self.__borderColorButton, 2, 1, 1, 1)

        verticalLayout.addLayout(gridLayout)
        verticalLayout.addWidget(
            QLabel('(*): docstrings use it only when shown as badges'))

        # Sample area
        self.__scene = QGraphicsScene()
        self.__view = QGraphicsView()
        self.__view.setScene(self.__scene)
        verticalLayout.addWidget(self.__view)

        # Buttons at the bottom
        buttonBox = QDialogButtonBox(self)
        buttonBox.setOrientation(Qt.Horizontal)
        buttonBox.setStandardButtons(QDialogButtonBox.Cancel
                                     | QDialogButtonBox.Ok)
        verticalLayout.addWidget(buttonBox)

        buttonBox.accepted.connect(self.accept)
        buttonBox.rejected.connect(self.reject)

    def __onColorChanged(self):
        """The user changed the color so redraw the sample"""
        viewWidth = self.__view.width()
        viewHeight = self.__view.height()

        self.__scene.clear()
        # without '-4' scrollbar will appear
        self.__scene.setSceneRect(0, 0, viewWidth - 4, viewHeight - 4)
        block = SampleBlock(getCflowSettings(self), self.backgroundColor(),
                            self.foregroundColor(), self.borderColor(),
                            viewWidth, viewHeight)
        self.__scene.addItem(block)
        self.__scene.update()

    def backgroundColor(self):
        """Provides the background color"""
        return self.__bgColorButton.color()

    def foregroundColor(self):
        """Provides the foreground color"""
        return self.__fgColorButton.color()

    def borderColor(self):
        """Provides the border color"""
        return self.__borderColorButton.color()