Ejemplo n.º 1
0
class GroupTitlePopup(QFrame):
    """Frameless panel to show the group title"""
    def __init__(self, parent):
        QFrame.__init__(self, parent)

        self.setWindowFlags(Qt.SplashScreen | Qt.WindowStaysOnTopHint
                            | Qt.X11BypassWindowManagerHint)

        self.setFrameShape(QFrame.StyledPanel)
        self.setLineWidth(1)

        self.__titleLabel = None
        self.__createLayout()

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

        self.__titleLabel = QLabel()
        self.__titleLabel.setAutoFillBackground(True)
        self.__titleLabel.setFrameShape(QFrame.StyledPanel)
        self.__titleLabel.setStyleSheet('padding: 2px')
        verticalLayout.addWidget(self.__titleLabel)

    def setTitleForGroup(self, group):
        """Sets the title of the group"""
        self.__titleLabel.setFont(group.canvas.settings.monoFont)
        self.__titleLabel.setText(group.getTitle())

    def resize(self, controlItem):
        """Moves the popup to the proper position"""
        # calculate the positions above the group
        # Taken from here:
        # https://stackoverflow.com/questions/9871749/find-screen-position-of-a-qgraphicsitem
        scene = controlItem.ref.scene()
        view = scene.views()[0]
        sceneP = controlItem.mapToScene(controlItem.boundingRect().topLeft())
        viewP = view.mapFromScene(sceneP)
        pos = view.viewport().mapToGlobal(viewP)

        self.move(pos.x(), pos.y() - self.height() - 2)
        QApplication.processEvents()

    def show(self, controlItem):
        """Shows the title above the group control"""
        # Use the palette from the group
        bgColor, fgColor, _ = controlItem.ref.getColors()
        palette = self.__titleLabel.palette()
        palette.setColor(QPalette.Background, bgColor)
        palette.setColor(QPalette.Foreground, fgColor)
        self.__titleLabel.setPalette(palette)

        # That's a trick: resizing works correctly only if the item is shown
        # So move it outside of the screen, show it so it is invisible and then
        # resize and move to the proper position
        screenHeight = GlobalData().screenHeight
        self.move(0, screenHeight + 128)
        QFrame.show(self)
        QApplication.processEvents()
        self.resize(controlItem)
Ejemplo n.º 2
0
class PylintResultViewer(QWidget):

    """Pylint results viewer"""

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

        self.__results = None
        self.__ide = ide
        self.__pluginHomeDir = pluginHomeDir

        self.__noneLabel = QLabel("\nNo results available")
        self.__noneLabel.setFrameShape(QFrame.StyledPanel)
        self.__noneLabel.setAlignment(Qt.AlignHCenter)
        font = self.__noneLabel.font()
        font.setPointSize(font.pointSize() + 4)
        self.__noneLabel.setFont(font)
        self.__noneLabel.setAutoFillBackground(True)
        noneLabelPalette = self.__noneLabel.palette()
        noneLabelPalette.setColor(QPalette.Background,
                                  GlobalData().skin['nolexerPaper'])
        self.__noneLabel.setPalette(noneLabelPalette)

        self.__createLayout(self.__pluginHomeDir)

    def __createLayout(self, pluginHomeDir):
        """Creates the layout"""
        self.clearButton = QAction(getIcon('trash.png'), 'Clear', self)
        self.clearButton.triggered.connect(self.clear)

        self.outputButton = QAction(QIcon(pluginHomeDir + 'output.png'),
                                    'Show pylint raw stdout and stderr', self)
        self.outputButton.triggered.connect(self.__showOutput)

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

        self.toolbar.addAction(self.outputButton)
        self.toolbar.addWidget(ToolBarExpandingSpacer(self.toolbar))
        self.toolbar.addAction(self.clearButton)

        self.__resultsTree = QTreeWidget(self)
        self.__resultsTree.setAlternatingRowColors(True)
        self.__resultsTree.setRootIsDecorated(True)
        self.__resultsTree.setItemsExpandable(True)
        self.__resultsTree.setUniformRowHeights(True)
        self.__resultsTree.setItemDelegate(NoOutlineHeightDelegate(4))
        headerLabels = ['Message type / line', 'id', 'Message']
        self.__resultsTree.setHeaderLabels(headerLabels)
        self.__resultsTree.itemActivated.connect(self.__resultActivated)

        self.__fileLabel = HeaderFitPathLabel(None, self)
        self.__fileLabel.setAlignment(Qt.AlignLeft)
        self.__fileLabel.setMinimumWidth(50)
        self.__fileLabel.setSizePolicy(QSizePolicy.Expanding,
                                       QSizePolicy.Fixed)
        self.__fileLabel.doubleClicked.connect(self.onPathLabelDoubleClick)
        self.__fileLabel.setContextMenuPolicy(Qt.CustomContextMenu)
        self.__fileLabel.customContextMenuRequested.connect(
            self.showPathLabelContextMenu)

        self.__rateLabel = HeaderLabel()
        self.__rateLabel.setToolTip('pylint analysis rate out of 10 '
                                    '(previous run if there was one)')
        self.__timestampLabel = HeaderLabel()
        self.__timestampLabel.setToolTip('pylint analysis timestamp')
        self.__labelLayout = QHBoxLayout()
        self.__labelLayout.setSpacing(4)
        self.__labelLayout.addWidget(self.__fileLabel)
        self.__labelLayout.addWidget(self.__rateLabel)
        self.__labelLayout.addWidget(self.__timestampLabel)

        self.__vLayout = QVBoxLayout()
        self.__vLayout.setSpacing(4)
        self.__vLayout.addLayout(self.__labelLayout)
        self.__vLayout.addWidget(self.__resultsTree)

        self.__hLayout = QHBoxLayout()
        self.__hLayout.setContentsMargins(0, 0, 0, 0)
        self.__hLayout.setSpacing(0)
        self.__hLayout.addWidget(self.toolbar)
        self.__hLayout.addWidget(self.__noneLabel)
        self.__hLayout.addLayout(self.__vLayout)

        self.setLayout(self.__hLayout)
        self.__updateButtons()

    def __updateButtons(self):
        """Updates the toolbar buttons approprietly"""
        self.clearButton.setEnabled(self.__results is not None)

        if self.__results is not None:
            stdout = self.__results.get('StdOut', None)
            stderr = self.__results.get('StdErr', None)
            self.outputButton.setEnabled(stdout is not None or
                                         stderr is not None)
        else:
            self.outputButton.setEnabled(False)

    def showResults(self, results):
        """Populates the analysis results"""
        self.clear()
        self.__noneLabel.setVisible(False)
        self.__fileLabel.setVisible(True)
        self.__rateLabel.setVisible(True)
        self.__timestampLabel.setVisible(True)
        self.__resultsTree.setVisible(True)

        self.__results = results
        self.__updateButtons()

        tooltip = ' '.join(['pylint results for',
                            os.path.basename(results['FileName']),
                            'at',
                            results['Timestamp']])
        self.__ide.sideBars['bottom'].setTabToolTip('pylint', tooltip)

        self.__fileLabel.setPath(results['FileName'])
        if 'Rate' in results:
            text = str(results['Rate'])
            if 'PreviousRunRate' in results:
                text += ' (' + str(results['PreviousRunRate']) + ')'
            self.__rateLabel.setText(' ' + text + ' ')
        else:
            self.__rateLabel.setVisible(False)
        self.__timestampLabel.setText(results['Timestamp'])

        totalMessages = 0
        totalMessages += self.__populateMessages('Errors')
        totalMessages += self.__populateMessages('Warnings')
        totalMessages += self.__populateMessages('Refactoring')
        totalMessages += self.__populateMessages('Cosmetics')

        # Update the header with the total number of matches
        headerLabels = ['Message type / line', 'id',
                        'Message (total messages: ' + str(totalMessages) + ')']
        self.__resultsTree.setHeaderLabels(headerLabels)

        # Resizing
        self.__resultsTree.header().resizeSections(
            QHeaderView.ResizeToContents)

    def __populateMessages(self, title):
        """Populates the analysis messages"""
        count = len(self.__results[title[0]])
        if count > 0:
            suffix = '' if count == 1 else 's'
            messageTypeItem = MessageTypeTableItem(
                [title, '', '(' + str(count) + ' message' + suffix + ')'])
            self.__resultsTree.addTopLevelItem(messageTypeItem)
            for item in self.__results[title[0]]:
                columns = [str(item[1]), item[3], item[2]]
                messageItem = MessageTableItem(columns)
                messageTypeItem.addChild(messageItem)
            messageTypeItem.setExpanded(True)
        return count

    def clear(self):
        """Clears the results view"""
        self.__results = None
        self.__updateButtons()

        tooltip = 'No results available'
        self.__ide.sideBars['bottom'].setTabToolTip('pylint', tooltip)
        self.__noneLabel.setVisible(True)

        self.__fileLabel.setVisible(False)
        self.__rateLabel.setVisible(False)
        self.__timestampLabel.setVisible(False)
        self.__resultsTree.setVisible(False)
        self.__resultsTree.clear()

    def __resultActivated(self, item, column):
        """Handles the double click (or Enter) on a message"""
        del column  # unused argument
        if self.__results:
            if isinstance(item, MessageTableItem):
                fileName = self.__results['FileName']
                lineNumber = int(item.data(0, Qt.DisplayRole))
                self.__ide.mainWindow.openFile(fileName, lineNumber)

    def __showOutput(self):
        """Shows the analysis stdout and stderr"""
        if self.__results is None:
            return

        # Show a separate dialog
        PylintStdoutStderrViewer(self.__ide.mainWindow,
                                 self.__results).exec_()

    def onPathLabelDoubleClick(self):
        """Double click on the path label"""
        txt = self.__getPathLabelFilePath()
        if txt.lower():
            QApplication.clipboard().setText(txt)

    def __getPathLabelFilePath(self):
        """Provides undecorated path label content"""
        txt = str(self.__fileLabel.getPath())
        if txt.startswith('File: '):
            return txt.replace('File: ', '')
        return txt

    def showPathLabelContextMenu(self, pos):
        """Triggered when a context menu is requested for the path label"""
        contextMenu = QMenu(self)
        contextMenu.addAction(getIcon('copymenu.png'),
                              'Copy full path to clipboard (double click)',
                              self.onPathLabelDoubleClick)
        contextMenu.addSeparator()
        contextMenu.addAction(getIcon(''), 'Copy directory path to clipboard',
                              self.onCopyDirToClipboard)
        contextMenu.addAction(getIcon(''), 'Copy file name to clipboard',
                              self.onCopyFileNameToClipboard)
        contextMenu.popup(self.__fileLabel.mapToGlobal(pos))

    def onCopyDirToClipboard(self):
        """Copies the dir path of the current file into the clipboard"""
        txt = self.__getPathLabelFilePath()
        if txt.lower():
            try:
                QApplication.clipboard().setText(os.path.dirname(txt) +
                                                 os.path.sep)
            except:
                pass

    def onCopyFileNameToClipboard(self):
        """Copies the file name of the current file into the clipboard"""
        txt = self.__getPathLabelFilePath()
        if txt.lower():
            try:
                QApplication.clipboard().setText(os.path.basename(txt))
            except:
                pass
Ejemplo n.º 3
0
class SearchResultsViewer(QWidget):

    """Search results viewer tab widget"""

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

        self.__results = QStackedWidget(self)

        self.__bufferChangeConnected = False

        # Prepare members for reuse
        self.__noneLabel = QLabel("\nNo results available")

        self.__noneLabel.setFrameShape(QFrame.StyledPanel)
        self.__noneLabel.setAlignment(Qt.AlignHCenter)
        self.__headerFont = self.__noneLabel.font()
        self.__headerFont.setPointSize(self.__headerFont.pointSize() + 4)
        self.__noneLabel.setFont(self.__headerFont)
        self.__noneLabel.setAutoFillBackground(True)
        noneLabelPalette = self.__noneLabel.palette()
        noneLabelPalette.setColor(QPalette.Background,
                                  GlobalData().skin['nolexerPaper'])
        self.__noneLabel.setPalette(noneLabelPalette)

        self.__createLayout(parent)
        self.__updateButtonsStatus()

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

    def __createLayout(self, parent):
        """Creates the toolbar and layout"""
        del parent  # unused argument

        # Buttons etc.
        self.clearButton = QAction(getIcon('trash.png'),
                                   'Delete current results', self)
        self.clearButton.triggered.connect(self.__clear)

        self.prevButton = QAction(getIcon('1leftarrow.png'),
                                  'Previous results', self)
        self.prevButton.triggered.connect(self.__previous)

        self.nextButton = QAction(getIcon('1rightarrow.png'),
                                  'Next results', self)
        self.nextButton.triggered.connect(self.__next)

        self.doAgainButton = QAction(getIcon('searchagain.png'),
                                     'Do again', self)
        self.doAgainButton.triggered.connect(self.__doAgain)

        self.removeItemButton = QAction(getIcon('delitem.png'),
                                        'Remove currently selected item (Del)',
                                        self)
        self.removeItemButton.triggered.connect(self.__removeItem)

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

        self.toolbar.addAction(self.prevButton)
        self.toolbar.addAction(self.nextButton)
        self.toolbar.addAction(self.doAgainButton)
        self.toolbar.addWidget(ToolBarExpandingSpacer(self.toolbar))
        self.toolbar.addAction(self.removeItemButton)
        self.toolbar.addAction(self.clearButton)

        self.__hLayout = QHBoxLayout()
        self.__hLayout.setContentsMargins(0, 0, 0, 0)
        self.__hLayout.setSpacing(0)
        self.__hLayout.addWidget(self.toolbar)
        self.__hLayout.addWidget(self.__noneLabel)
        self.__hLayout.addWidget(self.__results)
        self.__results.hide()

        self.setLayout(self.__hLayout)

    def __isReportShown(self):
        """True is any of the reports is shown"""
        return self.__results.count() > 0

    def getResultsViewer(self):
        """Provides a reference to the results tree"""
        return self.__results

    def __updateButtonsStatus(self):
        """Updates the buttons status"""
        if self.__isReportShown():
            index = self.__results.currentIndex()
            widget = self.__results.currentWidget()
            self.prevButton.setEnabled(index > 0)
            self.nextButton.setEnabled(index < self.__results.count() - 1)
            self.doAgainButton.setEnabled(widget.canDoAgain())
            self.removeItemButton.setEnabled(widget.selectedItem is not None)
            self.clearButton.setEnabled(True)
        else:
            self.prevButton.setEnabled(False)
            self.nextButton.setEnabled(False)
            self.doAgainButton.setEnabled(False)
            self.removeItemButton.setEnabled(False)
            self.clearButton.setEnabled(False)

    def __updateIndicators(self):
        """Updates all the indicators"""
        total = self.__results.count()
        for index in range(total):
            self.__results.widget(index).updateIndicator(index + 1, total)

    def setFocus(self):
        """Overridden setFocus"""
        self.__hLayout.setFocus()

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

    def __connectBufferChange(self):
        """Connects the sigBufferChange"""
        if not self.__bufferChangeConnected:
            self.__bufferChangeConnected = True
            mainWindow = GlobalData().mainWindow
            editorsManager = mainWindow.editorsManagerWidget.editorsManager
            editorsManager.sigBufferModified.connect(self.onBufferModified)

    def __disconnectBufferChange(self):
        """Disconnects the sigBufferModified signal"""
        if self.__bufferChangeConnected:
            self.__bufferChangeConnected = False
            mainWindow = GlobalData().mainWindow
            editorsManager = mainWindow.editorsManagerWidget.editorsManager
            editorsManager.sigBufferModified.disconnect(self.onBufferModified)

    def onBufferModified(self, fileName, uuid):
        """Triggered when a buffer is modified"""
        for index in range(self.__results.count()):
            self.__results.widget(index).resultsTree.onBufferModified(
                fileName, uuid)

    def __previous(self):
        """Switch to the previous results"""
        if self.__isReportShown():
            index = self.__results.currentIndex()
            if index > 0:
                self.__results.setCurrentIndex(index - 1)
                self.__updateButtonsStatus()

    def __next(self):
        """Switch to the next results"""
        if self.__isReportShown():
            index = self.__results.currentIndex()
            if index < self.__results.count() - 1:
                self.__results.setCurrentIndex(index + 1)
                self.__updateButtonsStatus()

    def keyPressEvent(self, event):
        """Del key processing"""
        if event.key() == Qt.Key_Delete:
            event.accept()
            self.__removeItem()
        else:
            QWidget.keyPressEvent(self, event)

    def __removeItem(self):
        """Removes one entry of the search"""
        widget = self.__results.currentWidget()
        if widget.selectedItem is None:
            return

        widget.removeSelectedItem()
        if widget.resultsTree.topLevelItemCount() == 0:
            # The last result, need to remove the search result
            self.__clear()
        else:
            self.__updateButtonsStatus()

    def __clear(self):
        """Clears the content of the vertical layout"""
        if not self.__isReportShown():
            return

        index = self.__results.currentIndex()
        widget = self.__results.currentWidget()
        widget.clear()

        if self.__results.count() == 1:
            self.__results.hide()
            self.__noneLabel.show()
            self.__disconnectBufferChange()

        self.__results.removeWidget(widget)
        widget.deleteLater()

        if self.__results.count() > 0:
            if index >= self.__results.count():
                index -= 1
            self.__results.setCurrentIndex(index)

        self.__updateButtonsStatus()
        self.__updateIndicators()

    def __doAgain(self):
        """Performs the action once again"""
        if self.__isReportShown():
            self.__results.currentWidget().doAgain(self)

    def showReport(self, providerId, results, parameters, searchId=None):
        """Shows the find in files results"""
        # Memorize the screen width
        searchTooltip.setScreenWidth(
            GlobalData().application.desktop().screenGeometry().width())

        if searchId is None:
            resultWidget = ResultsViewerWidget(providerId, results,
                                               parameters,
                                               str(uuid1()), self)
            index = self.__results.addWidget(resultWidget)
        else:
            # Find the widget with this searchId
            found = False
            for index in range(self.__results.count()):
                if self.__results.widget(index).searchId == searchId:
                    found = True
                    break
            if not found:
                # add as a new one
                resultWidget = ResultsViewerWidget(providerId, results,
                                                   parameters,
                                                   str(uuid1()), self)
                index = self.__results.addWidget(resultWidget)
            else:
                # Repopulate it
                widget = self.__results.widget(index)
                widget.populate(results)

        self.__results.setCurrentIndex(index)

        # Show the complete information
        self.__noneLabel.hide()
        self.__results.show()

        self.__updateButtonsStatus()
        self.__updateIndicators()
        self.__connectBufferChange()

    def __populateForProject(self):
        """Load the project saved search results"""

    def __saveProjectResults(self):
        """Serialize to the disk the search results"""
Ejemplo n.º 4
0
class MatchTooltip(QFrame):

    """Custom tooltip"""

    def __init__(self):
        QFrame.__init__(self)

        # Avoid the border around the window
        self.setWindowFlags(Qt.SplashScreen |
                            Qt.WindowStaysOnTopHint |
                            Qt.X11BypassWindowManagerHint)

        # Make the frame nice looking
        self.setFrameShape(QFrame.StyledPanel)
        self.setLineWidth(2)

        self.__cellHeight = 20     # default
        self.__screenWidth = 600   # default

        # On slow network connections when XServer is used the cursor movement
        # is delivered with a considerable delay which causes improper tooltip
        # displaying. This global variable prevents improper displaying.
        self.__inside = False

        self.info = None
        self.location = None
        self.__createLayout()

        # The item the tooltip is for
        self.item = None

        # The timer which shows the tooltip. The timer is controlled from
        # outside of the class.
        self.tooltipTimer = QTimer(self)
        self.tooltipTimer.setSingleShot(True)
        self.tooltipTimer.timeout.connect(self.__onTimer)

        self.startPosition = None

    def setCellHeight(self, height):
        """Sets the cell height"""
        self.__cellHeight = height

    def setScreenWidth(self, width):
        """Sets the screen width"""
        self.__screenWidth = width

    def setInside(self, inside):
        """Sets the inside flag"""
        self.__inside = inside

    def isInside(self):
        """Provides the inside flag"""
        return self.__inside

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

        self.info = QLabel()
        self.info.setAutoFillBackground(True)
        self.info.setFont(getZoomedMonoFont())
        self.info.setFrameShape(QFrame.StyledPanel)
        self.info.setStyleSheet('padding: 4px')
        verticalLayout.addWidget(self.info)

        self.location = QLabel()
        # verticalLayout.addWidget(self.location)

    def setText(self, text):
        """Sets the tooltip text"""
        self.info.setFont(getZoomedMonoFont())
        self.info.setText(text)

    def setLocation(self, text):
        """Sets the file name and line at the bottom"""
        self.location.setText(text)

    def setModified(self, status):
        """Sets the required tooltip background"""
        palette = self.info.palette()
        if status:
            # Reddish
            palette.setColor(QPalette.Background, QColor(255, 227, 227))
        else:
            # Blueish
            palette.setColor(QPalette.Background, QColor(224, 236, 255))
        self.info.setPalette(palette)

    def setItem(self, item):
        """Sets the item the tooltip is shown for"""
        self.item = item

    def __getTooltipPos(self):
        """Calculates the tooltip position - above the row"""
        pos = QCursor.pos()
        if pos.x() + self.sizeHint().width() >= self.__screenWidth:
            pos.setX(self.__screenWidth - self.sizeHint().width() - 2)
        pos.setY(pos.y() - self.__cellHeight - 1 - self.sizeHint().height())
        return pos

    def __onTimer(self):
        """Triggered by the show tooltip timer"""
        currentPos = QCursor.pos()
        if abs(currentPos.x() - self.startPosition.x()) <= 2 and \
           abs(currentPos.y() - self.startPosition.y()) <= 2:
            # No movement since last time, show the tooltip
            self.show()
            return

        # There item has not been changed, but the position within it was
        # So restart the timer, but for shorter
        self.startPosition = currentPos
        self.tooltipTimer.start(400)

    def startShowTimer(self):
        """Memorizes the cursor position and starts the timer"""
        self.tooltipTimer.stop()
        self.startPosition = QCursor.pos()
        self.tooltipTimer.start(500)  # 0.5 sec

    def show(self):
        """Shows the tooltip at the proper position"""
        QToolTip.hideText()
        QApplication.processEvents()
        if self.__inside:
            self.move(self.__getTooltipPos())
            self.raise_()
            QFrame.show(self)