Beispiel #1
0
class IgnoredExceptionsViewer(QWidget):
    " Implements the client exceptions viewer for a debugger "

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

        self.__createPopupMenu()
        self.__createLayout()
        self.__ignored = []
        self.__currentItem = None

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

        if Settings().showIgnoredExcViewer == False:
            self.__onShowHide(True)
        return

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

    def __createLayout(self):
        " 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.__excptLabel = QLabel("Ignored exception types")

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

        self.__showHideButton = QToolButton()
        self.__showHideButton.setAutoRaise(True)
        self.__showHideButton.setIcon(PixmapCache().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.__excptLabel)
        headerLayout.addSpacerItem(expandingSpacer)
        headerLayout.addWidget(self.__showHideButton)
        self.headerFrame.setLayout(headerLayout)

        self.exceptionsList = QTreeWidget(self)
        self.exceptionsList.setSortingEnabled(False)
        self.exceptionsList.setAlternatingRowColors(True)
        self.exceptionsList.setRootIsDecorated(False)
        self.exceptionsList.setItemsExpandable(True)
        self.exceptionsList.setUniformRowHeights(True)
        self.exceptionsList.setSelectionMode(QAbstractItemView.SingleSelection)
        self.exceptionsList.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.exceptionsList.setItemDelegate(NoOutlineHeightDelegate(4))
        self.exceptionsList.setContextMenuPolicy(Qt.CustomContextMenu)

        self.exceptionsList.customContextMenuRequested.connect(
            self.__showContextMenu)
        self.exceptionsList.itemSelectionChanged.connect(
            self.__onSelectionChanged)
        self.exceptionsList.setHeaderLabels(["Exception type"])

        self.__excTypeEdit = QLineEdit()
        self.__excTypeEdit.setFixedHeight(26)
        self.__excTypeEdit.textChanged.connect(self.__onNewFilterChanged)
        self.__excTypeEdit.returnPressed.connect(self.__onAddExceptionFilter)
        self.__addButton = QPushButton("Add")
        # self.__addButton.setFocusPolicy( Qt.NoFocus )
        self.__addButton.setEnabled(False)
        self.__addButton.clicked.connect(self.__onAddExceptionFilter)

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

        self.__removeButton = QAction(PixmapCache().getIcon('delitem.png'),
                                      "Remove selected exception type", self)
        self.__removeButton.triggered.connect(self.__onRemoveFromIgnore)
        self.__removeButton.setEnabled(False)

        fixedSpacer1 = QWidget()
        fixedSpacer1.setFixedWidth(5)

        self.__removeAllButton = QAction(
            PixmapCache().getIcon('ignexcptdelall.png'),
            "Remove all the exception types", self)
        self.__removeAllButton.triggered.connect(self.__onRemoveAllFromIgnore)
        self.__removeAllButton.setEnabled(False)

        self.toolbar = QToolBar()
        self.toolbar.setOrientation(Qt.Horizontal)
        self.toolbar.setMovable(False)
        self.toolbar.setAllowedAreas(Qt.TopToolBarArea)
        self.toolbar.setIconSize(QSize(16, 16))
        self.toolbar.setFixedHeight(28)
        self.toolbar.setContentsMargins(0, 0, 0, 0)
        self.toolbar.addWidget(expandingSpacer2)
        self.toolbar.addAction(self.__removeButton)
        self.toolbar.addWidget(fixedSpacer1)
        self.toolbar.addAction(self.__removeAllButton)

        addLayout = QHBoxLayout()
        addLayout.setContentsMargins(1, 1, 1, 1)
        addLayout.setSpacing(1)
        addLayout.addWidget(self.__excTypeEdit)
        addLayout.addWidget(self.__addButton)

        verticalLayout.addWidget(self.headerFrame)
        verticalLayout.addWidget(self.toolbar)
        verticalLayout.addWidget(self.exceptionsList)
        verticalLayout.addLayout(addLayout)
        return

    def clear(self):
        " Clears the content "
        self.exceptionsList.clear()
        self.__excTypeEdit.clear()
        self.__addButton.setEnabled(False)
        self.__ignored = []
        self.__currentItem = None
        self.__updateTitle()
        return

    def __onShowHide(self, startup=False):
        " Triggered when show/hide button is clicked "
        if startup or self.exceptionsList.isVisible():
            self.exceptionsList.setVisible(False)
            self.__excTypeEdit.setVisible(False)
            self.__addButton.setVisible(False)
            self.__removeButton.setVisible(False)
            self.__removeAllButton.setVisible(False)
            self.__showHideButton.setIcon(PixmapCache().getIcon('more.png'))
            self.__showHideButton.setToolTip("Show ignored exceptions list")

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

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

            Settings().showIgnoredExcViewer = False
        else:
            self.exceptionsList.setVisible(True)
            self.__excTypeEdit.setVisible(True)
            self.__addButton.setVisible(True)
            self.__removeButton.setVisible(True)
            self.__removeAllButton.setVisible(True)
            self.__showHideButton.setIcon(PixmapCache().getIcon('less.png'))
            self.__showHideButton.setToolTip("Hide ignored exceptions list")

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

            Settings().showIgnoredExcViewer = True
        return

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

    def __showContextMenu(self, coord):
        " Shows the frames list context menu "
        contextItem = self.exceptionsList.itemAt(coord)
        if contextItem is not None:
            self.__currentItem = contextItem
            self.__excptMenu.popup(QCursor.pos())
        return

    def __updateTitle(self):
        " Updates the section title "
        count = self.exceptionsList.topLevelItemCount()
        if count == 0:
            self.__excptLabel.setText("Ignored exception types")
        else:
            self.__excptLabel.setText("Ignored exception types (total: " +
                                      str(count) + ")")
        self.__removeAllButton.setEnabled(count != 0)
        return

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

        self.clear()
        project = GlobalData().project
        if project.isLoaded():
            self.__ignored = list(project.ignoredExcpt)
        else:
            self.__ignored = list(Settings().ignoredExceptions)

        for exceptionType in self.__ignored:
            item = QTreeWidgetItem(self.exceptionsList)
            item.setText(0, exceptionType)
        self.__updateTitle()
        return

    def __onNewFilterChanged(self, text):
        " Triggered when the text is changed "
        text = str(text).strip()
        if text == "":
            self.__addButton.setEnabled(False)
            return
        if " " in text:
            self.__addButton.setEnabled(False)
            return

        if text in self.__ignored:
            self.__addButton.setEnabled(False)
            return

        self.__addButton.setEnabled(True)
        return

    def __onAddExceptionFilter(self):
        " Adds an item into the ignored exceptions list "
        text = self.__excTypeEdit.text().strip()
        self.addExceptionFilter(text)

    def addExceptionFilter(self, excType):
        " Adds a new item into the ignored exceptions list "
        if excType == "":
            return
        if " " in excType:
            return
        if excType in self.__ignored:
            return

        item = QTreeWidgetItem(self.exceptionsList)
        item.setText(0, excType)

        project = GlobalData().project
        if project.isLoaded():
            project.addExceptionFilter(excType)
        else:
            Settings().addExceptionFilter(excType)
        self.__ignored.append(excType)
        self.__updateTitle()
        return

    def __onRemoveFromIgnore(self):
        " Removes an item from the ignored exception types list "
        if self.__currentItem is None:
            return

        text = self.__currentItem.text(0)

        # Find the item index and remove it
        index = 0
        while True:
            if self.exceptionsList.topLevelItem(index).text(0) == text:
                self.exceptionsList.takeTopLevelItem(index)
                break
            index += 1

        project = GlobalData().project
        if project.isLoaded():
            project.deleteExceptionFilter(text)
        else:
            Settings().deleteExceptionFilter(text)
        self.__ignored.remove(text)
        self.__updateTitle()
        return

    def __onRemoveAllFromIgnore(self):
        " Triggered when all the ignored exceptions should be deleted "
        self.clear()

        project = GlobalData().project
        if project.isLoaded():
            project.setExceptionFilters([])
        else:
            Settings().setExceptionFilters([])
        return

    def isIgnored(self, exceptionType):
        " Returns True if this exception type should be ignored "
        return exceptionType in self.__ignored
Beispiel #2
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]

            fileType = detectFileType(item[0])

            if os.path.isdir( item[ 0 ] ) or item[ 1 ] in [ IND_REPLACED ] \
                or not isFileTypeSearchable( fileType ):
                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()
        return

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

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

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

    @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)
        return

    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(
            PixmapCache().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(PixmapCache().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(PixmapCache().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)
        return

    def __onShowHideDiff(self):
        if self.__diffViewer.isVisible():
            self.__diffViewer.setVisible(False)
            self.__showHideDiffButton.setIcon(
                PixmapCache().getIcon('less.png'))
            self.__showHideDiffButton.setToolTip("Show diff")
        else:
            self.__diffViewer.setVisible(True)
            self.__showHideDiffButton.setIcon(
                PixmapCache().getIcon('more.png'))
            self.__showHideDiffButton.setToolTip("Hide diff")
        return

    def __onShowHideIgnored(self):
        if self.__pathToIgnoreView.isVisible():
            self.__pathToIgnoreView.setVisible(False)
            self.__showHideIgnoredButton.setIcon(
                PixmapCache().getIcon('less.png'))
            self.__showHideIgnoredButton.setToolTip("Show ignored path list")
        else:
            self.__pathToIgnoreView.setVisible(True)
            self.__showHideIgnoredButton.setIcon(
                PixmapCache().getIcon('more.png'))
            self.__showHideIgnoredButton.setToolTip("Hide ignored path list")
        return

    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()
        return

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

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

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

    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()
        return

    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, exc:
            logging.error(str(exc))
        except:
class RecentProjectsViewer( QWidget ):
    " Recent projects viewer implementation "

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

        self.__projectContextItem = None
        self.__fileContextItem = None

        self.upper = self.__createRecentFilesLayout()
        self.lower = self.__createRecentProjectsLayout()
        self.__createProjectPopupMenu()
        self.__createFilePopupMenu()

        layout = QVBoxLayout()
        layout.setContentsMargins( 1, 1, 1, 1 )
        splitter = QSplitter( Qt.Vertical )
        splitter.addWidget( self.upper )
        splitter.addWidget( self.lower )
        splitter.setCollapsible( 0, False )
        splitter.setCollapsible( 1, False )

        layout.addWidget( splitter )
        self.setLayout( layout )

        self.__populateProjects()
        self.__populateFiles()
        self.__updateProjectToolbarButtons()
        self.__updateFileToolbarButtons()

        # Debugging mode support
        self.__debugMode = False
        parent.debugModeChanged.connect( self.__onDebugMode )
        return

    def setTooltips( self, switchOn ):
        " Switches the tooltips mode "
        for index in xrange( 0, self.recentFilesView.topLevelItemCount() ):
            self.recentFilesView.topLevelItem( index ).updateIconAndTooltip()
        for index in xrange( 0, self.projectsView.topLevelItemCount() ):
            self.projectsView.topLevelItem( index ).updateTooltip()
        return

    def __createFilePopupMenu( self ):
        " create the recent files popup menu "
        self.__fileMenu = QMenu( self.recentFilesView )
        self.__openMenuItem = self.__fileMenu.addAction( \
                                PixmapCache().getIcon( 'openitem.png' ),
                                'Open', self.__openFile )
        self.__copyPathFileMenuItem = self.__fileMenu.addAction( \
                        PixmapCache().getIcon( 'copytoclipboard.png' ),
                        'Copy path to clipboard', self.__filePathToClipboard )
        self.__fileMenu.addSeparator()
        self.__delFileMenuItem = self.__fileMenu.addAction( \
                                PixmapCache().getIcon( 'trash.png' ),
                                'Delete from recent',
                                self.__deleteFile )
        self.recentFilesView.setContextMenuPolicy( Qt.CustomContextMenu )
        self.connect( self.recentFilesView,
                      SIGNAL( "customContextMenuRequested(const QPoint &)" ),
                      self.__handleShowFileContextMenu )

        self.connect( GlobalData().project, SIGNAL( 'recentFilesChanged' ),
                      self.__populateFiles )
        return


    def __createProjectPopupMenu( self ):
        " Creates the recent project popup menu "
        self.__projectMenu = QMenu( self.projectsView )
        self.__prjLoadMenuItem = self.__projectMenu.addAction( \
                                PixmapCache().getIcon( 'load.png' ),
                                'Load',
                                self.__loadProject )
        self.__projectMenu.addSeparator()
        self.__propsMenuItem = self.__projectMenu.addAction( \
                                PixmapCache().getIcon( 'smalli.png' ),
                                'Properties',
                                self.__viewProperties )
        self.__prjCopyPathMenuItem = self.__projectMenu.addAction( \
                                PixmapCache().getIcon( 'copytoclipboard.png' ),
                                'Copy path to clipboard',
                                self.__prjPathToClipboard )
        self.__projectMenu.addSeparator()
        self.__delPrjMenuItem = self.__projectMenu.addAction( \
                                PixmapCache().getIcon( 'trash.png' ),
                                'Delete from recent',
                                self.__deleteProject )
        self.projectsView.setContextMenuPolicy( Qt.CustomContextMenu )
        self.connect( self.projectsView,
                      SIGNAL( "customContextMenuRequested(const QPoint &)" ),
                      self.__handleShowPrjContextMenu )

        self.connect( Settings().iInstance, SIGNAL( 'recentListChanged' ),
                      self.__populateProjects )
        GlobalData().project.projectChanged.connect( self.__projectChanged )
        return

    def __createRecentFilesLayout( self ):
        " Creates the upper part - recent files "
        headerFrame = QFrame()
        headerFrame.setFrameStyle( QFrame.StyledPanel )
        headerFrame.setAutoFillBackground( True )
        headerPalette = 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 )
        headerFrame.setPalette( headerPalette )
        headerFrame.setFixedHeight( 24 )

        recentFilesLabel = QLabel()
        recentFilesLabel.setText( "Recent files" )

        headerLayout = QHBoxLayout()
        headerLayout.setContentsMargins( 3, 0, 0, 0 )
        headerLayout.addWidget( recentFilesLabel )
        headerFrame.setLayout( headerLayout )

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

        self.__filesHeaderItem = QTreeWidgetItem( [ "", "File",
                                                    "Absolute path" ] )
        self.recentFilesView.setHeaderItem( self.__filesHeaderItem )
        self.recentFilesView.header().setSortIndicator( 1, Qt.AscendingOrder )

        self.connect( self.recentFilesView,
                      SIGNAL( "itemSelectionChanged()" ),
                      self.__fileSelectionChanged )
        self.connect( self.recentFilesView,
                      SIGNAL( "itemActivated(QTreeWidgetItem *, int)" ),
                      self.__fileActivated )

        # Toolbar part - buttons
        self.openFileButton = QAction( PixmapCache().getIcon( 'openitem.png' ),
                                       'Open the highlighted file', self )
        self.connect( self.openFileButton, SIGNAL( "triggered()" ),
                      self.__openFile )
        self.copyFilePathButton = QAction( \
                        PixmapCache().getIcon( 'copytoclipboard.png' ),
                        'Copy path to clipboard', self )
        self.connect( self.copyFilePathButton, SIGNAL( "triggered()" ),
                      self.__filePathToClipboard )
        spacer = QWidget()
        spacer.setSizePolicy( QSizePolicy.Expanding, QSizePolicy.Expanding )
        self.trashFileButton = QAction( PixmapCache().getIcon( 'delitem.png' ),
                                        'Remove selected (not from the disk)',
                                        self )
        self.connect( self.trashFileButton, SIGNAL( "triggered()" ),
                      self.__deleteFile )

        self.upperToolbar = QToolBar()
        self.upperToolbar.setMovable( False )
        self.upperToolbar.setAllowedAreas( Qt.TopToolBarArea )
        self.upperToolbar.setIconSize( QSize( 16, 16 ) )
        self.upperToolbar.setFixedHeight( 28 )
        self.upperToolbar.setContentsMargins( 0, 0, 0, 0 )
        self.upperToolbar.addAction( self.openFileButton )
        self.upperToolbar.addAction( self.copyFilePathButton )
        self.upperToolbar.addWidget( spacer )
        self.upperToolbar.addAction( self.trashFileButton )

        recentFilesLayout = QVBoxLayout()
        recentFilesLayout.setContentsMargins( 0, 0, 0, 0 )
        recentFilesLayout.setSpacing( 0 )
        recentFilesLayout.addWidget( headerFrame )
        recentFilesLayout.addWidget( self.upperToolbar )
        recentFilesLayout.addWidget( self.recentFilesView )

        upperContainer = QWidget()
        upperContainer.setContentsMargins( 0, 0, 0, 0 )
        upperContainer.setLayout( recentFilesLayout )
        return upperContainer

    def getRecentFilesToolbar( self ):
        " Provides a reference to the recent files toolbar "
        return self.upperToolbar

    def __createRecentProjectsLayout( self ):
        " Creates the bottom layout "
        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 )

        recentProjectsLabel = QLabel()
        recentProjectsLabel.setText( "Recent projects" )

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

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

        headerLayout = QHBoxLayout()
        headerLayout.setContentsMargins( 3, 0, 0, 0 )
        headerLayout.addWidget( recentProjectsLabel )
        headerLayout.addSpacerItem( expandingSpacer )
        headerLayout.addWidget( self.__showHideButton )
        self.headerFrame.setLayout( headerLayout )

        # Toolbar part - buttons
        self.loadButton = QAction( PixmapCache().getIcon( 'load.png' ),
                                   'Load the highlighted project', self )
        self.connect( self.loadButton, SIGNAL( "triggered()" ),
                      self.__loadProject )
        self.propertiesButton = QAction( PixmapCache().getIcon( 'smalli.png' ),
                                         'Show the highlighted project ' \
                                         'properties', self )
        self.connect( self.propertiesButton, SIGNAL( "triggered()" ),
                      self.__viewProperties )
        self.copyPrjPathButton = QAction( \
                        PixmapCache().getIcon( 'copytoclipboard.png' ),
                        'Copy path to clipboard', self )
        self.connect( self.copyPrjPathButton, SIGNAL( "triggered()" ),
                      self.__prjPathToClipboard )
        spacer = QWidget()
        spacer.setSizePolicy( QSizePolicy.Expanding, QSizePolicy.Expanding )
        self.trashButton = QAction( PixmapCache().getIcon( 'delitem.png' ),
                                    'Remove selected (not from the disk)',
                                    self )
        self.connect( self.trashButton, SIGNAL( "triggered()" ),
                      self.__deleteProject )

        self.lowerToolbar = QToolBar()
        self.lowerToolbar.setMovable( False )
        self.lowerToolbar.setAllowedAreas( Qt.TopToolBarArea )
        self.lowerToolbar.setIconSize( QSize( 16, 16 ) )
        self.lowerToolbar.setFixedHeight( 28 )
        self.lowerToolbar.setContentsMargins( 0, 0, 0, 0 )
        self.lowerToolbar.addAction( self.loadButton )
        self.lowerToolbar.addAction( self.propertiesButton )
        self.lowerToolbar.addAction( self.copyPrjPathButton )
        self.lowerToolbar.addWidget( spacer )
        self.lowerToolbar.addAction( self.trashButton )

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

        self.__projectsHeaderItem = QTreeWidgetItem( [ "", "Project",
                                                       "Absolute path" ] )
        self.projectsView.setHeaderItem( self.__projectsHeaderItem )

        self.projectsView.header().setSortIndicator( 1, Qt.AscendingOrder )
        self.connect( self.projectsView,
                      SIGNAL( "itemActivated(QTreeWidgetItem *, int)" ),
                      self.__projectActivated )
        self.connect( self.projectsView,
                      SIGNAL( "itemSelectionChanged()" ),
                      self.__projectSelectionChanged )

        recentProjectsLayout = QVBoxLayout()
        recentProjectsLayout.setContentsMargins( 0, 0, 0, 0 )
        recentProjectsLayout.setSpacing( 0 )
        recentProjectsLayout.addWidget( self.headerFrame )
        recentProjectsLayout.addWidget( self.lowerToolbar )
        recentProjectsLayout.addWidget( self.projectsView )

        lowerContainer = QWidget()
        lowerContainer.setContentsMargins( 0, 0, 0, 0 )
        lowerContainer.setLayout( recentProjectsLayout )
        return lowerContainer

    def getRecentProjectsToolbar( self ):
        " Provides a reference to the projects toolbar "
        return self.lowerToolbar

    def __projectSelectionChanged( self ):
        " Handles the projects changed selection "
        selected = list( self.projectsView.selectedItems() )

        if selected:
            self.__projectContextItem = selected[ 0 ]
        else:
            self.__projectContextItem = None

        self.__updateProjectToolbarButtons()
        return

    def __fileSelectionChanged( self ):
        " Handles the files changed selection "
        selected = list( self.recentFilesView.selectedItems() )

        if selected:
            self.__fileContextItem = selected[ 0 ]
        else:
            self.__fileContextItem = None
        self.__updateFileToolbarButtons()
        return

    def __updateProjectToolbarButtons( self ):
        " Updates the toolbar buttons depending on the __projectContextItem "
        if self.__projectContextItem is None:
            self.loadButton.setEnabled( False )
            self.propertiesButton.setEnabled( False )
            self.copyPrjPathButton.setEnabled( False )
            self.trashButton.setEnabled( False )
        else:
            enabled = self.__projectContextItem.isValid()
            isCurrentProject = self.__projectContextItem.isCurrent()

            self.propertiesButton.setEnabled( enabled )
            self.copyPrjPathButton.setEnabled( True )
            self.loadButton.setEnabled( enabled and
                                        not isCurrentProject and
                                        not self.__debugMode )
            self.trashButton.setEnabled( not isCurrentProject )
        return

    def __updateFileToolbarButtons( self ):
        " Updates the toolbar buttons depending on the __fileContextItem "
        enabled = self.__fileContextItem is not None
        self.openFileButton.setEnabled( enabled )
        self.copyFilePathButton.setEnabled( enabled )
        self.trashFileButton.setEnabled( enabled )
        return

    def __handleShowPrjContextMenu( self, coord ):
        " Show the project item context menu "
        self.__projectContextItem = self.projectsView.itemAt( coord )
        if self.__projectContextItem is None:
            return

        enabled = self.__projectContextItem.isValid()
        isCurrentProject = self.__projectContextItem.isCurrent()

        self.__propsMenuItem.setEnabled( enabled )
        self.__delPrjMenuItem.setEnabled( not isCurrentProject )
#        fName = self.__projectContextItem.getFilename()
        self.__prjLoadMenuItem.setEnabled( enabled and \
                                           not isCurrentProject and \
                                           not self.__debugMode )

        self.__projectMenu.popup( QCursor.pos() )
        return

    def __sortProjects( self ):
        " Sort the project items "
        self.projectsView.sortItems( \
                self.projectsView.sortColumn(),
                self.projectsView.header().sortIndicatorOrder() )
        return

    def __sortFiles( self ):
        " Sort the file items "
        self.recentFilesView.sortItems( \
                self.recentFilesView.sortColumn(),
                self.recentFilesView.header().sortIndicatorOrder() )
        return

    def __resizeProjectColumns( self ):
        """ Resize the projects list columns """
        self.projectsView.header().setStretchLastSection( True )
        self.projectsView.header().resizeSections( \
                                    QHeaderView.ResizeToContents )
        self.projectsView.header().resizeSection( 0, 22 )
        self.projectsView.header().setResizeMode( 0, QHeaderView.Fixed )
        return

    def __resizeFileColumns( self ):
        " Resize the files list columns "
        self.recentFilesView.header().setStretchLastSection( True )
        self.recentFilesView.header().resizeSections( \
                                    QHeaderView.ResizeToContents )
        self.recentFilesView.header().resizeSection( 0, 22 )
        self.recentFilesView.header().setResizeMode( 0, QHeaderView.Fixed )
        return

    def __projectActivated( self, item, column ):
        " Handles the double click (or Enter) on the item "
        self.__projectContextItem = item
        self.__loadProject()
        return

    def __fileActivated( self, item, column ):
        " Handles the double click (or Enter) on a file item "
        self.__fileContextItem = item
        self.__openFile()
        return

    def __viewProperties( self ):
        " Handles the 'view properties' context menu item "
        if self.__projectContextItem is None:
            return
        if not self.__projectContextItem.isValid():
            return

        if self.__projectContextItem.isCurrent():
            # This is the current project - it can be edited
            project = GlobalData().project
            dialog = ProjectPropertiesDialog( project )
            if dialog.exec_() == QDialog.Accepted:
                importDirs = []
                for index in xrange( dialog.importDirList.count() ):
                    importDirs.append( dialog.importDirList.item( index ).text() )

                scriptName = dialog.scriptEdit.text().strip()
                relativePath = relpath( scriptName, project.getProjectDir() )
                if not relativePath.startswith( '..' ):
                    scriptName = relativePath

                project.updateProperties(
                    scriptName, importDirs,
                    dialog.creationDateEdit.text().strip(),
                    dialog.authorEdit.text().strip(),
                    dialog.licenseEdit.text().strip(),
                    dialog.copyrightEdit.text().strip(),
                    dialog.versionEdit.text().strip(),
                    dialog.emailEdit.text().strip(),
                    dialog.descriptionEdit.toPlainText().strip() )
        else:
            # This is not the current project - it can be viewed
            fName = self.__projectContextItem.getFilename()
            dialog = ProjectPropertiesDialog( fName )
            dialog.exec_()
        return

    def __deleteProject( self ):
        " Handles the 'delete from recent' context menu item "
        if self.__projectContextItem is None:
            return

        # Removal from the visible list is done via a signal which comes back
        # from settings
        fName = self.__projectContextItem.getFilename()
        Settings().deleteRecentProject( fName )
        return

    def __loadProject( self ):
        " handles 'Load' context menu item "
        if self.__projectContextItem is None:
            return
        if not self.__projectContextItem.isValid():
            return
        if self.__debugMode:
            return

        projectFileName = self.__projectContextItem.getFilename()

        if self.__projectContextItem.isCurrent():
            GlobalData().mainWindow.openFile( projectFileName, -1 )
            return  # This is the current project, open for text editing

        QApplication.processEvents()
        QApplication.setOverrideCursor( QCursor( Qt.WaitCursor ) )
        if os.path.exists( projectFileName ):
            mainWin = GlobalData().mainWindow
            editorsManager = mainWin.editorsManagerWidget.editorsManager
            if editorsManager.closeRequest():
                prj = GlobalData().project
                prj.setTabsStatus( editorsManager.getTabsStatus() )
                editorsManager.closeAll()
                prj.loadProject( projectFileName )
                mainWin.activateProjectTab()
        else:
            logging.error( "The project " + \
                           os.path.basename( projectFileName ) + \
                           " disappeared from the file system." )
            self.__populateProjects()
        QApplication.restoreOverrideCursor()
        return

    def __populateProjects( self ):
        " Populates the recent projects "
        self.projectsView.clear()
        for item in Settings().recentProjects:
            self.projectsView.addTopLevelItem( RecentProjectViewItem( item ) )

        self.__sortProjects()
        self.__resizeProjectColumns()
        self.__updateProjectToolbarButtons()
        return

    def __populateFiles( self ):
        " Populates the recent files "
        self.recentFilesView.clear()
        for path in GlobalData().project.recentFiles:
            self.recentFilesView.addTopLevelItem( RecentFileViewItem( path ) )

        self.__sortFiles()
        self.__resizeFileColumns()
        self.__updateFileToolbarButtons()
        return

    def __projectChanged( self, what ):
        " Triggered when the current project is changed "
        if what == CodimensionProject.CompleteProject:
            self.__populateProjects()
            self.__populateFiles()
            return

        if what == CodimensionProject.Properties:
            # Update the corresponding tooltip
            items = self.projectsView.findItems( GlobalData().project.fileName,
                                                 Qt.MatchExactly, 2 )
            if len( items ) != 1:
                logging.error( "Unexpected number of matched projects: " + \
                               str( len( items ) ) )
                return

            items[ 0 ].updateTooltip()
            return

    def __openFile( self ):
        " Handles 'open' file menu item "
        self.__fileContextItem.updateIconAndTooltip()
        fName = self.__fileContextItem.getFilename()

        if not self.__fileContextItem.isValid():
            logging.warning( "Cannot open " + fName )
            return

        fileType = detectFileType( fName )
        if fileType == PixmapFileType:
            GlobalData().mainWindow.openPixmapFile( fName )
            return

        GlobalData().mainWindow.openFile( fName, -1 )
        return

    def __deleteFile( self ):
        " Handles 'delete from recent' file menu item "
        self.removeRecentFile( self.__fileContextItem.getFilename() )
        return

    def __handleShowFileContextMenu( self, coord ):
        " File context menu "
        self.__fileContextItem = self.recentFilesView.itemAt( coord )
        if self.__fileContextItem is not None:
            self.__fileMenu.popup( QCursor.pos() )
        return

    def __filePathToClipboard( self ):
        " Copies the file item path to the clipboard "
        if self.__fileContextItem is not None:
            QApplication.clipboard().setText( \
                    self.__fileContextItem.getFilename() )
        return

    def __prjPathToClipboard( self ):
        " Copies the project item path to the clipboard "
        if self.__projectContextItem is not None:
            QApplication.clipboard().setText( \
                    self.__projectContextItem.getFilename() )
        return

    def onFileUpdated( self, fileName, uuid ):
        " Triggered when the file is updated: python or project "
        realPath = os.path.realpath( fileName )

        count = self.recentFilesView.topLevelItemCount()
        for index in xrange( 0, count ):
            item = self.recentFilesView.topLevelItem( index )

            itemRealPath = os.path.realpath( item.getFilename() )
            if realPath == itemRealPath:
                item.updateIconAndTooltip()
                break

        for index in xrange( 0, self.projectsView.topLevelItemCount() ):
            item = self.projectsView.topLevelItem( index )

            itemRealPath = os.path.realpath( item.getFilename() )
            if realPath == itemRealPath:
                item.updateTooltip()
                break
        return

    def __onShowHide( self ):
        " Triggered when show/hide button is clicked "
        if self.projectsView.isVisible():
            self.projectsView.setVisible( False )
            self.lowerToolbar.setVisible( False )
            self.__showHideButton.setIcon( PixmapCache().getIcon( 'more.png' ) )
            self.__showHideButton.setToolTip( "Show recent projects list" )

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

            self.lower.setMinimumHeight( self.headerFrame.height() )
            self.lower.setMaximumHeight( self.headerFrame.height() )
        else:
            self.projectsView.setVisible( True )
            self.lowerToolbar.setVisible( True )
            self.__showHideButton.setIcon( PixmapCache().getIcon( 'less.png' ) )
            self.__showHideButton.setToolTip( "Hide recent projects list" )

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

    def __onDebugMode( self, newState ):
        " Triggered when debug mode has changed "
        self.__debugMode = newState

        # Disable the load project button
        self.__updateProjectToolbarButtons()
        return

    def removeRecentFile( self, fName ):
        " Removes a single file from the recent files list "
        GlobalData().project.removeRecentFile( fName )

        for index in xrange( self.recentFilesView.topLevelItemCount() ):
            candidate = self.recentFilesView.topLevelItem( index )
            if candidate.getFilename() == fName:
                self.recentFilesView.takeTopLevelItem( index )
                return
        return
Beispiel #4
0
class ThreadsViewer(QWidget):
    " Implements the threads viewer for a debugger "

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

        self.__debugger = debugger
        self.__createLayout()

        if Settings().showThreadViewer == False:
            self.__onShowHide(True)
        return

    def __createLayout(self):
        " 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.__threadsLabel = QLabel("Threads")

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

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

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

        self.__threadsList = QTreeWidget()
        self.__threadsList.setSortingEnabled(False)
        # I might not need that because of two reasons:
        # - the window has no focus
        # - the window has custom current indicator
        # self.__threadsList.setAlternatingRowColors( True )
        self.__threadsList.setRootIsDecorated(False)
        self.__threadsList.setItemsExpandable(False)
        self.__threadsList.setUniformRowHeights(True)
        self.__threadsList.setSelectionMode(QAbstractItemView.NoSelection)
        self.__threadsList.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.__threadsList.setItemDelegate(NoOutlineHeightDelegate(4))
        self.__threadsList.setFocusPolicy(Qt.NoFocus)

        self.__threadsList.itemClicked.connect(self.__onThreadClicked)
        self.__threadsList.setHeaderLabels(["", "Name", "State", "TID"])

        verticalLayout.addWidget(self.headerFrame)
        verticalLayout.addWidget(self.__threadsList)
        return

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

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

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

            Settings().showThreadViewer = False
        else:
            self.__threadsList.setVisible(True)
            self.__showHideButton.setIcon(PixmapCache().getIcon('less.png'))
            self.__showHideButton.setToolTip("Hide threads list")

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

            Settings().showThreadViewer = True
        return

    def __resizeColumns(self):
        " Resize the files list columns "
        self.__threadsList.header().setStretchLastSection(True)
        self.__threadsList.header().resizeSections(
            QHeaderView.ResizeToContents)
        self.__threadsList.header().resizeSection(0, 22)
        self.__threadsList.header().setResizeMode(0, QHeaderView.Fixed)
        return

    def clear(self):
        " Clears the content "
        self.__threadsList.clear()
        self.__threadsLabel.setText("Threads")
        return

    def populate(self, currentThreadID, threadList):
        " Populates the thread list from the client "
        self.clear()
        for thread in threadList:
            if thread['broken']:
                state = "Waiting at breakpoint"
            else:
                state = "Running"
            item = ThreadItem(thread['id'], thread['name'], state)
            if thread['id'] == currentThreadID:
                item.setCurrent(True)
            self.__threadsList.addTopLevelItem(item)

        self.__resizeColumns()
        self.__threadsLabel.setText("Threads (total: " + str(len(threadList)) +
                                    ")")
        return

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

    def __onThreadClicked(self, item, column):
        " Triggered when a thread is clicked "
        if item.isCurrent():
            return

        for index in xrange(self.__threadsList.topLevelItemCount()):
            listItem = self.__threadsList.topLevelItem(index)
            if listItem.isCurrent():
                listItem.setCurrent(False)
                break
        item.setCurrent(True)

        self.__debugger.remoteSetThread(item.getTID())
        return
Beispiel #5
0
class StackViewer( QWidget ):
    " Implements the stack viewer for a debugger "

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

        self.__debugger = debugger
        self.currentStack = None
        self.currentFrame = 0
        self.__createPopupMenu()
        self.__createLayout()

        if Settings().showStackViewer == False:
            self.__onShowHide( True )
        return

    def __createPopupMenu( self ):
        " Creates the popup menu "
        self.__framesMenu = QMenu()
        self.__setCurrentMenuItem = self.__framesMenu.addAction(
                    "Set current (single click)", self.__onSetCurrent )
        self.__jumpMenuItem = self.__framesMenu.addAction(
                    "Set current and jump to the source (double click)",
                    self.__onSetCurrentAndJump )
        return

    def __createLayout( self ):
        " 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.__stackLabel = QLabel( "Stack" )

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

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

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

        self.__framesList = QTreeWidget( self )
        self.__framesList.setSortingEnabled( False )
        # I might not need that because of two reasons:
        # - the window has no focus
        # - the window has custom current indicator
        # self.__framesList.setAlternatingRowColors( True )
        self.__framesList.setRootIsDecorated( False )
        self.__framesList.setItemsExpandable( False )
        self.__framesList.setUniformRowHeights( True )
        self.__framesList.setSelectionMode( QAbstractItemView.NoSelection )
        self.__framesList.setSelectionBehavior( QAbstractItemView.SelectRows )
        self.__framesList.setItemDelegate( NoOutlineHeightDelegate( 4 ) )
        self.__framesList.setFocusPolicy( Qt.NoFocus )
        self.__framesList.setContextMenuPolicy( Qt.CustomContextMenu )

        self.__framesList.itemClicked.connect( self.__onFrameClicked )
        self.__framesList.itemDoubleClicked.connect( self.__onFrameDoubleClicked )
        self.__framesList.customContextMenuRequested.connect( self.__showContextMenu )

        self.__framesList.setHeaderLabels( [ "", "File:line", "Function",
                                             "Full path" ] )

        verticalLayout.addWidget( self.headerFrame )
        verticalLayout.addWidget( self.__framesList )
        return

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

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

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

            Settings().showStackViewer = False
        else:
            self.__framesList.setVisible( True )
            self.__showHideButton.setIcon( PixmapCache().getIcon( 'less.png' ) )
            self.__showHideButton.setToolTip( "Hide frames list" )

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

            Settings().showStackViewer = True
        return

    def clear( self ):
        " Clears the content "
        self.__framesList.clear()
        self.currentStack = None
        self.__stackLabel.setText( "Stack" )
        return

    def __resizeColumns( self ):
        " Resize the files list columns "
        self.__framesList.header().setStretchLastSection( True )
        self.__framesList.header().resizeSections(
                                    QHeaderView.ResizeToContents )
        self.__framesList.header().resizeSection( 0, 22 )
        self.__framesList.header().setResizeMode( 0, QHeaderView.Fixed )
        return


    def populate( self, stack ):
        " Sets the new call stack and selects the first item in it "
        self.clear()

        self.currentStack = stack
        self.currentFrame = 0
        frameNumber = 0
        for s in stack:
            if len( s ) == 2:
                # This is when an exception comes
                funcName = ""
            else:
                funcName = s[ 2 ]
            item = StackFrameItem( s[ 0 ], s[ 1 ], funcName, frameNumber )
            self.__framesList.addTopLevelItem( item )
            frameNumber += 1
        self.__resizeColumns()
        self.__framesList.topLevelItem( 0 ).setCurrent( True )
        self.__stackLabel.setText( "Stack (total: " +
                                   str( len( stack ) ) + ")" )
        return

    def getFrameNumber( self ):
        " Provides the current frame number "
        return self.currentFrame

    def __onFrameClicked( self, item, column ):
        " Triggered when a frame is clicked "
        if item.isCurrent():
            return

        # Hide the current indicator
        self.__framesList.topLevelItem( self.currentFrame ).setCurrent( False )

        # Show the new indicator
        self.currentFrame = item.getFrameNumber()
        for index in xrange( self.__framesList.topLevelItemCount() ):
            item = self.__framesList.topLevelItem( index )
            if item.getFrameNumber() == self.currentFrame:
                item.setCurrent( True )
        self.__debugger.remoteClientVariables( 1, self.currentFrame )  # globals
        self.__debugger.remoteClientVariables( 0, self.currentFrame )  # locals
        return

    def __onFrameDoubleClicked( self, item, column ):
        " Triggered when a frame is double clicked "
        # The frame has been switched already because the double click
        # signal always comes after the single click one
        fileName = item.getFilename()
        lineNumber = item.getLineNumber()

        editorsManager = GlobalData().mainWindow.editorsManager()
        editorsManager.openFile( fileName, lineNumber )
        editor = editorsManager.currentWidget().getEditor()
        editor.gotoLine( lineNumber )
        editorsManager.currentWidget().setFocus()
        return

    def __showContextMenu( self, coord ):
        " Shows the frames list context menu "
        self.__contextItem = self.__framesList.itemAt( coord )
        if self.__contextItem is not None:
            self.__setCurrentMenuItem.setEnabled(
                                not self.__contextItem.isCurrent() )
            self.__framesMenu.popup( QCursor.pos() )
        return

    def __onSetCurrent( self ):
        " Context menu item handler "
        self.__onFrameClicked( self.__contextItem, 0 )
        return

    def __onSetCurrentAndJump( self ):
        " Context menu item handler "
        self.__onFrameClicked( self.__contextItem, 0 )
        self.__onFrameDoubleClicked( self.__contextItem, 0 )
        return

    def switchControl( self, isInIDE ):
        " Switches the UI depending where the control flow is "
        self.__framesList.setEnabled( isInIDE )
        return
Beispiel #6
0
class RecentProjectsViewer(QWidget):
    " Recent projects viewer implementation "

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

        self.__projectContextItem = None
        self.__fileContextItem = None

        self.upper = self.__createRecentFilesLayout()
        self.lower = self.__createRecentProjectsLayout()
        self.__createProjectPopupMenu()
        self.__createFilePopupMenu()

        layout = QVBoxLayout()
        layout.setContentsMargins(1, 1, 1, 1)
        splitter = QSplitter(Qt.Vertical)
        splitter.addWidget(self.upper)
        splitter.addWidget(self.lower)
        splitter.setCollapsible(0, False)
        splitter.setCollapsible(1, False)

        layout.addWidget(splitter)
        self.setLayout(layout)

        self.__populateProjects()
        self.__populateFiles()
        self.__updateProjectToolbarButtons()
        self.__updateFileToolbarButtons()

        # Debugging mode support
        self.__debugMode = False
        parent.debugModeChanged.connect(self.__onDebugMode)
        return

    def setTooltips(self, switchOn):
        " Switches the tooltips mode "
        for index in xrange(0, self.recentFilesView.topLevelItemCount()):
            self.recentFilesView.topLevelItem(index).updateIconAndTooltip()
        for index in xrange(0, self.projectsView.topLevelItemCount()):
            self.projectsView.topLevelItem(index).updateTooltip()
        return

    def __createFilePopupMenu(self):
        " create the recent files popup menu "
        self.__fileMenu = QMenu(self.recentFilesView)
        self.__openMenuItem = self.__fileMenu.addAction(
            getIcon('openitem.png'), 'Open', self.__openFile)
        self.__copyPathFileMenuItem = self.__fileMenu.addAction(
            getIcon('copytoclipboard.png'), 'Copy path to clipboard',
            self.__filePathToClipboard)
        self.__fileMenu.addSeparator()
        self.__delFileMenuItem = self.__fileMenu.addAction(
            getIcon('trash.png'), 'Delete from recent', self.__deleteFile)
        self.recentFilesView.setContextMenuPolicy(Qt.CustomContextMenu)
        self.connect(self.recentFilesView,
                     SIGNAL("customContextMenuRequested(const QPoint &)"),
                     self.__handleShowFileContextMenu)

        self.connect(GlobalData().project, SIGNAL('recentFilesChanged'),
                     self.__populateFiles)
        return

    def __createProjectPopupMenu(self):
        " Creates the recent project popup menu "
        self.__projectMenu = QMenu(self.projectsView)
        self.__prjLoadMenuItem = self.__projectMenu.addAction(
            getIcon('load.png'), 'Load', self.__loadProject)
        self.__projectMenu.addSeparator()
        self.__propsMenuItem = self.__projectMenu.addAction(
            getIcon('smalli.png'), 'Properties', self.__viewProperties)
        self.__prjCopyPathMenuItem = self.__projectMenu.addAction(
            getIcon('copytoclipboard.png'), 'Copy path to clipboard',
            self.__prjPathToClipboard)
        self.__projectMenu.addSeparator()
        self.__delPrjMenuItem = self.__projectMenu.addAction(
            getIcon('trash.png'), 'Delete from recent', self.__deleteProject)
        self.projectsView.setContextMenuPolicy(Qt.CustomContextMenu)
        self.connect(self.projectsView,
                     SIGNAL("customContextMenuRequested(const QPoint &)"),
                     self.__handleShowPrjContextMenu)

        Settings().recentListChanged.connect(self.__populateProjects)
        GlobalData().project.projectChanged.connect(self.__projectChanged)
        return

    def __createRecentFilesLayout(self):
        " Creates the upper part - recent files "
        headerFrame = QFrame()
        headerFrame.setFrameStyle(QFrame.StyledPanel)
        headerFrame.setAutoFillBackground(True)
        headerPalette = 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)
        headerFrame.setPalette(headerPalette)
        headerFrame.setFixedHeight(24)

        recentFilesLabel = QLabel()
        recentFilesLabel.setText("Recent files")

        headerLayout = QHBoxLayout()
        headerLayout.setContentsMargins(3, 0, 0, 0)
        headerLayout.addWidget(recentFilesLabel)
        headerFrame.setLayout(headerLayout)

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

        self.__filesHeaderItem = QTreeWidgetItem(["", "File", "Absolute path"])
        self.recentFilesView.setHeaderItem(self.__filesHeaderItem)
        self.recentFilesView.header().setSortIndicator(1, Qt.AscendingOrder)

        self.connect(self.recentFilesView, SIGNAL("itemSelectionChanged()"),
                     self.__fileSelectionChanged)
        self.connect(self.recentFilesView,
                     SIGNAL("itemActivated(QTreeWidgetItem *, int)"),
                     self.__fileActivated)

        # Toolbar part - buttons
        self.openFileButton = QAction(getIcon('openitem.png'),
                                      'Open the highlighted file', self)
        self.connect(self.openFileButton, SIGNAL("triggered()"),
                     self.__openFile)
        self.copyFilePathButton = QAction(getIcon('copytoclipboard.png'),
                                          'Copy path to clipboard', self)
        self.connect(self.copyFilePathButton, SIGNAL("triggered()"),
                     self.__filePathToClipboard)
        spacer = QWidget()
        spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        self.trashFileButton = QAction(getIcon('delitem.png'),
                                       'Remove selected (not from the disk)',
                                       self)
        self.connect(self.trashFileButton, SIGNAL("triggered()"),
                     self.__deleteFile)

        self.upperToolbar = QToolBar()
        self.upperToolbar.setMovable(False)
        self.upperToolbar.setAllowedAreas(Qt.TopToolBarArea)
        self.upperToolbar.setIconSize(QSize(16, 16))
        self.upperToolbar.setFixedHeight(28)
        self.upperToolbar.setContentsMargins(0, 0, 0, 0)
        self.upperToolbar.addAction(self.openFileButton)
        self.upperToolbar.addAction(self.copyFilePathButton)
        self.upperToolbar.addWidget(spacer)
        self.upperToolbar.addAction(self.trashFileButton)

        recentFilesLayout = QVBoxLayout()
        recentFilesLayout.setContentsMargins(0, 0, 0, 0)
        recentFilesLayout.setSpacing(0)
        recentFilesLayout.addWidget(headerFrame)
        recentFilesLayout.addWidget(self.upperToolbar)
        recentFilesLayout.addWidget(self.recentFilesView)

        upperContainer = QWidget()
        upperContainer.setContentsMargins(0, 0, 0, 0)
        upperContainer.setLayout(recentFilesLayout)
        return upperContainer

    def getRecentFilesToolbar(self):
        " Provides a reference to the recent files toolbar "
        return self.upperToolbar

    def __createRecentProjectsLayout(self):
        " Creates the bottom layout "
        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)

        recentProjectsLabel = QLabel()
        recentProjectsLabel.setText("Recent projects")

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

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

        headerLayout = QHBoxLayout()
        headerLayout.setContentsMargins(3, 0, 0, 0)
        headerLayout.addWidget(recentProjectsLabel)
        headerLayout.addSpacerItem(expandingSpacer)
        headerLayout.addWidget(self.__showHideButton)
        self.headerFrame.setLayout(headerLayout)

        # Toolbar part - buttons
        self.loadButton = QAction(getIcon('load.png'),
                                  'Load the highlighted project', self)
        self.connect(self.loadButton, SIGNAL("triggered()"),
                     self.__loadProject)
        self.propertiesButton = QAction(
            getIcon('smalli.png'), 'Show the highlighted project '
            'properties', self)
        self.connect(self.propertiesButton, SIGNAL("triggered()"),
                     self.__viewProperties)
        self.copyPrjPathButton = QAction(getIcon('copytoclipboard.png'),
                                         'Copy path to clipboard', self)
        self.connect(self.copyPrjPathButton, SIGNAL("triggered()"),
                     self.__prjPathToClipboard)
        spacer = QWidget()
        spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        self.trashButton = QAction(getIcon('delitem.png'),
                                   'Remove selected (not from the disk)', self)
        self.connect(self.trashButton, SIGNAL("triggered()"),
                     self.__deleteProject)

        self.lowerToolbar = QToolBar()
        self.lowerToolbar.setMovable(False)
        self.lowerToolbar.setAllowedAreas(Qt.TopToolBarArea)
        self.lowerToolbar.setIconSize(QSize(16, 16))
        self.lowerToolbar.setFixedHeight(28)
        self.lowerToolbar.setContentsMargins(0, 0, 0, 0)
        self.lowerToolbar.addAction(self.loadButton)
        self.lowerToolbar.addAction(self.propertiesButton)
        self.lowerToolbar.addAction(self.copyPrjPathButton)
        self.lowerToolbar.addWidget(spacer)
        self.lowerToolbar.addAction(self.trashButton)

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

        self.__projectsHeaderItem = QTreeWidgetItem(
            ["", "Project", "Absolute path"])
        self.projectsView.setHeaderItem(self.__projectsHeaderItem)

        self.projectsView.header().setSortIndicator(1, Qt.AscendingOrder)
        self.connect(self.projectsView,
                     SIGNAL("itemActivated(QTreeWidgetItem *, int)"),
                     self.__projectActivated)
        self.connect(self.projectsView, SIGNAL("itemSelectionChanged()"),
                     self.__projectSelectionChanged)

        recentProjectsLayout = QVBoxLayout()
        recentProjectsLayout.setContentsMargins(0, 0, 0, 0)
        recentProjectsLayout.setSpacing(0)
        recentProjectsLayout.addWidget(self.headerFrame)
        recentProjectsLayout.addWidget(self.lowerToolbar)
        recentProjectsLayout.addWidget(self.projectsView)

        lowerContainer = QWidget()
        lowerContainer.setContentsMargins(0, 0, 0, 0)
        lowerContainer.setLayout(recentProjectsLayout)
        return lowerContainer

    def getRecentProjectsToolbar(self):
        " Provides a reference to the projects toolbar "
        return self.lowerToolbar

    def __projectSelectionChanged(self):
        " Handles the projects changed selection "
        selected = list(self.projectsView.selectedItems())

        if selected:
            self.__projectContextItem = selected[0]
        else:
            self.__projectContextItem = None

        self.__updateProjectToolbarButtons()
        return

    def __fileSelectionChanged(self):
        " Handles the files changed selection "
        selected = list(self.recentFilesView.selectedItems())

        if selected:
            self.__fileContextItem = selected[0]
        else:
            self.__fileContextItem = None
        self.__updateFileToolbarButtons()
        return

    def __updateProjectToolbarButtons(self):
        " Updates the toolbar buttons depending on the __projectContextItem "
        if self.__projectContextItem is None:
            self.loadButton.setEnabled(False)
            self.propertiesButton.setEnabled(False)
            self.copyPrjPathButton.setEnabled(False)
            self.trashButton.setEnabled(False)
        else:
            enabled = self.__projectContextItem.isValid()
            isCurrentProject = self.__projectContextItem.isCurrent()

            self.propertiesButton.setEnabled(enabled)
            self.copyPrjPathButton.setEnabled(True)
            self.loadButton.setEnabled(enabled and not isCurrentProject
                                       and not self.__debugMode)
            self.trashButton.setEnabled(not isCurrentProject)
        return

    def __updateFileToolbarButtons(self):
        " Updates the toolbar buttons depending on the __fileContextItem "
        enabled = self.__fileContextItem is not None
        self.openFileButton.setEnabled(enabled)
        self.copyFilePathButton.setEnabled(enabled)
        self.trashFileButton.setEnabled(enabled)
        return

    def __handleShowPrjContextMenu(self, coord):
        " Show the project item context menu "
        self.__projectContextItem = self.projectsView.itemAt(coord)
        if self.__projectContextItem is None:
            return

        enabled = self.__projectContextItem.isValid()
        isCurrentProject = self.__projectContextItem.isCurrent()

        self.__propsMenuItem.setEnabled(enabled)
        self.__delPrjMenuItem.setEnabled(not isCurrentProject)
        #        fName = self.__projectContextItem.getFilename()
        self.__prjLoadMenuItem.setEnabled(enabled and not isCurrentProject
                                          and not self.__debugMode)

        self.__projectMenu.popup(QCursor.pos())
        return

    def __sortProjects(self):
        " Sort the project items "
        self.projectsView.sortItems( \
                self.projectsView.sortColumn(),
                self.projectsView.header().sortIndicatorOrder() )
        return

    def __sortFiles(self):
        " Sort the file items "
        self.recentFilesView.sortItems(
            self.recentFilesView.sortColumn(),
            self.recentFilesView.header().sortIndicatorOrder())
        return

    def __resizeProjectColumns(self):
        """ Resize the projects list columns """
        self.projectsView.header().setStretchLastSection(True)
        self.projectsView.header().resizeSections(QHeaderView.ResizeToContents)
        self.projectsView.header().resizeSection(0, 22)
        self.projectsView.header().setResizeMode(0, QHeaderView.Fixed)
        return

    def __resizeFileColumns(self):
        " Resize the files list columns "
        self.recentFilesView.header().setStretchLastSection(True)
        self.recentFilesView.header().resizeSections(
            QHeaderView.ResizeToContents)
        self.recentFilesView.header().resizeSection(0, 22)
        self.recentFilesView.header().setResizeMode(0, QHeaderView.Fixed)
        return

    def __projectActivated(self, item, column):
        " Handles the double click (or Enter) on the item "
        self.__projectContextItem = item
        self.__loadProject()
        return

    def __fileActivated(self, item, column):
        " Handles the double click (or Enter) on a file item "
        self.__fileContextItem = item
        self.__openFile()
        return

    def __viewProperties(self):
        " Handles the 'view properties' context menu item "
        if self.__projectContextItem is None:
            return
        if not self.__projectContextItem.isValid():
            return

        if self.__projectContextItem.isCurrent():
            # This is the current project - it can be edited
            project = GlobalData().project
            dialog = ProjectPropertiesDialog(project, self)
            if dialog.exec_() == QDialog.Accepted:
                importDirs = []
                for index in xrange(dialog.importDirList.count()):
                    importDirs.append(dialog.importDirList.item(index).text())

                scriptName = dialog.scriptEdit.text().strip()
                relativePath = relpath(scriptName, project.getProjectDir())
                if not relativePath.startswith('..'):
                    scriptName = relativePath

                project.updateProperties(
                    scriptName, importDirs,
                    dialog.creationDateEdit.text().strip(),
                    dialog.authorEdit.text().strip(),
                    dialog.licenseEdit.text().strip(),
                    dialog.copyrightEdit.text().strip(),
                    dialog.versionEdit.text().strip(),
                    dialog.emailEdit.text().strip(),
                    dialog.descriptionEdit.toPlainText().strip())
        else:
            # This is not the current project - it can be viewed
            fName = self.__projectContextItem.getFilename()
            dialog = ProjectPropertiesDialog(fName, self)
            dialog.exec_()
        return

    def __deleteProject(self):
        " Handles the 'delete from recent' context menu item "
        if self.__projectContextItem is None:
            return

        # Removal from the visible list is done via a signal which comes back
        # from settings
        fName = self.__projectContextItem.getFilename()
        Settings().deleteRecentProject(fName)
        return

    def __loadProject(self):
        " handles 'Load' context menu item "
        if self.__projectContextItem is None:
            return
        if not self.__projectContextItem.isValid():
            return
        if self.__debugMode:
            return

        projectFileName = self.__projectContextItem.getFilename()

        if self.__projectContextItem.isCurrent():
            GlobalData().mainWindow.openFile(projectFileName, -1)
            return  # This is the current project, open for text editing

        QApplication.processEvents()
        QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
        if os.path.exists(projectFileName):
            mainWin = GlobalData().mainWindow
            editorsManager = mainWin.editorsManagerWidget.editorsManager
            if editorsManager.closeRequest():
                prj = GlobalData().project
                prj.setTabsStatus(editorsManager.getTabsStatus())
                editorsManager.closeAll()
                prj.loadProject(projectFileName)
                mainWin.activateProjectTab()
        else:
            logging.error("The project " + os.path.basename(projectFileName) +
                          " disappeared from the file system.")
            self.__populateProjects()
        QApplication.restoreOverrideCursor()
        return

    def __populateProjects(self):
        " Populates the recent projects "
        self.projectsView.clear()
        for item in Settings().recentProjects:
            self.projectsView.addTopLevelItem(RecentProjectViewItem(item))

        self.__sortProjects()
        self.__resizeProjectColumns()
        self.__updateProjectToolbarButtons()
        return

    def __populateFiles(self):
        " Populates the recent files "
        self.recentFilesView.clear()
        for path in GlobalData().project.recentFiles:
            self.recentFilesView.addTopLevelItem(RecentFileViewItem(path))

        self.__sortFiles()
        self.__resizeFileColumns()
        self.__updateFileToolbarButtons()
        return

    def __projectChanged(self, what):
        " Triggered when the current project is changed "
        if what == CodimensionProject.CompleteProject:
            self.__populateProjects()
            self.__populateFiles()
            return

        if what == CodimensionProject.Properties:
            # Update the corresponding tooltip
            items = self.projectsView.findItems(GlobalData().project.fileName,
                                                Qt.MatchExactly, 2)
            if len(items) != 1:
                logging.error("Unexpected number of matched projects: " +
                              str(len(items)))
                return

            items[0].updateTooltip()
            return

    def __openFile(self):
        " Handles 'open' file menu item "
        self.__fileContextItem.updateIconAndTooltip()
        fName = self.__fileContextItem.getFilename()

        if not self.__fileContextItem.isValid():
            logging.warning("Cannot open " + fName)
            return

        fileType = detectFileType(fName)
        if fileType == PixmapFileType:
            GlobalData().mainWindow.openPixmapFile(fName)
            return

        GlobalData().mainWindow.openFile(fName, -1)
        return

    def __deleteFile(self):
        " Handles 'delete from recent' file menu item "
        self.removeRecentFile(self.__fileContextItem.getFilename())
        return

    def __handleShowFileContextMenu(self, coord):
        " File context menu "
        self.__fileContextItem = self.recentFilesView.itemAt(coord)
        if self.__fileContextItem is not None:
            self.__fileMenu.popup(QCursor.pos())
        return

    def __filePathToClipboard(self):
        " Copies the file item path to the clipboard "
        if self.__fileContextItem is not None:
            QApplication.clipboard().setText(
                self.__fileContextItem.getFilename())
        return

    def __prjPathToClipboard(self):
        " Copies the project item path to the clipboard "
        if self.__projectContextItem is not None:
            QApplication.clipboard().setText(
                self.__projectContextItem.getFilename())
        return

    def onFileUpdated(self, fileName, uuid):
        " Triggered when the file is updated: python or project "
        realPath = os.path.realpath(fileName)

        count = self.recentFilesView.topLevelItemCount()
        for index in xrange(0, count):
            item = self.recentFilesView.topLevelItem(index)

            itemRealPath = os.path.realpath(item.getFilename())
            if realPath == itemRealPath:
                item.updateIconAndTooltip()
                break

        for index in xrange(0, self.projectsView.topLevelItemCount()):
            item = self.projectsView.topLevelItem(index)

            itemRealPath = os.path.realpath(item.getFilename())
            if realPath == itemRealPath:
                item.updateTooltip()
                break
        return

    def __onShowHide(self):
        " Triggered when show/hide button is clicked "
        if self.projectsView.isVisible():
            self.projectsView.setVisible(False)
            self.lowerToolbar.setVisible(False)
            self.__showHideButton.setIcon(getIcon('more.png'))
            self.__showHideButton.setToolTip("Show recent projects list")

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

            self.lower.setMinimumHeight(self.headerFrame.height())
            self.lower.setMaximumHeight(self.headerFrame.height())
        else:
            self.projectsView.setVisible(True)
            self.lowerToolbar.setVisible(True)
            self.__showHideButton.setIcon(getIcon('less.png'))
            self.__showHideButton.setToolTip("Hide recent projects list")

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

    def __onDebugMode(self, newState):
        " Triggered when debug mode has changed "
        self.__debugMode = newState

        # Disable the load project button
        self.__updateProjectToolbarButtons()
        return

    def removeRecentFile(self, fName):
        " Removes a single file from the recent files list "
        GlobalData().project.removeRecentFile(fName)

        for index in xrange(self.recentFilesView.topLevelItemCount()):
            candidate = self.recentFilesView.topLevelItem(index)
            if candidate.getFilename() == fName:
                self.recentFilesView.takeTopLevelItem(index)
                return
        return
Beispiel #7
0
class ThreadsViewer( QWidget ):
    " Implements the threads viewer for a debugger "

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

        self.__debugger = debugger
        self.__createLayout()

        if Settings().showThreadViewer == False:
            self.__onShowHide( True )
        return

    def __createLayout( self ):
        " 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.__threadsLabel = QLabel( "Threads" )

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

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

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

        self.__threadsList = QTreeWidget()
        self.__threadsList.setSortingEnabled( False )
        # I might not need that because of two reasons:
        # - the window has no focus
        # - the window has custom current indicator
        # self.__threadsList.setAlternatingRowColors( True )
        self.__threadsList.setRootIsDecorated( False )
        self.__threadsList.setItemsExpandable( False )
        self.__threadsList.setUniformRowHeights( True )
        self.__threadsList.setSelectionMode( QAbstractItemView.NoSelection )
        self.__threadsList.setSelectionBehavior( QAbstractItemView.SelectRows )
        self.__threadsList.setItemDelegate( NoOutlineHeightDelegate( 4 ) )
        self.__threadsList.setFocusPolicy( Qt.NoFocus )

        self.__threadsList.itemClicked.connect( self.__onThreadClicked )
        self.__threadsList.setHeaderLabels( [ "", "Name", "State", "TID" ] )

        verticalLayout.addWidget( self.headerFrame )
        verticalLayout.addWidget( self.__threadsList )
        return

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

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

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

            Settings().showThreadViewer = False
        else:
            self.__threadsList.setVisible( True )
            self.__showHideButton.setIcon( PixmapCache().getIcon( 'less.png' ) )
            self.__showHideButton.setToolTip( "Hide threads list" )

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

            Settings().showThreadViewer = True
        return

    def __resizeColumns( self ):
        " Resize the files list columns "
        self.__threadsList.header().setStretchLastSection( True )
        self.__threadsList.header().resizeSections(
                                    QHeaderView.ResizeToContents )
        self.__threadsList.header().resizeSection( 0, 22 )
        self.__threadsList.header().setResizeMode( 0, QHeaderView.Fixed )
        return

    def clear( self ):
        " Clears the content "
        self.__threadsList.clear()
        self.__threadsLabel.setText( "Threads" )
        return

    def populate( self, currentThreadID, threadList ):
        " Populates the thread list from the client "
        self.clear()
        for thread in threadList:
            if thread[ 'broken' ]:
                state = "Waiting at breakpoint"
            else:
                state = "Running"
            item = ThreadItem( thread[ 'id' ], thread[ 'name' ], state )
            if thread[ 'id' ] == currentThreadID:
                item.setCurrent( True )
            self.__threadsList.addTopLevelItem( item )

        self.__resizeColumns()
        self.__threadsLabel.setText( "Threads (total: " +
                                     str( len( threadList ) ) + ")" )
        return

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

    def __onThreadClicked( self, item, column ):
        " Triggered when a thread is clicked "
        if item.isCurrent():
            return

        for index in xrange( self.__threadsList.topLevelItemCount() ):
            listItem = self.__threadsList.topLevelItem( index )
            if listItem.isCurrent():
                listItem.setCurrent( False )
                break
        item.setCurrent( True )

        self.__debugger.remoteSetThread( item.getTID() )
        return
Beispiel #8
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 ]

            fileType = detectFileType( item[ 0 ] )

            if os.path.isdir( item[ 0 ] ) or item[ 1 ] in [ IND_REPLACED ] \
                or not isFileTypeSearchable( fileType ):
                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()
        return

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

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

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

    @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 )
        return

    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( PixmapCache().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( PixmapCache().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( PixmapCache().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 )
        return

    def __onShowHideDiff( self ):
        if self.__diffViewer.isVisible():
            self.__diffViewer.setVisible( False )
            self.__showHideDiffButton.setIcon( PixmapCache().getIcon( 'less.png' ) )
            self.__showHideDiffButton.setToolTip( "Show diff" )
        else:
            self.__diffViewer.setVisible( True )
            self.__showHideDiffButton.setIcon( PixmapCache().getIcon( 'more.png' ) )
            self.__showHideDiffButton.setToolTip( "Hide diff" )
        return

    def __onShowHideIgnored( self ):
        if self.__pathToIgnoreView.isVisible():
            self.__pathToIgnoreView.setVisible( False )
            self.__showHideIgnoredButton.setIcon( PixmapCache().getIcon( 'less.png' ) )
            self.__showHideIgnoredButton.setToolTip( "Show ignored path list" )
        else:
            self.__pathToIgnoreView.setVisible( True )
            self.__showHideIgnoredButton.setIcon( PixmapCache().getIcon( 'more.png' ) )
            self.__showHideIgnoredButton.setToolTip( "Hide ignored path list" )
        return

    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()
        return

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

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

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

    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()
        return

    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, exc:
            logging.error( str( exc ) )
        except:
Beispiel #9
0
class StackViewer(QWidget):
    " Implements the stack viewer for a debugger "

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

        self.__debugger = debugger
        self.currentStack = None
        self.currentFrame = 0
        self.__createPopupMenu()
        self.__createLayout()

        if Settings().showStackViewer == False:
            self.__onShowHide(True)
        return

    def __createPopupMenu(self):
        " Creates the popup menu "
        self.__framesMenu = QMenu()
        self.__setCurrentMenuItem = self.__framesMenu.addAction(
            "Set current (single click)", self.__onSetCurrent)
        self.__jumpMenuItem = self.__framesMenu.addAction(
            "Set current and jump to the source (double click)",
            self.__onSetCurrentAndJump)
        return

    def __createLayout(self):
        " 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.__stackLabel = QLabel("Stack")

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

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

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

        self.__framesList = QTreeWidget(self)
        self.__framesList.setSortingEnabled(False)
        # I might not need that because of two reasons:
        # - the window has no focus
        # - the window has custom current indicator
        # self.__framesList.setAlternatingRowColors( True )
        self.__framesList.setRootIsDecorated(False)
        self.__framesList.setItemsExpandable(False)
        self.__framesList.setUniformRowHeights(True)
        self.__framesList.setSelectionMode(QAbstractItemView.NoSelection)
        self.__framesList.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.__framesList.setItemDelegate(NoOutlineHeightDelegate(4))
        self.__framesList.setFocusPolicy(Qt.NoFocus)
        self.__framesList.setContextMenuPolicy(Qt.CustomContextMenu)

        self.__framesList.itemClicked.connect(self.__onFrameClicked)
        self.__framesList.itemDoubleClicked.connect(
            self.__onFrameDoubleClicked)
        self.__framesList.customContextMenuRequested.connect(
            self.__showContextMenu)

        self.__framesList.setHeaderLabels(
            ["", "File:line", "Function", "Full path"])

        verticalLayout.addWidget(self.headerFrame)
        verticalLayout.addWidget(self.__framesList)
        return

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

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

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

            Settings().showStackViewer = False
        else:
            self.__framesList.setVisible(True)
            self.__showHideButton.setIcon(PixmapCache().getIcon('less.png'))
            self.__showHideButton.setToolTip("Hide frames list")

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

            Settings().showStackViewer = True
        return

    def clear(self):
        " Clears the content "
        self.__framesList.clear()
        self.currentStack = None
        self.__stackLabel.setText("Stack")
        return

    def __resizeColumns(self):
        " Resize the files list columns "
        self.__framesList.header().setStretchLastSection(True)
        self.__framesList.header().resizeSections(QHeaderView.ResizeToContents)
        self.__framesList.header().resizeSection(0, 22)
        self.__framesList.header().setResizeMode(0, QHeaderView.Fixed)
        return

    def populate(self, stack):
        " Sets the new call stack and selects the first item in it "
        self.clear()

        self.currentStack = stack
        self.currentFrame = 0
        frameNumber = 0
        for s in stack:
            if len(s) == 2:
                # This is when an exception comes
                funcName = ""
            else:
                funcName = s[2]
            item = StackFrameItem(s[0], s[1], funcName, frameNumber)
            self.__framesList.addTopLevelItem(item)
            frameNumber += 1
        self.__resizeColumns()
        self.__framesList.topLevelItem(0).setCurrent(True)
        self.__stackLabel.setText("Stack (total: " + str(len(stack)) + ")")
        return

    def getFrameNumber(self):
        " Provides the current frame number "
        return self.currentFrame

    def __onFrameClicked(self, item, column):
        " Triggered when a frame is clicked "
        if item.isCurrent():
            return

        # Hide the current indicator
        self.__framesList.topLevelItem(self.currentFrame).setCurrent(False)

        # Show the new indicator
        self.currentFrame = item.getFrameNumber()
        for index in xrange(self.__framesList.topLevelItemCount()):
            item = self.__framesList.topLevelItem(index)
            if item.getFrameNumber() == self.currentFrame:
                item.setCurrent(True)
        self.__debugger.remoteClientVariables(1, self.currentFrame)  # globals
        self.__debugger.remoteClientVariables(0, self.currentFrame)  # locals
        return

    def __onFrameDoubleClicked(self, item, column):
        " Triggered when a frame is double clicked "
        # The frame has been switched already because the double click
        # signal always comes after the single click one
        fileName = item.getFilename()
        lineNumber = item.getLineNumber()

        editorsManager = GlobalData().mainWindow.editorsManager()
        editorsManager.openFile(fileName, lineNumber)
        editor = editorsManager.currentWidget().getEditor()
        editor.gotoLine(lineNumber)
        editorsManager.currentWidget().setFocus()
        return

    def __showContextMenu(self, coord):
        " Shows the frames list context menu "
        self.__contextItem = self.__framesList.itemAt(coord)
        if self.__contextItem is not None:
            self.__setCurrentMenuItem.setEnabled(
                not self.__contextItem.isCurrent())
            self.__framesMenu.popup(QCursor.pos())
        return

    def __onSetCurrent(self):
        " Context menu item handler "
        self.__onFrameClicked(self.__contextItem, 0)
        return

    def __onSetCurrentAndJump(self):
        " Context menu item handler "
        self.__onFrameClicked(self.__contextItem, 0)
        self.__onFrameDoubleClicked(self.__contextItem, 0)
        return

    def switchControl(self, isInIDE):
        " Switches the UI depending where the control flow is "
        self.__framesList.setEnabled(isInIDE)
        return
class IgnoredExceptionsViewer( QWidget ):
    " Implements the client exceptions viewer for a debugger "

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

        self.__createPopupMenu()
        self.__createLayout()
        self.__ignored = []
        self.__currentItem = None

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

        if Settings().showIgnoredExcViewer == False:
            self.__onShowHide( True )
        return

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

    def __createLayout( self ):
        " 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.__excptLabel = QLabel( "Ignored exception types" )

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

        self.__showHideButton = QToolButton()
        self.__showHideButton.setAutoRaise( True )
        self.__showHideButton.setIcon( PixmapCache().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.__excptLabel )
        headerLayout.addSpacerItem( expandingSpacer )
        headerLayout.addWidget( self.__showHideButton )
        self.headerFrame.setLayout( headerLayout )

        self.exceptionsList = QTreeWidget( self )
        self.exceptionsList.setSortingEnabled( False )
        self.exceptionsList.setAlternatingRowColors( True )
        self.exceptionsList.setRootIsDecorated( False )
        self.exceptionsList.setItemsExpandable( True )
        self.exceptionsList.setUniformRowHeights( True )
        self.exceptionsList.setSelectionMode( QAbstractItemView.SingleSelection )
        self.exceptionsList.setSelectionBehavior( QAbstractItemView.SelectRows )
        self.exceptionsList.setItemDelegate( NoOutlineHeightDelegate( 4 ) )
        self.exceptionsList.setContextMenuPolicy( Qt.CustomContextMenu )

        self.exceptionsList.customContextMenuRequested.connect( self.__showContextMenu )
        self.exceptionsList.itemSelectionChanged.connect( self.__onSelectionChanged )
        self.exceptionsList.setHeaderLabels( [ "Exception type" ] )

        self.__excTypeEdit = QLineEdit()
        self.__excTypeEdit.setFixedHeight( 26 )
        self.__excTypeEdit.textChanged.connect( self.__onNewFilterChanged )
        self.__excTypeEdit.returnPressed.connect( self.__onAddExceptionFilter )
        self.__addButton = QPushButton( "Add" )
        # self.__addButton.setFocusPolicy( Qt.NoFocus )
        self.__addButton.setEnabled( False )
        self.__addButton.clicked.connect( self.__onAddExceptionFilter )

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

        self.__removeButton = QAction(
            PixmapCache().getIcon( 'delitem.png' ),
            "Remove selected exception type", self )
        self.__removeButton.triggered.connect( self.__onRemoveFromIgnore )
        self.__removeButton.setEnabled( False )

        fixedSpacer1 = QWidget()
        fixedSpacer1.setFixedWidth( 5 )

        self.__removeAllButton = QAction(
            PixmapCache().getIcon( 'ignexcptdelall.png' ),
            "Remove all the exception types", self )
        self.__removeAllButton.triggered.connect( self.__onRemoveAllFromIgnore )
        self.__removeAllButton.setEnabled( False )


        self.toolbar = QToolBar()
        self.toolbar.setOrientation( Qt.Horizontal )
        self.toolbar.setMovable( False )
        self.toolbar.setAllowedAreas( Qt.TopToolBarArea )
        self.toolbar.setIconSize( QSize( 16, 16 ) )
        self.toolbar.setFixedHeight( 28 )
        self.toolbar.setContentsMargins( 0, 0, 0, 0 )
        self.toolbar.addWidget( expandingSpacer2 )
        self.toolbar.addAction( self.__removeButton )
        self.toolbar.addWidget( fixedSpacer1 )
        self.toolbar.addAction( self.__removeAllButton )

        addLayout = QHBoxLayout()
        addLayout.setContentsMargins( 1, 1, 1, 1 )
        addLayout.setSpacing( 1 )
        addLayout.addWidget( self.__excTypeEdit )
        addLayout.addWidget( self.__addButton )

        verticalLayout.addWidget( self.headerFrame )
        verticalLayout.addWidget( self.toolbar )
        verticalLayout.addWidget( self.exceptionsList )
        verticalLayout.addLayout( addLayout )
        return

    def clear( self ):
        " Clears the content "
        self.exceptionsList.clear()
        self.__excTypeEdit.clear()
        self.__addButton.setEnabled( False )
        self.__ignored = []
        self.__currentItem = None
        self.__updateTitle()
        return

    def __onShowHide( self, startup = False ):
        " Triggered when show/hide button is clicked "
        if startup or self.exceptionsList.isVisible():
            self.exceptionsList.setVisible( False )
            self.__excTypeEdit.setVisible( False )
            self.__addButton.setVisible( False )
            self.__removeButton.setVisible( False )
            self.__removeAllButton.setVisible( False )
            self.__showHideButton.setIcon( PixmapCache().getIcon( 'more.png' ) )
            self.__showHideButton.setToolTip( "Show ignored exceptions list" )

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

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

            Settings().showIgnoredExcViewer = False
        else:
            self.exceptionsList.setVisible( True )
            self.__excTypeEdit.setVisible( True )
            self.__addButton.setVisible( True )
            self.__removeButton.setVisible( True )
            self.__removeAllButton.setVisible( True )
            self.__showHideButton.setIcon( PixmapCache().getIcon( 'less.png' ) )
            self.__showHideButton.setToolTip( "Hide ignored exceptions list" )

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

            Settings().showIgnoredExcViewer = True
        return

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

    def __showContextMenu( self, coord ):
        " Shows the frames list context menu "
        contextItem = self.exceptionsList.itemAt( coord )
        if contextItem is not None:
            self.__currentItem = contextItem
            self.__excptMenu.popup( QCursor.pos() )
        return

    def __updateTitle( self ):
        " Updates the section title "
        count = self.exceptionsList.topLevelItemCount()
        if count == 0:
            self.__excptLabel.setText( "Ignored exception types" )
        else:
            self.__excptLabel.setText( "Ignored exception types (total: " +
                                       str( count ) + ")" )
        self.__removeAllButton.setEnabled( count != 0 )
        return

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

        self.clear()
        project = GlobalData().project
        if project.isLoaded():
            self.__ignored = list( project.ignoredExcpt )
        else:
            self.__ignored = list( Settings().ignoredExceptions )

        for exceptionType in self.__ignored:
            item = QTreeWidgetItem( self.exceptionsList )
            item.setText( 0, exceptionType )
        self.__updateTitle()
        return

    def __onNewFilterChanged( self, text ):
        " Triggered when the text is changed "
        text = str( text ).strip()
        if text == "":
            self.__addButton.setEnabled( False )
            return
        if " " in text:
            self.__addButton.setEnabled( False )
            return

        if text in self.__ignored:
            self.__addButton.setEnabled( False )
            return

        self.__addButton.setEnabled( True )
        return

    def __onAddExceptionFilter( self ):
        " Adds an item into the ignored exceptions list "
        text = self.__excTypeEdit.text().strip()
        self.addExceptionFilter( text )

    def addExceptionFilter( self, excType ):
        " Adds a new item into the ignored exceptions list "
        if excType == "":
            return
        if " " in excType:
            return
        if excType in self.__ignored:
            return

        item = QTreeWidgetItem( self.exceptionsList )
        item.setText( 0, excType )

        project = GlobalData().project
        if project.isLoaded():
            project.addExceptionFilter( excType )
        else:
            Settings().addExceptionFilter( excType )
        self.__ignored.append( excType )
        self.__updateTitle()
        return


    def __onRemoveFromIgnore( self ):
        " Removes an item from the ignored exception types list "
        if self.__currentItem is None:
            return

        text = self.__currentItem.text( 0 )

        # Find the item index and remove it
        index = 0
        while True:
            if self.exceptionsList.topLevelItem( index ).text( 0 ) == text:
                self.exceptionsList.takeTopLevelItem( index )
                break
            index += 1

        project = GlobalData().project
        if project.isLoaded():
            project.deleteExceptionFilter( text )
        else:
            Settings().deleteExceptionFilter( text )
        self.__ignored.remove( text )
        self.__updateTitle()
        return

    def __onRemoveAllFromIgnore( self ):
        " Triggered when all the ignored exceptions should be deleted "
        self.clear()

        project = GlobalData().project
        if project.isLoaded():
            project.setExceptionFilters( [] )
        else:
            Settings().setExceptionFilters( [] )
        return

    def isIgnored( self, exceptionType ):
        " Returns True if this exception type should be ignored "
        return exceptionType in self.__ignored