Example #1
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']
class VariablesViewer(QWidget):
    """Implements the variables viewer for a debugger"""

    # First group of filters
    FilterGlobalAndLocal = 0
    FilterGlobalOnly = 1
    FilterLocalOnly = 2

    def __init__(self, debugger, parent=None):
        QWidget.__init__(self, parent)

        self.__debugger = debugger
        self.__browser = VariablesBrowser(debugger, self)
        self.__createLayout()

        self.setTabOrder(self.__browser, self.__execStatement)
        self.setTabOrder(self.__execStatement, self.__execButton)

        self.__updateFilter()

    def __createLayout(self):
        """Creates the widget layout"""
        verticalLayout = QVBoxLayout(self)
        verticalLayout.setContentsMargins(0, 0, 0, 0)
        verticalLayout.setSpacing(0)

        self.__headerLabel = HeaderFitLabel(self)
        self.__headerLabel.setText('Variables')
        self.__headerLabel.setSizePolicy(QSizePolicy.Expanding,
                                         QSizePolicy.Fixed)
        self.__headerLabel.setMinimumWidth(10)

        self.__filterMenu = QMenu(self)
        self.__showAllAct = self.__filterMenu.addAction('Show all variables')
        self.__showAllAct.setData('showall')
        self.__filterMenu.addSeparator()
        self.__filters = []
        for title, settingName, _ in VARIABLE_FILTERS:
            action = self.__filterMenu.addAction(title)
            action.setCheckable(True)
            action.setData(settingName)
            self.__filters.append(action)
        self.__filterMenu.aboutToShow.connect(self.__filterMenuAboutToShow)
        self.__filterMenu.triggered.connect(self.__filterMenuTriggered)

        self.__filterButton = QToolButton(self)
        self.__filterButton.setIcon(getIcon('dbgvarflt.png'))
        self.__filterButton.setToolTip('Variable filter')
        self.__filterButton.setPopupMode(QToolButton.InstantPopup)
        self.__filterButton.setMenu(self.__filterMenu)
        self.__filterButton.setFocusPolicy(Qt.NoFocus)
        self.__filterButton.setFixedSize(self.__headerLabel.height(),
                                         self.__headerLabel.height())

        self.__execStatement = CDMComboBox(True)
        self.__execStatement.setSizePolicy(QSizePolicy.Expanding,
                                           QSizePolicy.Expanding)
        self.__execStatement.lineEdit().setToolTip("Execute statement")
        self.__execStatement.setFixedHeight(26)
        self.__execStatement.editTextChanged.connect(
            self.__execStatementChanged)
        self.__execStatement.enterClicked.connect(self.__onEnterInExec)
        self.__execButton = QPushButton("Exec")
        self.__execButton.setEnabled(False)
        self.__execButton.setFixedHeight(26)
        self.__execButton.clicked.connect(self.__onExec)

        self.headerToolbar = QToolBar(self)
        self.headerToolbar.setIconSize(QSize(18, 18))
        self.headerToolbar.setContentsMargins(1, 1, 1, 1)
        self.headerToolbar.addWidget(self.__headerLabel)
        self.headerToolbar.addWidget(self.__filterButton)

        execLayout = QGridLayout()
        execLayout.setContentsMargins(1, 1, 1, 1)
        execLayout.setSpacing(1)
        execLayout.addWidget(self.__execStatement, 0, 0)
        execLayout.addWidget(self.__execButton, 0, 1)

        verticalLayout.addWidget(self.headerToolbar)
        verticalLayout.addWidget(self.__browser)
        verticalLayout.addLayout(execLayout)

    def __filterMenuAboutToShow(self):
        """Debug variable filter menu is about to show"""
        for flt in self.__filters:
            flt.setChecked(Settings()[flt.data()])

    def __filterMenuTriggered(self, act):
        """A filter has been changed"""
        name = act.data()
        if name == 'showall':
            for _, settingName, _ in VARIABLE_FILTERS:
                Settings()[settingName] = True
        else:
            Settings()[name] = not Settings()[name]
        self.__updateFilter()

    def updateVariables(self, areGlobals, frameNumber, variables):
        """Triggered when a new set of variables is received"""
        self.__browser.showVariables(areGlobals, variables, frameNumber)
        self.__updateHeaderLabel()

    def updateVariable(self, areGlobals, variables):
        """Triggered when a new variable has been received"""
        self.__browser.showVariable(areGlobals, variables)
        self.__updateHeaderLabel()

    def __updateHeaderLabel(self):
        """Updates the header text"""
        shown, total = self.__browser.getShownAndTotalCounts()
        if shown == 0 and total == 0:
            self.__headerLabel.setText("Variables")
        else:
            self.__headerLabel.setText("Variables (" + str(shown) + " of " +
                                       str(total) + ")")

    def __updateFilter(self):
        """Updates the current filter"""
        self.__browser.filterChanged()
        self.__updateHeaderLabel()

    def clear(self):
        """Clears the content"""
        self.__browser.clear()
        self.__updateHeaderLabel()

    def clearAll(self):
        """Clears everything including the history"""
        self.clear()
        self.__execStatement.lineEdit().setText("")
        self.__execStatement.clear()

    def __execStatementChanged(self, text):
        """Triggered when a exec statement is changed"""
        text = str(text).strip()
        self.__execButton.setEnabled(text != "")

    def __onEnterInExec(self):
        """Enter/return clicked in exec"""
        self.__onExec()

    def __onExec(self):
        """Triggered when the Exec button is clicked"""
        text = self.__execStatement.currentText().strip()
        if text != "":
            currentFrame = GlobalData().mainWindow.getCurrentFrameNumber()
            self.__debugger.remoteExecuteStatement(text, currentFrame)
            self.__debugger.remoteClientVariables(1, currentFrame)  # globals
            self.__debugger.remoteClientVariables(0, currentFrame)  # locals

    def switchControl(self, isInIDE):
        """Switches the UI depending where the control flow is"""
        self.__browser.setEnabled(isInIDE)
        self.__filterButton.setEnabled(isInIDE)

        self.__execStatement.setEnabled(isInIDE)
        if isInIDE:
            text = self.__execStatement.currentText().strip()
            self.__execButton.setEnabled(text != "")
        else:
            self.__execButton.setEnabled(False)
Example #3
0
class TextEditorTabWidget(QWidget):
    """Plain text editor tab widget"""

    sigReloadRequest = pyqtSignal()
    reloadAllNonModifiedRequest = pyqtSignal()
    sigTabRunChanged = pyqtSignal(bool)

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

        extendInstance(self, MainWindowTabWidgetBase)
        MainWindowTabWidgetBase.__init__(self)

        self.__navigationBar = None
        self.__editor = TextEditor(self, debugger)
        self.__fileName = ""
        self.__shortName = ""

        self.__createLayout()

        self.__editor.redoAvailable.connect(self.__redoAvailable)
        self.__editor.undoAvailable.connect(self.__undoAvailable)
        self.__editor.modificationChanged.connect(self.modificationChanged)
        self.__editor.sigCFlowSyncRequested.connect(self.cflowSyncRequested)
        self.__editor.languageChanged.connect(self.__languageChanged)

        self.__diskModTime = None
        self.__diskSize = None
        self.__reloadDlgShown = False

        self.__debugMode = False

        self.__vcsStatus = None

    def onTextZoomChanged(self):
        """Triggered when a text zoom is changed"""
        self.__editor.onTextZoomChanged()

    def onFlowZoomChanged(self):
        """Triggered when a flow zoom is changed"""
        self.__flowUI.onFlowZoomChanged()

    def getNavigationBar(self):
        """Provides a reference to the navigation bar"""
        return self.__navigationBar

    def shouldAcceptFocus(self):
        """True if it can accept the focus"""
        return self.__outsideChangesBar.isHidden()

    def readFile(self, fileName):
        """Reads the text from a file"""
        self.__editor.readFile(fileName)
        self.setFileName(fileName)
        self.__editor.restoreBreakpoints()

        # Memorize the modification date
        path = os.path.realpath(fileName)
        self.__diskModTime = os.path.getmtime(path)
        self.__diskSize = os.path.getsize(path)

    def writeFile(self, fileName):
        """Writes the text to a file"""
        if self.__editor.writeFile(fileName):
            # Memorize the modification date
            path = os.path.realpath(fileName)
            self.__diskModTime = os.path.getmtime(path)
            self.__diskSize = os.path.getsize(path)
            self.setFileName(fileName)
            self.__editor.restoreBreakpoints()
            return True
        return False

    def __createLayout(self):
        """Creates the toolbar and layout"""
        # Buttons
        printButton = QAction(getIcon('printer.png'), 'Print (Ctrl+P)', self)
        printButton.triggered.connect(self.__onPrint)

        printPreviewButton = QAction(getIcon('printpreview.png'),
                                     'Print preview', self)
        printPreviewButton.triggered.connect(self.__onPrintPreview)
        printPreviewButton.setEnabled(False)
        printPreviewButton.setVisible(False)

        printSpacer = QWidget()
        printSpacer.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
        printSpacer.setFixedHeight(8)

        # Imports diagram and its menu
        importsMenu = QMenu(self)
        importsDlgAct = importsMenu.addAction(getIcon('detailsdlg.png'),
                                              'Fine tuned imports diagram')
        importsDlgAct.triggered.connect(self.onImportDgmTuned)
        self.importsDiagramButton = QToolButton(self)
        self.importsDiagramButton.setIcon(getIcon('importsdiagram.png'))
        self.importsDiagramButton.setToolTip('Generate imports diagram')
        self.importsDiagramButton.setPopupMode(QToolButton.DelayedPopup)
        self.importsDiagramButton.setMenu(importsMenu)
        self.importsDiagramButton.setFocusPolicy(Qt.NoFocus)
        self.importsDiagramButton.clicked.connect(self.onImportDgm)
        self.importsDiagramButton.setEnabled(False)

        # Run script and its menu
        runScriptMenu = QMenu(self)
        runScriptDlgAct = runScriptMenu.addAction(getIcon('detailsdlg.png'),
                                                  'Set run/debug parameters')
        runScriptDlgAct.triggered.connect(self.onRunScriptDlg)
        self.runScriptButton = QToolButton(self)
        self.runScriptButton.setIcon(getIcon('run.png'))
        self.runScriptButton.setToolTip('Run script')
        self.runScriptButton.setPopupMode(QToolButton.DelayedPopup)
        self.runScriptButton.setMenu(runScriptMenu)
        self.runScriptButton.setFocusPolicy(Qt.NoFocus)
        self.runScriptButton.clicked.connect(self.onRunScript)
        self.runScriptButton.setEnabled(False)

        # Profile script and its menu
        profileScriptMenu = QMenu(self)
        profileScriptDlgAct = profileScriptMenu.addAction(
            getIcon('detailsdlg.png'), 'Set profile parameters')
        profileScriptDlgAct.triggered.connect(self.onProfileScriptDlg)
        self.profileScriptButton = QToolButton(self)
        self.profileScriptButton.setIcon(getIcon('profile.png'))
        self.profileScriptButton.setToolTip('Profile script')
        self.profileScriptButton.setPopupMode(QToolButton.DelayedPopup)
        self.profileScriptButton.setMenu(profileScriptMenu)
        self.profileScriptButton.setFocusPolicy(Qt.NoFocus)
        self.profileScriptButton.clicked.connect(self.onProfileScript)
        self.profileScriptButton.setEnabled(False)

        # Debug script and its menu
        debugScriptMenu = QMenu(self)
        debugScriptDlgAct = debugScriptMenu.addAction(
            getIcon('detailsdlg.png'), 'Set run/debug parameters')
        debugScriptDlgAct.triggered.connect(self.onDebugScriptDlg)
        self.debugScriptButton = QToolButton(self)
        self.debugScriptButton.setIcon(getIcon('debugger.png'))
        self.debugScriptButton.setToolTip('Debug script')
        self.debugScriptButton.setPopupMode(QToolButton.DelayedPopup)
        self.debugScriptButton.setMenu(debugScriptMenu)
        self.debugScriptButton.setFocusPolicy(Qt.NoFocus)
        self.debugScriptButton.clicked.connect(self.onDebugScript)
        self.debugScriptButton.setEnabled(False)

        # Disassembling
        disasmScriptMenu = QMenu(self)
        disasmScriptMenu.addAction(getIcon(''),
                                   'Disassembly (no optimization)',
                                   self.__editor._onDisasm0)
        disasmScriptMenu.addAction(getIcon(''),
                                   'Disassembly (optimization level 1)',
                                   self.__editor._onDisasm1)
        disasmScriptMenu.addAction(getIcon(''),
                                   'Disassembly (optimization level 2)',
                                   self.__editor._onDisasm2)
        self.disasmScriptButton = QToolButton(self)
        self.disasmScriptButton.setIcon(getIcon('disassembly.png'))
        self.disasmScriptButton.setToolTip('Disassembly script')
        self.disasmScriptButton.setPopupMode(QToolButton.DelayedPopup)
        self.disasmScriptButton.setMenu(disasmScriptMenu)
        self.disasmScriptButton.setFocusPolicy(Qt.NoFocus)
        self.disasmScriptButton.clicked.connect(self.__editor._onDisasm0)
        self.disasmScriptButton.setEnabled(False)

        # Dead code
        self.deadCodeScriptButton = QAction(getIcon('deadcode.png'),
                                            'Find dead code', self)
        self.deadCodeScriptButton.triggered.connect(self.__onDeadCode)
        self.deadCodeScriptButton.setEnabled(False)

        undoSpacer = QWidget()
        undoSpacer.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
        undoSpacer.setFixedHeight(8)

        self.__undoButton = QAction(getIcon('undo.png'), 'Undo (Ctrl+Z)', self)
        self.__undoButton.setShortcut('Ctrl+Z')
        self.__undoButton.triggered.connect(self.__editor.onUndo)
        self.__undoButton.setEnabled(False)

        self.__redoButton = QAction(getIcon('redo.png'), 'Redo (Ctrl+Y)', self)
        self.__redoButton.setShortcut('Ctrl+Y')
        self.__redoButton.triggered.connect(self.__editor.onRedo)
        self.__redoButton.setEnabled(False)

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

        self.removeTrailingSpacesButton = QAction(getIcon('trailingws.png'),
                                                  'Remove trailing spaces',
                                                  self)
        self.removeTrailingSpacesButton.triggered.connect(
            self.onRemoveTrailingWS)
        self.expandTabsButton = QAction(getIcon('expandtabs.png'),
                                        'Expand tabs (4 spaces)', self)
        self.expandTabsButton.triggered.connect(self.onExpandTabs)

        # The toolbar
        toolbar = QToolBar(self)
        toolbar.setOrientation(Qt.Vertical)
        toolbar.setMovable(False)
        toolbar.setAllowedAreas(Qt.RightToolBarArea)
        toolbar.setIconSize(QSize(16, 16))
        toolbar.setFixedWidth(30)
        toolbar.setContentsMargins(0, 0, 0, 0)

        toolbar.addAction(printPreviewButton)
        toolbar.addAction(printButton)
        toolbar.addWidget(printSpacer)
        toolbar.addWidget(self.importsDiagramButton)
        toolbar.addWidget(self.runScriptButton)
        toolbar.addWidget(self.profileScriptButton)
        toolbar.addWidget(self.debugScriptButton)
        toolbar.addWidget(self.disasmScriptButton)
        toolbar.addAction(self.deadCodeScriptButton)
        toolbar.addWidget(undoSpacer)
        toolbar.addAction(self.__undoButton)
        toolbar.addAction(self.__redoButton)
        toolbar.addWidget(spacer)
        toolbar.addAction(self.removeTrailingSpacesButton)
        toolbar.addAction(self.expandTabsButton)

        self.importsBar = ImportListWidget(self.__editor)
        self.importsBar.hide()

        self.__outsideChangesBar = OutsideChangeWidget(self.__editor)
        self.__outsideChangesBar.sigReloadRequest.connect(self.__onReload)
        self.__outsideChangesBar.reloadAllNonModifiedRequest.connect(
            self.reloadAllNonModified)
        self.__outsideChangesBar.hide()

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

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

        self.__navigationBar = NavigationBar(self.__editor, self)
        vLayout.addWidget(self.__navigationBar)
        vLayout.addWidget(self.__editor)

        hLayout.addLayout(vLayout)
        hLayout.addWidget(toolbar)
        widget = QWidget()
        widget.setLayout(hLayout)

        self.__splitter = QSplitter(Qt.Horizontal, self)

        self.__flowUI = FlowUIWidget(self.__editor, self)
        self.__mdView = MDWidget(self.__editor, self)

        self.__renderLayout = QVBoxLayout()
        self.__renderLayout.setContentsMargins(0, 0, 0, 0)
        self.__renderLayout.setSpacing(0)
        self.__renderLayout.addWidget(self.__flowUI)
        self.__renderLayout.addWidget(self.__mdView)
        self.__renderWidget = QWidget()
        self.__renderWidget.setLayout(self.__renderLayout)

        self.__splitter.addWidget(widget)
        self.__splitter.addWidget(self.__renderWidget)

        containerLayout = QHBoxLayout()
        containerLayout.setContentsMargins(0, 0, 0, 0)
        containerLayout.setSpacing(0)
        containerLayout.addWidget(self.__splitter)
        self.setLayout(containerLayout)

        self.__renderWidget.setVisible(False)

        self.__splitter.setSizes(Settings()['flowSplitterSizes'])
        self.__splitter.splitterMoved.connect(self.flowSplitterMoved)
        Settings().sigFlowSplitterChanged.connect(self.otherFlowSplitterMoved)

    def flowSplitterMoved(self, pos, index):
        """Splitter has been moved"""
        del pos  # unused argument
        del index  # unused argument
        Settings()['flowSplitterSizes'] = list(self.__splitter.sizes())

    def otherFlowSplitterMoved(self):
        """Other window has changed the splitter position"""
        self.__splitter.setSizes(Settings()['flowSplitterSizes'])

    def updateStatus(self):
        """Updates the toolbar buttons status"""
        self.__updateRunDebugButtons()
        isPythonFile = isPythonMime(self.__editor.mime)
        self.importsDiagramButton.setEnabled(
            isPythonFile and GlobalData().graphvizAvailable)
        self.__editor.diagramsMenu.setEnabled(
            self.importsDiagramButton.isEnabled())
        self.__editor.toolsMenu.setEnabled(self.runScriptButton.isEnabled())

    def onNavigationBar(self):
        """Triggered when navigation bar focus is requested"""
        if self.__navigationBar.isVisible():
            self.__navigationBar.setFocusToLastCombo()
        return True

    def __onPrint(self):
        """Triggered when the print button is pressed"""
        self.__editor._onShortcutPrint()

    def __onPrintPreview(self):
        """Triggered when the print preview button is pressed"""
        pass

    def __onDeadCode(self):
        """Triggered when vulture analysis is requested"""
        GlobalData().mainWindow.tabDeadCodeClicked()

    def __redoAvailable(self, available):
        """Reports redo ops available"""
        self.__redoButton.setEnabled(available)

    def __undoAvailable(self, available):
        """Reports undo ops available"""
        self.__undoButton.setEnabled(available)

    def __languageChanged(self, _=None):
        """Language changed"""
        isPython = self.__editor.isPythonBuffer()
        isMarkdown = self.__editor.isMarkdownBuffer()
        self.disasmScriptButton.setEnabled(isPython)
        self.__renderWidget.setVisible(not Settings()['floatingRenderer']
                                       and (isPython or isMarkdown))

    # Arguments: modified
    def modificationChanged(self, _=None):
        """Triggered when the content is changed"""
        self.__updateRunDebugButtons()

    def __updateRunDebugButtons(self):
        """Enables/disables the run and debug buttons as required"""
        enable = isPythonMime(self.__editor.mime) and \
                 not self.isModified() and \
                 not self.__debugMode and \
                 os.path.isabs(self.__fileName)

        if enable != self.runScriptButton.isEnabled():
            self.runScriptButton.setEnabled(enable)
            self.profileScriptButton.setEnabled(enable)
            self.debugScriptButton.setEnabled(enable)
            self.deadCodeScriptButton.setEnabled(enable)
            self.sigTabRunChanged.emit(enable)

    def isTabRunEnabled(self):
        """Tells the status of run-like buttons"""
        return self.runScriptButton.isEnabled()

    def replaceAll(self, newText):
        """Replaces the current buffer content with a new text"""
        # Unfortunately, the setText() clears the undo history so it cannot be
        # used. The selectAll() and replacing selected text do not suite
        # because after undo the cursor does not jump to the previous position.
        # So, there is an ugly select -> replace manipulation below...
        with self.__editor:
            origLine, origPos = self.__editor.cursorPosition
            self.__editor.setSelection(0, 0, origLine, origPos)
            self.__editor.removeSelectedText()
            self.__editor.insert(newText)
            self.__editor.setCurrentPosition(len(newText))
            line, pos = self.__editor.cursorPosition
            lastLine = self.__editor.lines()
            self.__editor.setSelection(line, pos, lastLine - 1,
                                       len(self.__editor.text(lastLine - 1)))
            self.__editor.removeSelectedText()
            self.__editor.cursorPosition = origLine, origPos

            # These two for the proper cursor positioning after redo
            self.__editor.insert("s")
            self.__editor.cursorPosition = origLine, origPos + 1
            self.__editor.deleteBack()
            self.__editor.cursorPosition = origLine, origPos

    def onRemoveTrailingWS(self):
        """Triggers when the trailing spaces should be wiped out"""
        self.__editor.removeTrailingWhitespaces()

    def onExpandTabs(self):
        """Expands tabs if there are any"""
        self.__editor.expandTabs(4)

    def setFocus(self):
        """Overridden setFocus"""
        if self.__outsideChangesBar.isHidden():
            self.__editor.setFocus()
        else:
            self.__outsideChangesBar.setFocus()

    def onImportDgmTuned(self):
        """Runs the settings dialog first"""
        if self.isModified():
            what = ImportsDiagramDialog.SingleBuffer
            if not os.path.isabs(self.getFileName()):
                logging.warning("Imports diagram can only be generated for "
                                "a file. Save the editor buffer "
                                "and try again.")
                return
        else:
            what = ImportsDiagramDialog.SingleFile
        dlg = ImportsDiagramDialog(what, self.getFileName(), self)
        if dlg.exec_() == QDialog.Accepted:
            # Should proceed with the diagram generation
            self.__generateImportDiagram(what, dlg.options)

    # Arguments: action
    def onImportDgm(self, _=None):
        """Runs the generation process with default options"""
        if self.isModified():
            what = ImportsDiagramDialog.SingleBuffer
            if not os.path.isabs(self.getFileName()):
                logging.warning("Imports diagram can only be generated for "
                                "a file. Save the editor buffer "
                                "and try again.")
                return
        else:
            what = ImportsDiagramDialog.SingleFile
        self.__generateImportDiagram(what, ImportDiagramOptions())

    def __generateImportDiagram(self, what, options):
        """Show the generation progress and display the diagram"""
        if self.isModified():
            progressDlg = ImportsDiagramProgress(what, options,
                                                 self.getFileName(),
                                                 self.__editor.text)
            tooltip = "Generated for modified buffer (" + \
                      self.getFileName() + ")"
        else:
            progressDlg = ImportsDiagramProgress(what, options,
                                                 self.getFileName())
            tooltip = "Generated for file " + self.getFileName()
        if progressDlg.exec_() == QDialog.Accepted:
            GlobalData().mainWindow.openDiagram(progressDlg.scene, tooltip)

    def onOpenImport(self):
        """Triggered when Ctrl+I is received"""
        if isPythonMime(self.__editor.mime):
            # Take all the file imports and resolve them
            fileImports = getImportsList(self.__editor.text)
            if not fileImports:
                GlobalData().mainWindow.showStatusBarMessage(
                    "There are no imports")
            else:
                self.__onImportList(self.__fileName, fileImports)

    def __onImportList(self, fileName, imports):
        """Works with a list of imports"""
        # It has already been checked that the file is a Python one
        resolvedList, errors = resolveImports(fileName, imports)
        del errors  # errors are OK here
        if resolvedList:
            # Display the import selection widget
            self.importsBar.showResolvedImports(resolvedList)
        else:
            GlobalData().mainWindow.showStatusBarMessage(
                "Could not resolve any imports")

    def resizeEvent(self, event):
        """Resizes the import selection dialogue if necessary"""
        self.__editor.hideCompleter()
        QWidget.resizeEvent(self, event)
        self.resizeBars()

    def resizeBars(self):
        """Resize the bars if they are shown"""
        if not self.importsBar.isHidden():
            self.importsBar.resize()
        if not self.__outsideChangesBar.isHidden():
            self.__outsideChangesBar.resize()
        self.__editor.resizeCalltip()

    def showOutsideChangesBar(self, allEnabled):
        """Shows the bar for the editor for the user to choose the action"""
        self.setReloadDialogShown(True)
        self.__outsideChangesBar.showChoice(self.isModified(), allEnabled)

    def __onReload(self):
        """Triggered when a request to reload the file is received"""
        self.sigReloadRequest.emit()

    def reload(self):
        """Called (from the editors manager) to reload the file"""
        # Re-read the file with updating the file timestamp
        self.readFile(self.__fileName)

        # Hide the bars, just in case both of them
        if not self.importsBar.isHidden():
            self.importsBar.hide()
        if not self.__outsideChangesBar.isHidden():
            self.__outsideChangesBar.hide()

        # Set the shown flag
        self.setReloadDialogShown(False)

    def reloadAllNonModified(self):
        """Request to reload all the non-modified files"""
        self.reloadAllNonModifiedRequest.emit()

    @staticmethod
    def onRunScript(action=None):
        """Runs the script"""
        del action  # unused argument
        GlobalData().mainWindow.onRunTab()

    @staticmethod
    def onRunScriptDlg():
        """Shows the run parameters dialogue"""
        GlobalData().mainWindow.onRunTabDlg()

    @staticmethod
    def onProfileScript(action=None):
        """Profiles the script"""
        del action  # unused argument
        GlobalData().mainWindow.onProfileTab()

    @staticmethod
    def onProfileScriptDlg():
        """Shows the profile parameters dialogue"""
        GlobalData().mainWindow.onProfileTabDlg()

    @staticmethod
    def onDebugScript(action=None):
        """Starts debugging"""
        del action  # unused argument
        GlobalData().mainWindow.onDebugTab()

    @staticmethod
    def onDebugScriptDlg():
        """Shows the debug parameters dialogue"""
        GlobalData().mainWindow.onDebugTabDlg()

    def getCFEditor(self):
        """Provides a reference to the control flow widget"""
        return self.__flowUI

    def cflowSyncRequested(self, absPos, line, pos):
        """Highlight the item closest to the absPos"""
        self.__flowUI.highlightAtAbsPos(absPos, line, pos)

    def passFocusToFlow(self):
        """Sets the focus to the graphics part"""
        if isPythonMime(self.__editor.mime):
            self.__flowUI.setFocus()
            return True
        return False

    def getMDView(self):
        """Provides a reference to the MD rendered view"""
        return self.__mdView

    # Mandatory interface part is below

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

    def isModified(self):
        """Tells if the file is modified"""
        return self.__editor.document().isModified()

    def getRWMode(self):
        """Tells if the file is read only"""
        if not os.path.exists(self.__fileName):
            return None
        return 'RW' if QFileInfo(self.__fileName).isWritable() else 'RO'

    def getMime(self):
        """Provides the buffer mime"""
        return self.__editor.mime

    @staticmethod
    def getType():
        """Tells the widget type"""
        return MainWindowTabWidgetBase.PlainTextEditor

    def getLanguage(self):
        """Tells the content language"""
        editorLanguage = self.__editor.language()
        if editorLanguage:
            return editorLanguage
        return self.__editor.mime if self.__editor.mime else 'n/a'

    def getFileName(self):
        """Tells what file name of the widget content"""
        return self.__fileName

    def setFileName(self, name):
        """Sets the file name"""
        self.__fileName = name
        self.__shortName = os.path.basename(name)

    def getEol(self):
        """Tells the EOL style"""
        return self.__editor.getEolIndicator()

    def getLine(self):
        """Tells the cursor line"""
        line, _ = self.__editor.cursorPosition
        return line

    def getPos(self):
        """Tells the cursor column"""
        _, pos = self.__editor.cursorPosition
        return pos

    def getEncoding(self):
        """Tells the content encoding"""
        if self.__editor.explicitUserEncoding:
            return self.__editor.explicitUserEncoding
        return self.__editor.encoding

    def getShortName(self):
        """Tells the display name"""
        return self.__shortName

    def setShortName(self, name):
        """Sets the display name"""
        self.__shortName = name

    def isDiskFileModified(self):
        """Return True if the loaded file is modified"""
        if not os.path.isabs(self.__fileName):
            return False
        if not os.path.exists(self.__fileName):
            return True
        path = os.path.realpath(self.__fileName)
        return self.__diskModTime != os.path.getmtime(path) or \
               self.__diskSize != os.path.getsize(path)

    def doesFileExist(self):
        """Returns True if the loaded file still exists"""
        return os.path.exists(self.__fileName)

    def setReloadDialogShown(self, value=True):
        """Memorizes if the reloading dialogue has already been displayed"""
        self.__reloadDlgShown = value

    def getReloadDialogShown(self):
        """Tells if the reload dialog has already been shown"""
        return self.__reloadDlgShown and \
            not self.__outsideChangesBar.isVisible()

    def updateModificationTime(self, fileName):
        """Updates the modification time"""
        path = os.path.realpath(fileName)
        self.__diskModTime = os.path.getmtime(path)
        self.__diskSize = os.path.getsize(path)

    def setDebugMode(self, debugOn, disableEditing):
        """Called to switch debug/development"""
        self.__debugMode = debugOn
        self.__editor.setDebugMode(debugOn, disableEditing)

        if debugOn:
            if disableEditing:
                # Undo/redo
                self.__undoButton.setEnabled(False)
                self.__redoButton.setEnabled(False)

                # Spaces/tabs/line
                self.removeTrailingSpacesButton.setEnabled(False)
                self.expandTabsButton.setEnabled(False)
        else:
            # Undo/redo
            self.__undoButton.setEnabled(
                self.__editor.document().isUndoAvailable())
            self.__redoButton.setEnabled(
                self.__editor.document().isRedoAvailable())

            # Spaces/tabs
            self.removeTrailingSpacesButton.setEnabled(True)
            self.expandTabsButton.setEnabled(True)

        # Run/debug buttons
        self.__updateRunDebugButtons()

    def isLineBreakable(self,
                        line=None,
                        enforceRecalc=False,
                        enforceSure=False):
        """True if a breakpoint could be placed on the current line"""
        return self.__editor.isLineBreakable()

    def getVCSStatus(self):
        """Provides the VCS status"""
        return self.__vcsStatus

    def setVCSStatus(self, newStatus):
        """Sets the new VCS status"""
        self.__vcsStatus = newStatus

    # Floating renderer support
    def popRenderingWidgets(self):
        """Pops the rendering widgets"""
        self.__renderLayout.removeWidget(self.__flowUI)
        self.__renderLayout.removeWidget(self.__mdView)
        self.__renderWidget.setVisible(False)
        return [self.__flowUI, self.__mdView]

    def pushRenderingWidgets(self, widgets):
        """Returns back the rendering widgets"""
        for widget in widgets:
            self.__renderLayout.addWidget(widget)
        self.__languageChanged()  # Sets the widget visibility
Example #4
0
class SVNPluginPropsDialog(QDialog):
    """SVN plugin properties dialog"""
    def __init__(self, plugin, client, path, parent=None):
        QDialog.__init__(self, parent)

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

        self.__createLayout()
        self.setWindowTitle("SVN Properties of " + path)
        self.__populate()
        self.__propsView.setFocus()

    def __populate(self):
        """Populate the properties list"""
        # Get the currently selected name
        selectedName = None
        selected = list(self.__propsView.selectedItems())
        if selected:
            selectedName = str(selected[0].text(0))

        self.__propsView.clear()
        properties = readProperties(self.__client, self.__path)
        if properties:
            for itemPath, itemProps in properties:
                if self.__path == itemPath or \
                   self.__path == itemPath + os.path.sep:
                    for name, value in itemProps.iteritems():
                        name = str(name).strip()
                        value = str(value).strip()
                        newItem = QTreeWidgetItem([name, value])
                        self.__propsView.addTopLevelItem(newItem)

        self.__resizePropsView()
        self.__sortPropsView()

        if selectedName:
            index = 0
            for index in range(0, self.__propsView.topLevelItemCount()):
                item = self.__propsView.topLevelItem(index)
                if selectedName == item.text(0):
                    item.setSelected(True)

    def __resizePropsView(self):
        """Resizes the properties table"""
        self.__propsView.header().setStretchLastSection(True)
        self.__propsView.header().resizeSections(QHeaderView.ResizeToContents)

    def __sortPropsView(self):
        """Sorts the properties table"""
        self.__propsView.sortItems(
            self.__propsView.sortColumn(),
            self.__propsView.header().sortIndicatorOrder())

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

        vboxLayout = QVBoxLayout(self)

        hLayout = QHBoxLayout()
        self.__propsView = QTreeWidget()
        self.__propsView.setAlternatingRowColors(True)
        self.__propsView.setRootIsDecorated(False)
        self.__propsView.setItemsExpandable(False)
        self.__propsView.setSortingEnabled(True)
        self.__propsView.setItemDelegate(NoOutlineHeightDelegate(4))
        self.__propsView.itemSelectionChanged.connect(
            self.__propsSelectionChanged)

        propsViewHeader = QTreeWidgetItem(["Property Name", "Property Value"])
        self.__propsView.setHeaderItem(propsViewHeader)
        self.__propsView.header().setSortIndicator(0, Qt.DescendingOrder)
        hLayout.addWidget(self.__propsView)

        self.__delButton = QToolButton()
        self.__delButton.setText("Delete")
        self.__delButton.setFocusPolicy(Qt.NoFocus)
        self.__delButton.setEnabled(False)
        self.__delButton.clicked.connect(self.__onDel)
        hLayout.addWidget(self.__delButton, 0, Qt.AlignBottom)
        vboxLayout.addLayout(hLayout)

        # Set property part
        setGroupbox = QGroupBox(self)
        setGroupbox.setTitle("Set Property")

        setLayout = QGridLayout(setGroupbox)
        setLayout.addWidget(QLabel("Name"), 0, 0, Qt.AlignTop | Qt.AlignRight)
        setLayout.addWidget(QLabel("Value"), 1, 0, Qt.AlignTop | Qt.AlignRight)

        self.__nameEdit = QLineEdit()
        self.__nameEdit.textChanged.connect(self.__nameChanged)
        setLayout.addWidget(self.__nameEdit, 0, 1)

        self.__valueEdit = QTextEdit()
        self.__valueEdit.setAcceptRichText(False)
        self.__valueEdit.textChanged.connect(self.__valueChanged)
        metrics = QFontMetrics(self.__valueEdit.font())
        rect = metrics.boundingRect("X")
        self.__valueEdit.setFixedHeight(rect.height() * 4 + 5)
        setLayout.addWidget(self.__valueEdit, 1, 1)

        self.__setButton = QToolButton()
        self.__setButton.setText("Set")
        self.__setButton.setFocusPolicy(Qt.NoFocus)
        self.__setButton.setEnabled(False)
        self.__setButton.clicked.connect(self.__onSet)
        setLayout.addWidget(self.__setButton, 1, 2,
                            Qt.AlignBottom | Qt.AlignHCenter)

        sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(
            setGroupbox.sizePolicy().hasHeightForWidth())
        setGroupbox.setSizePolicy(sizePolicy)
        vboxLayout.addWidget(setGroupbox)

        # 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)

    def __onSet(self):
        """Triggered when propery set is clicked"""
        name = self.__nameEdit.text().strip()
        value = self.__valueEdit.toPlainText().strip()
        try:
            commitInfo = self.__client.propset(name, value, self.__path)
            if commitInfo:
                logging.info(str(commitInfo))
            self.__populate()
            self.__plugin.notifyPathChanged(self.__path)
            self.__nameEdit.clear()
            self.__valueEdit.clear()
            self.__propsView.setFocus()
        except pysvn.ClientError as exc:
            message = exc.args[0]
            logging.error(message)
        except Exception as exc:
            logging.error(str(exc))
        except:
            logging.error("Unknown property setting error")

    def __propsSelectionChanged(self):
        """Selection of a property has changed"""
        selected = list(self.__propsView.selectedItems())
        self.__delButton.setEnabled(len(selected) > 0)

    def __onDel(self):
        """Triggered when a property del is clicked"""
        selected = list(self.__propsView.selectedItems())
        if len(selected) == 0:
            self.__delButton.setEnabled(False)
            return

        name = str(selected[0].text(0))
        res = QMessageBox.warning(
            self, "Deleting Property", "You are about to delete <b>" + name +
            "</b> SVN property from " + self.__path + ".\nAre you sure?",
            QMessageBox.StandardButtons(QMessageBox.Cancel | QMessageBox.Yes),
            QMessageBox.Cancel)
        if res != QMessageBox.Yes:
            return

        try:
            self.__client.propdel(name, self.__path)
            self.__populate()
            self.__plugin.notifyPathChanged(self.__path)
            self.__propsView.setFocus()
        except pysvn.ClientError as exc:
            message = exc.args[0]
            logging.error(message)
        except Exception as exc:
            logging.error(str(exc))
        except:
            logging.error("Unknown property deleting error")

    def __nameChanged(self, text):
        """Triggered when a property name to set is changed"""
        self.__updateSetButton()

    def __valueChanged(self):
        """Triggered when a property value to set is changed"""
        self.__updateSetButton()

    def __updateSetButton(self):
        """Updates the 'Set' button state"""
        name = self.__nameEdit.text().strip()
        value = self.__valueEdit.toPlainText().strip()
        self.__setButton.setEnabled(name != "" and value != "")
Example #5
0
class SVNPluginCommitDialog(QDialog):
    """SVN Plugin commit dialog"""

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

    def __init__(self, plugin, pathsToCommit, pathsToIgnore, parent=None):
        QDialog.__init__(self, parent)

        self.__plugin = plugin

        self.__createLayout(pathsToCommit, pathsToIgnore)
        self.setWindowTitle("SVN commit")

        # Fill the lists
        for item in pathsToCommit:
            newItem = QTreeWidgetItem(["", item[0], STATUS[item[1]]])
            newItem.setCheckState(CHECK_COL, Qt.Checked)
            newItem.setToolTip(PATH_COL, item[0])
            newItem.setToolTip(STATUS_COL, STATUS[item[1]])
            self.__pathToCommitView.addTopLevelItem(newItem)

            diffButton = self.__createDiffButton()
            diffButton.path = item[0]
            diffButton.status = item[1]

            if os.path.isdir(item[0]) or item[1] in [IND_REPLACED] \
                or not isFileSearchable(item[0]):
                diffButton.setEnabled(False)
                diffButton.setToolTip("Diff is not available")
            else:
                diffButton.setEnabled(True)
                diffButton.setToolTip("Click to see diff")
            self.__pathToCommitView.setItemWidget(newItem, DIFF_COL,
                                                  diffButton)

        self.__resizeCommitPaths()
        self.__sortCommitPaths()

        for item in pathsToIgnore:
            newItem = QTreeWidgetItem([item[0], STATUS[item[1]]])
            newItem.setToolTip(0, item[0])
            newItem.setToolTip(1, STATUS[item[1]])
            self.__pathToIgnoreView.addTopLevelItem(newItem)
        self.__pathToIgnoreView.header().resizeSections(
            QHeaderView.ResizeToContents)

        self.__updateSelectAllStatus()
        self.__updateOKStatus()
        self.__message.setFocus()

    def __resizeCommitPaths(self):
        """Resizes the plugins table"""
        self.__pathToCommitView.header().setStretchLastSection(False)
        self.__pathToCommitView.header().resizeSections(
            QHeaderView.ResizeToContents)
        self.__pathToCommitView.header().resizeSection(CHECK_COL, 28)
        self.__pathToCommitView.header().setResizeMode(CHECK_COL,
                                                       QHeaderView.Fixed)

        # By some reasons, to have PATH_COL visually adjustable the only STATUS_COL
        # must be set to be stretchable, so there is a comment below.
        # self.__pathToCommitView.header().setResizeMode( PATH_COL, QHeaderView.Stretch )
        self.__pathToCommitView.header().setResizeMode(STATUS_COL,
                                                       QHeaderView.Stretch)
        self.__pathToCommitView.header().resizeSection(DIFF_COL, 24)
        self.__pathToCommitView.header().setResizeMode(DIFF_COL,
                                                       QHeaderView.Fixed)

    def __sortCommitPaths(self):
        """Sorts the commit paths table"""
        self.__pathToCommitView.sortItems(
            self.__pathToCommitView.sortColumn(),
            self.__pathToCommitView.header().sortIndicatorOrder())

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

    @staticmethod
    def __setLightPalette(frame):
        """Creates a lighter paletter 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)

    @staticmethod
    def __configTable(table):
        """Sets common properties for a table"""
        table.setAlternatingRowColors(True)
        table.setRootIsDecorated(False)
        table.setItemsExpandable(False)
        table.setSortingEnabled(True)
        table.setItemDelegate(NoOutlineHeightDelegate(4))
        table.setUniformRowHeights(True)

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

        vboxLayout = QVBoxLayout(self)

        # Paths to commit part
        commitHeaderFrame = QFrame()
        commitHeaderFrame.setFrameStyle(QFrame.StyledPanel)
        commitHeaderFrame.setAutoFillBackground(True)
        self.__setLightPalette(commitHeaderFrame)
        commitHeaderFrame.setFixedHeight(24)

        expandingCommitSpacer = QSpacerItem(10, 10, QSizePolicy.Expanding)

        self.__selectAllButton = QToolButton()
        self.__selectAllButton.setAutoRaise(True)
        self.__selectAllButton.setIcon(
            getIcon(pluginHomeDir + 'svnselectall.png'))
        self.__selectAllButton.setFixedSize(20, 20)
        self.__selectAllButton.setToolTip("Select all")
        self.__selectAllButton.setFocusPolicy(Qt.NoFocus)
        self.__selectAllButton.clicked.connect(self.__onSelectAll)

        commitHeaderLayout = QHBoxLayout()
        commitHeaderLayout.setContentsMargins(3, 0, 0, 0)
        commitHeaderLayout.addWidget(
            QLabel("Paths to commit (total: " + str(len(pathsToCommit)) + ")"))
        commitHeaderLayout.addSpacerItem(expandingCommitSpacer)
        commitHeaderLayout.addWidget(self.__selectAllButton)
        commitHeaderFrame.setLayout(commitHeaderLayout)

        vboxLayout.addWidget(commitHeaderFrame)

        self.__pathToCommitView = QTreeWidget()
        self.__configTable(self.__pathToCommitView)

        self.__pathToCommitHeader = QTreeWidgetItem(["", "Path", "Status", ""])
        self.__pathToCommitView.setHeaderItem(self.__pathToCommitHeader)
        self.__pathToCommitView.header().setSortIndicator(
            PATH_COL, Qt.AscendingOrder)
        self.__pathToCommitView.itemChanged.connect(self.__onCommitPathChanged)
        vboxLayout.addWidget(self.__pathToCommitView)

        # Paths to ignore part
        headerFrame = QFrame()
        headerFrame.setFrameStyle(QFrame.StyledPanel)
        headerFrame.setAutoFillBackground(True)
        self.__setLightPalette(headerFrame)
        headerFrame.setFixedHeight(24)

        ignoreLabel = QLabel("Ignored paths (total: " +
                             str(len(pathsToIgnore)) + ")")
        expandingSpacer = QSpacerItem(10, 10, QSizePolicy.Expanding)

        self.__showHideIgnoredButton = QToolButton()
        self.__showHideIgnoredButton.setAutoRaise(True)
        self.__showHideIgnoredButton.setIcon(getIcon('less.png'))
        self.__showHideIgnoredButton.setFixedSize(20, 20)
        self.__showHideIgnoredButton.setToolTip("Show ignored path list")
        self.__showHideIgnoredButton.setFocusPolicy(Qt.NoFocus)
        self.__showHideIgnoredButton.clicked.connect(self.__onShowHideIgnored)

        ignoredHeaderLayout = QHBoxLayout()
        ignoredHeaderLayout.setContentsMargins(3, 0, 0, 0)
        ignoredHeaderLayout.addWidget(ignoreLabel)
        ignoredHeaderLayout.addSpacerItem(expandingSpacer)
        ignoredHeaderLayout.addWidget(self.__showHideIgnoredButton)
        headerFrame.setLayout(ignoredHeaderLayout)

        vboxLayout.addWidget(headerFrame)

        self.__pathToIgnoreView = QTreeWidget()
        self.__configTable(self.__pathToIgnoreView)
        self.__pathToIgnoreView.setVisible(False)

        pathToIgnoreHeader = QTreeWidgetItem(["Path", "Status"])
        self.__pathToIgnoreView.setHeaderItem(pathToIgnoreHeader)
        self.__pathToIgnoreView.header().setSortIndicator(0, Qt.AscendingOrder)
        vboxLayout.addWidget(self.__pathToIgnoreView)

        # Message part
        vboxLayout.addWidget(QLabel("Message"))
        self.__message = QTextEdit()
        self.__message.setAcceptRichText(False)
        metrics = QFontMetrics(self.__message.font())
        rect = metrics.boundingRect("X")
        self.__message.setFixedHeight(rect.height() * 4 + 5)
        vboxLayout.addWidget(self.__message)

        # 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
                                     | QDialogButtonBox.Cancel)
        self.__OKButton = buttonBox.button(QDialogButtonBox.Ok)
        self.__OKButton.setText("Commit")
        buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
        buttonBox.accepted.connect(self.userAccept)
        buttonBox.rejected.connect(self.close)
        vboxLayout.addWidget(buttonBox)

    def __onShowHideDiff(self):
        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 __onShowHideIgnored(self):
        if self.__pathToIgnoreView.isVisible():
            self.__pathToIgnoreView.setVisible(False)
            self.__showHideIgnoredButton.setIcon(getIcon('less.png'))
            self.__showHideIgnoredButton.setToolTip("Show ignored path list")
        else:
            self.__pathToIgnoreView.setVisible(True)
            self.__showHideIgnoredButton.setIcon(getIcon('more.png'))
            self.__showHideIgnoredButton.setToolTip("Hide ignored path list")

    def userAccept(self):
        """ Triggered when the user clicks OK"""
        # Collect the list of checked paths
        self.commitMessage = self.__message.toPlainText().strip()

        self.commitPaths = []
        index = 0
        while index < self.__pathToCommitView.topLevelItemCount():
            item = self.__pathToCommitView.topLevelItem(index)
            if item.checkState(0) == Qt.Checked:
                path = str(item.text(1))
                if os.path.isdir(path) and not os.path.islink(path) and \
                    not path.endswith(os.path.sep):
                    path += os.path.sep
                self.commitPaths.append(path)
            index += 1
        self.accept()

    def __getCheckedCount(self):
        """Provides the number of selected items in the commit paths section"""
        index = 0
        checkedCount = 0
        while index < self.__pathToCommitView.topLevelItemCount():
            item = self.__pathToCommitView.topLevelItem(index)
            if item.checkState(0) == Qt.Checked:
                checkedCount += 1
            index += 1
        return checkedCount

    def __updateSelectAllStatus(self):
        """Updates the select all status button"""
        total = self.__pathToCommitView.topLevelItemCount()
        if total == 0 or total == self.__getCheckedCount():
            self.__selectAllButton.setEnabled(False)
        else:
            self.__selectAllButton.setEnabled(True)

    def __updateOKStatus(self):
        """Updates the OK button status"""
        self.__OKButton.setEnabled(self.__getCheckedCount() > 0)

    def __onCommitPathChanged(self, item, column):
        """Triggered when an item is changed"""
        self.__updateSelectAllStatus()
        self.__updateOKStatus()

    def __onSelectAll(self):
        """Triggered when select all button is clicked"""
        index = 0
        while index < self.__pathToCommitView.topLevelItemCount():
            item = self.__pathToCommitView.topLevelItem(index)
            item.setCheckState(0, Qt.Checked)
            index += 1
        self.__updateSelectAllStatus()
        self.__updateOKStatus()

    def onDiff(self, path, status):
        """Triggered when diff for the path is called"""
        if not path:
            return

        QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))

        try:
            # Status is one of the following:
            # IND_ADDED, IND_DELETED, IND_MERGED, IND_MODIFIED_LR, IND_MODIFIED_L, IND_CONFLICTED
            repositoryContent = ""
            localContent = ""
            if status != IND_ADDED:
                client = self.__plugin.getSVNClient(
                    self.__plugin.getSettings())
                repositoryContent = client.cat(path)
            if status != IND_DELETED:
                with open(path) as f:
                    localContent = f.read()

            diff = difflib.unified_diff(repositoryContent.splitlines(),
                                        localContent.splitlines())
            nodiffMessage = path + " has no difference to the " \
                                   "repository at revision HEAD"
            if diff is None:
                QApplication.restoreOverrideCursor()
                logging.info(nodiffMessage)
                return

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

            source = "+++ local " + os.path.basename(path)
            diffAsText = diffAsText.replace("+++ ", source, 1)
            diffAsText = diffAsText.replace("--- ",
                                            "--- repository at revision HEAD",
                                            1)

            self.__diffViewer.setHTML(
                parse_from_memory(diffAsText, False, True))
            if not self.__diffViewer.isVisible():
                self.__onShowHideDiff()
        except Exception as exc:
            logging.error(str(exc))
        except:
            logging.error("Unknown error while calculating difference for " +
                          path)

        QApplication.restoreOverrideCursor()
Example #6
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)
class WatchPointViewer(QWidget):
    """Implements the watch point viewer for a debugger"""
    def __init__(self, parent, wpointModel):
        QWidget.__init__(self, parent)

        self.__currentItem = None

        self.__createPopupMenu()
        self.__createLayout(wpointModel)

        GlobalData().project.sigProjectChanged.connect(self.__onProjectChanged)

        if Settings()['showWatchPointViewer'] == False:
            self.__onShowHide(True)

    def __createPopupMenu(self):
        """Creates the popup menu"""
        #        self.__excptMenu = QMenu()
        #        self.__removeMenuItem = self.__excptMenu.addAction(
        #                    "Remove from ignore list", self.__onRemoveFromIgnore )
        return

    def __createLayout(self, wpointModel):
        """Creates the widget layout"""
        verticalLayout = QVBoxLayout(self)
        verticalLayout.setContentsMargins(0, 0, 0, 0)
        verticalLayout.setSpacing(0)

        self.headerFrame = QFrame()
        self.headerFrame.setFrameStyle(QFrame.StyledPanel)
        self.headerFrame.setAutoFillBackground(True)
        headerPalette = self.headerFrame.palette()
        headerBackground = headerPalette.color(QPalette.Background)
        headerBackground.setRgb(min(headerBackground.red() + 30, 255),
                                min(headerBackground.green() + 30, 255),
                                min(headerBackground.blue() + 30, 255))
        headerPalette.setColor(QPalette.Background, headerBackground)
        self.headerFrame.setPalette(headerPalette)
        self.headerFrame.setFixedHeight(24)

        self.__watchpointLabel = QLabel("Watchpoints")

        expandingSpacer = QSpacerItem(10, 10, QSizePolicy.Expanding)
        fixedSpacer = QSpacerItem(3, 3)

        self.__showHideButton = QToolButton()
        self.__showHideButton.setAutoRaise(True)
        self.__showHideButton.setIcon(getIcon('less.png'))
        self.__showHideButton.setFixedSize(20, 20)
        self.__showHideButton.setToolTip("Hide ignored exceptions list")
        self.__showHideButton.setFocusPolicy(Qt.NoFocus)
        self.__showHideButton.clicked.connect(self.__onShowHide)

        headerLayout = QHBoxLayout()
        headerLayout.setContentsMargins(1, 1, 1, 1)
        headerLayout.addSpacerItem(fixedSpacer)
        headerLayout.addWidget(self.__watchpointLabel)
        headerLayout.addSpacerItem(expandingSpacer)
        headerLayout.addWidget(self.__showHideButton)
        self.headerFrame.setLayout(headerLayout)

        self.__wpointsList = WatchPointView(self, wpointModel)

        self.__enableButton = QToolButton()
        self.__enableButton.setIcon(getIcon('add.png'))
        self.__enableButton.setFixedSize(24, 24)
        self.__enableButton.setToolTip("Enable/disable the watchpoint")
        self.__enableButton.setFocusPolicy(Qt.NoFocus)
        self.__enableButton.setEnabled(False)
        self.__enableButton.clicked.connect(self.__onEnableDisable)

        expandingSpacer = QSpacerItem(10, 10, QSizePolicy.Expanding)

        self.__jumpToCodeButton = QToolButton()
        self.__jumpToCodeButton.setIcon(getIcon('gotoline.png'))
        self.__jumpToCodeButton.setFixedSize(24, 24)
        self.__jumpToCodeButton.setToolTip("Jump to the code")
        self.__jumpToCodeButton.setFocusPolicy(Qt.NoFocus)
        self.__jumpToCodeButton.setEnabled(False)
        self.__jumpToCodeButton.clicked.connect(self.__onJumpToCode)

        toolbarLayout = QHBoxLayout()
        toolbarLayout.addWidget(self.__enableButton)
        toolbarLayout.addSpacerItem(expandingSpacer)
        toolbarLayout.addWidget(self.__jumpToCodeButton)

        self.__wpointsList.sigSelectionChanged.connect(
            self.__onSelectionChanged)

        verticalLayout.addWidget(self.headerFrame)
        verticalLayout.addLayout(toolbarLayout)
        verticalLayout.addWidget(self.__wpointsList)

    def clear(self):
        """Clears the content"""
        #        self.__wpointsList.clear()
        self.__updateTitle()
        self.__jumpToCodeButton.setEnabled(False)
        self.__currentItem = None

    def __onJumpToCode(self):
        """Jumps to the corresponding source code line"""
        return

    def __onShowHide(self, startup=False):
        """Triggered when show/hide button is clicked"""
        if startup or self.__wpointsList.isVisible():
            self.__wpointsList.setVisible(False)
            self.__enableButton.setVisible(False)
            self.__jumpToCodeButton.setVisible(False)
            self.__showHideButton.setIcon(getIcon('more.png'))
            self.__showHideButton.setToolTip("Show watchpoints list")

            self.__minH = self.minimumHeight()
            self.__maxH = self.maximumHeight()

            self.setMinimumHeight(self.headerFrame.height())
            self.setMaximumHeight(self.headerFrame.height())

            Settings()['showWatchPointViewer'] = False
        else:
            self.__wpointsList.setVisible(True)
            self.__enableButton.setVisible(True)
            self.__jumpToCodeButton.setVisible(True)
            self.__showHideButton.setIcon(getIcon('less.png'))
            self.__showHideButton.setToolTip("Hide watchpoints list")

            self.setMinimumHeight(self.__minH)
            self.setMaximumHeight(self.__maxH)

            Settings()['showWatchPointViewer'] = True

    def __onSelectionChanged(self, index):
        """Triggered when the current item is changed"""
        if index.isValid():
            pass
        else:
            pass
        return
        selected = list(self.__exceptionsList.selectedItems())
        if selected:
            self.__currentItem = selected[0]
            self.__removeButton.setEnabled(True)
        else:
            self.__currentItem = None
            self.__removeButton.setEnabled(False)

    def __updateTitle(self):
        """Updates the section title"""
        count = self.getTotalCount()
        if count == 0:
            self.__watchpointLabel.setText("Watchpoints")
        else:
            self.__watchpointLabel.setText("Watchpoints (total: " +
                                           str(count) + ")")

    def getTotalCount(self):
        """Provides the total number of watch points"""
        count = 0
        return count

    def __onProjectChanged(self, what):
        """Triggered when a project is changed"""
        if what == CodimensionProject.CompleteProject:
            self.clear()

    def __onEnableDisable(self):
        """Triggered when a breakpoint should be enabled/disabled"""
        pass