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