def getLabelStyle(owner): """Creates a label stylesheet for the given owner widget""" modelLabel = QLabel(owner) bgColor = modelLabel.palette().color(modelLabel.backgroundRole()) del modelLabel red = bgColor.red() green = bgColor.green() blue = bgColor.blue() delta = 60 borderColor = QColor(max(red - delta, 0), max(green - delta, 0), max(blue - delta, 0)) bgColor = QColor(min(red + delta, 255), min(green + delta, 255), min(blue + delta, 255)) props = [ 'border-radius: 3px', 'padding: 4px', 'background-color: ' + colorAsString(bgColor, True), 'border: 1px solid ' + colorAsString(borderColor, True) ] return '; '.join(props)
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)
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
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"""
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)