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 __createStackedViews(self): """Creates the graphics view""" self.smartViews = QStackedWidget(self) self.smartViews.setContentsMargins(0, 0, 0, 0) self.smartViews.addWidget(CFGraphicsView(self.__navBar, self)) self.smartViews.addWidget(CFGraphicsView(self.__navBar, self)) return self.smartViews
class FlowUIWidget(QWidget): """The widget which goes along with the text editor""" def __init__(self, editor, parent): QWidget.__init__(self, parent) # It is always not visible at the beginning because there is no # editor content at the start self.setVisible(False) self.__editor = editor self.__parentWidget = parent self.__connected = False self.__needPathUpdate = False self.cflowSettings = getCflowSettings(self) self.__displayProps = (self.cflowSettings.hidedocstrings, self.cflowSettings.hidecomments, self.cflowSettings.hideexcepts, Settings()['smartZoom']) hLayout = QHBoxLayout() hLayout.setContentsMargins(0, 0, 0, 0) hLayout.setSpacing(0) vLayout = QVBoxLayout() vLayout.setContentsMargins(0, 0, 0, 0) vLayout.setSpacing(0) # Make pylint happy self.__toolbar = None self.__navBar = None self.__cf = None self.__canvas = None self.__validGroups = [] self.__allGroupId = set() # Create the update timer self.__updateTimer = QTimer(self) self.__updateTimer.setSingleShot(True) self.__updateTimer.timeout.connect(self.process) vLayout.addWidget(self.__createNavigationBar()) vLayout.addWidget(self.__createStackedViews()) hLayout.addLayout(vLayout) hLayout.addWidget(self.__createToolbar()) self.setLayout(hLayout) self.updateSettings() # Connect to the change file type signal self.__mainWindow = GlobalData().mainWindow editorsManager = self.__mainWindow.editorsManagerWidget.editorsManager editorsManager.sigFileTypeChanged.connect(self.__onFileTypeChanged) Settings().sigHideDocstringsChanged.connect( self.__onHideDocstringsChanged) Settings().sigHideCommentsChanged.connect(self.__onHideCommentsChanged) Settings().sigHideExceptsChanged.connect(self.__onHideExceptsChanged) Settings().sigSmartZoomChanged.connect(self.__onSmartZoomChanged) self.setSmartZoomLevel(Settings()['smartZoom']) def getParentWidget(self): return self.__parentWidget def view(self): """Provides a reference to the current view""" return self.smartViews.currentWidget() def scene(self): """Provides a reference to the current scene""" return self.view().scene def __createToolbar(self): """Creates the toolbar""" self.__toolbar = QToolBar(self) self.__toolbar.setOrientation(Qt.Vertical) self.__toolbar.setMovable(False) self.__toolbar.setAllowedAreas(Qt.RightToolBarArea) self.__toolbar.setIconSize(QSize(16, 16)) self.__toolbar.setFixedWidth(30) self.__toolbar.setContentsMargins(0, 0, 0, 0) # Buttons saveAsMenu = QMenu(self) saveAsSVGAct = saveAsMenu.addAction(getIcon('filesvg.png'), 'Save as SVG...') saveAsSVGAct.triggered.connect(self.onSaveAsSVG) saveAsPDFAct = saveAsMenu.addAction(getIcon('filepdf.png'), 'Save as PDF...') saveAsPDFAct.triggered.connect(self.onSaveAsPDF) saveAsPNGAct = saveAsMenu.addAction(getIcon('filepixmap.png'), 'Save as PNG...') saveAsPNGAct.triggered.connect(self.onSaveAsPNG) saveAsMenu.addSeparator() saveAsCopyToClipboardAct = saveAsMenu.addAction( getIcon('copymenu.png'), 'Copy to clipboard') saveAsCopyToClipboardAct.triggered.connect(self.copyToClipboard) self.__saveAsButton = QToolButton(self) self.__saveAsButton.setIcon(getIcon('saveasmenu.png')) self.__saveAsButton.setToolTip('Save as') self.__saveAsButton.setPopupMode(QToolButton.InstantPopup) self.__saveAsButton.setMenu(saveAsMenu) self.__saveAsButton.setFocusPolicy(Qt.NoFocus) self.__levelUpButton = QToolButton(self) self.__levelUpButton.setFocusPolicy(Qt.NoFocus) self.__levelUpButton.setIcon(getIcon('levelup.png')) self.__levelUpButton.setToolTip('Smart zoom level up (Shift+wheel)') self.__levelUpButton.clicked.connect(self.onSmartZoomLevelUp) self.__levelIndicator = QLabel('<b>0</b>', self) self.__levelIndicator.setAlignment(Qt.AlignCenter) self.__levelDownButton = QToolButton(self) self.__levelDownButton.setFocusPolicy(Qt.NoFocus) self.__levelDownButton.setIcon(getIcon('leveldown.png')) self.__levelDownButton.setToolTip('Smart zoom level down (Shift+wheel)') self.__levelDownButton.clicked.connect(self.onSmartZoomLevelDown) fixedSpacer = QWidget() fixedSpacer.setFixedHeight(10) self.__hideDocstrings = QToolButton(self) self.__hideDocstrings.setCheckable(True) self.__hideDocstrings.setIcon(getIcon('hidedocstrings.png')) self.__hideDocstrings.setToolTip('Show/hide docstrings') self.__hideDocstrings.setFocusPolicy(Qt.NoFocus) self.__hideDocstrings.setChecked(Settings()['hidedocstrings']) self.__hideDocstrings.clicked.connect(self.__onHideDocstrings) self.__hideComments = QToolButton(self) self.__hideComments.setCheckable(True) self.__hideComments.setIcon(getIcon('hidecomments.png')) self.__hideComments.setToolTip('Show/hide comments') self.__hideComments.setFocusPolicy(Qt.NoFocus) self.__hideComments.setChecked(Settings()['hidecomments']) self.__hideComments.clicked.connect(self.__onHideComments) self.__hideExcepts = QToolButton(self) self.__hideExcepts.setCheckable(True) self.__hideExcepts.setIcon(getIcon('hideexcepts.png')) self.__hideExcepts.setToolTip('Show/hide except blocks') self.__hideExcepts.setFocusPolicy(Qt.NoFocus) self.__hideExcepts.setChecked(Settings()['hideexcepts']) self.__hideExcepts.clicked.connect(self.__onHideExcepts) spacer = QWidget() spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.__toolbar.addWidget(self.__saveAsButton) self.__toolbar.addWidget(spacer) self.__toolbar.addWidget(self.__levelUpButton) self.__toolbar.addWidget(self.__levelIndicator) self.__toolbar.addWidget(self.__levelDownButton) self.__toolbar.addWidget(fixedSpacer) self.__toolbar.addWidget(self.__hideDocstrings) self.__toolbar.addWidget(self.__hideComments) self.__toolbar.addWidget(self.__hideExcepts) return self.__toolbar def __createNavigationBar(self): """Creates the navigation bar""" self.__navBar = ControlFlowNavigationBar(self) return self.__navBar def __createStackedViews(self): """Creates the graphics view""" self.smartViews = QStackedWidget(self) self.smartViews.setContentsMargins(0, 0, 0, 0) self.smartViews.addWidget(CFGraphicsView(self.__navBar, self)) self.smartViews.addWidget(CFGraphicsView(self.__navBar, self)) return self.smartViews def process(self): """Parses the content and displays the results""" if not self.__connected: self.__connectEditorSignals() start = timer() cf = getControlFlowFromMemory(self.__editor.text) end = timer() if cf.errors: self.__navBar.updateInfoIcon(self.__navBar.STATE_BROKEN_UTD) errors = [] for err in cf.errors: if err[0] == -1 and err[1] == -1: errors.append(err[2]) elif err[1] == -1: errors.append('[' + str(err[0]) + ':] ' + err[2]) elif err[0] == -1: errors.append('[:' + str(err[1]) + '] ' + err[2]) else: errors.append('[' + str(err[0]) + ':' + str(err[1]) + '] ' + err[2]) self.__navBar.setErrors(errors) return self.__cf = cf if self.isDebugMode(): logging.info('Parsed file: %s', formatFlow(str(self.__cf))) logging.info('Parse timing: %f', end - start) # Collect warnings (parser + CML warnings) and valid groups self.__validGroups = [] self.__allGroupId = set() allWarnings = self.__cf.warnings + \ CMLVersion.validateCMLComments(self.__cf, self.__validGroups, self.__allGroupId) # That will clear the error tooltip as well self.__navBar.updateInfoIcon(self.__navBar.STATE_OK_UTD) if allWarnings: warnings = [] for warn in allWarnings: if warn[0] == -1 and warn[1] == -1: warnings.append(warn[2]) elif warn[1] == -1: warnings.append('[' + str(warn[0]) + ':] ' + warn[2]) elif warn[0] == -1: warnings.append('[:' + str(warn[1]) + '] ' + warn[2]) else: warnings.append('[' + str(warn[0]) + ':' + str(warn[1]) + '] ' + warn[2]) self.__navBar.setWarnings(warnings) else: self.__navBar.clearWarnings() self.redrawScene() def __cleanupCanvas(self): """Cleans up the canvas""" if self.__canvas is not None: self.__canvas.cleanup() self.__canvas = None for item in self.scene().items(): item.cleanup() self.scene().clear() def redrawScene(self): """Redraws the scene""" smartZoomLevel = Settings()['smartZoom'] self.cflowSettings = getCflowSettings(self) if self.dirty(): self.__displayProps = (self.cflowSettings.hidedocstrings, self.cflowSettings.hidecomments, self.cflowSettings.hideexcepts, smartZoomLevel) self.cflowSettings.itemID = 0 self.cflowSettings = tweakSmartSettings(self.cflowSettings, smartZoomLevel) try: fileName = self.__parentWidget.getFileName() if not fileName: fileName = self.__parentWidget.getShortName() collapsedGroups = getCollapsedGroups(fileName) # Top level canvas has no adress and no parent canvas self.__cleanupCanvas() self.__canvas = VirtualCanvas(self.cflowSettings, None, None, self.__validGroups, collapsedGroups, None) lStart = timer() self.__canvas.layoutModule(self.__cf) lEnd = timer() self.__canvas.setEditor(self.__editor) width, height = self.__canvas.render() rEnd = timer() self.scene().setSceneRect(0, 0, width, height) self.__canvas.draw(self.scene(), 0, 0) dEnd = timer() if self.isDebugMode(): logging.info('Redrawing is done. Size: %d x %d', width, height) logging.info('Layout timing: %f', lEnd - lStart) logging.info('Render timing: %f', rEnd - lEnd) logging.info('Draw timing: %f', dEnd - rEnd) except Exception as exc: logging.error(str(exc)) raise def onFlowZoomChanged(self): """Triggered when a flow zoom is changed""" if self.__cf: selection = self.scene().serializeSelection() firstOnScreen = self.scene().getFirstLogicalItem() self.cflowSettings.onFlowZoomChanged() self.redrawScene() self.updateNavigationToolbar('') self.scene().restoreSelectionByID(selection) self.__restoreScroll(firstOnScreen) def __onFileTypeChanged(self, fileName, uuid, newFileType): """Triggered when a buffer content type has changed""" if self.__parentWidget.getUUID() != uuid: return if not isPythonMime(newFileType): self.__disconnectEditorSignals() self.__updateTimer.stop() self.__cleanupCanvas() self.__cf = None self.__validGroups = [] self.setVisible(False) self.__navBar.updateInfoIcon(self.__navBar.STATE_UNKNOWN) return # Update the bar and show it self.setVisible(True) self.process() # The buffer type change event comes when the content is loaded first # time. So this is a good point to restore the position _, _, _, cflowHPos, cflowVPos = getFilePosition(fileName) self.setScrollbarPositions(cflowHPos, cflowVPos) def terminate(self): """Called when a tab is closed""" if self.__updateTimer.isActive(): self.__updateTimer.stop() self.__updateTimer.deleteLater() self.__disconnectEditorSignals() self.__mainWindow = GlobalData().mainWindow editorsManager = self.__mainWindow.editorsManagerWidget.editorsManager editorsManager.sigFileTypeChanged.disconnect(self.__onFileTypeChanged) Settings().sigHideDocstringsChanged.disconnect( self.__onHideDocstringsChanged) Settings().sigHideCommentsChanged.disconnect(self.__onHideCommentsChanged) Settings().sigHideExceptsChanged.disconnect(self.__onHideExceptsChanged) Settings().sigSmartZoomChanged.disconnect(self.__onSmartZoomChanged) # Helps GC to collect more self.__cleanupCanvas() for index in range(self.smartViews.count()): self.smartViews.widget(index).terminate() self.smartViews.widget(index).deleteLater() self.smartViews.deleteLater() self.__navBar.deleteLater() self.__cf = None self.__saveAsButton.menu().deleteLater() self.__saveAsButton.deleteLater() self.__levelUpButton.clicked.disconnect(self.onSmartZoomLevelUp) self.__levelUpButton.deleteLater() self.__levelDownButton.clicked.disconnect(self.onSmartZoomLevelDown) self.__levelDownButton.deleteLater() self.__hideDocstrings.clicked.disconnect(self.__onHideDocstrings) self.__hideDocstrings.deleteLater() self.__hideComments.clicked.disconnect(self.__onHideComments) self.__hideComments.deleteLater() self.__hideExcepts.clicked.disconnect(self.__onHideExcepts) self.__hideExcepts.deleteLater() self.__toolbar.deleteLater() self.__editor = None self.__parentWidget = None self.cflowSettings = None self.__displayProps = None def __connectEditorSignals(self): """When it is a python file - connect to the editor signals""" if not self.__connected: self.__editor.cursorPositionChanged.connect( self.__cursorPositionChanged) self.__editor.textChanged.connect(self.__onBufferChanged) self.__connected = True def __disconnectEditorSignals(self): """Disconnect the editor signals when the file is not a python one""" if self.__connected: self.__editor.cursorPositionChanged.disconnect( self.__cursorPositionChanged) self.__editor.textChanged.disconnect(self.__onBufferChanged) self.__connected = False def __cursorPositionChanged(self): """Cursor position changed""" # The timer should be reset only in case if the redrawing was delayed if self.__updateTimer.isActive(): self.__updateTimer.stop() self.__updateTimer.start(IDLE_TIMEOUT) def __onBufferChanged(self): """Triggered to update status icon and to restart the timer""" self.__updateTimer.stop() if self.__navBar.getCurrentState() in [self.__navBar.STATE_OK_UTD, self.__navBar.STATE_OK_CHN, self.__navBar.STATE_UNKNOWN]: self.__navBar.updateInfoIcon(self.__navBar.STATE_OK_CHN) else: self.__navBar.updateInfoIcon(self.__navBar.STATE_BROKEN_CHN) self.__updateTimer.start(IDLE_TIMEOUT) def redrawNow(self): """Redraw the diagram regardless of the timer""" if self.__updateTimer.isActive(): self.__updateTimer.stop() self.process() def generateNewGroupId(self): """Generates a new group ID (string)""" # It can also consider the current set of the groups: valid + invalid # and generate an integer id which is shorter for vacantGroupId in range(1000): groupId = str(vacantGroupId) if not groupId in self.__allGroupId: return groupId # Last resort return str(uuid.uuid1()) def updateNavigationToolbar(self, text): """Updates the toolbar text""" if self.__needPathUpdate: self.__navBar.setPath(text) def updateSettings(self): """Updates settings""" self.__needPathUpdate = Settings()['showCFNavigationBar'] self.__navBar.setPathVisible(self.__needPathUpdate) self.__navBar.setPath('') def highlightAtAbsPos(self, absPos, line, pos): """Scrolls the view to the item closest to absPos and selects it. line and pos are 1-based """ item, _ = self.scene().getNearestItem(absPos, line, pos) if item: GlobalData().mainWindow.setFocusToFloatingRenderer() self.scene().clearSelection() item.setSelected(True) self.view().scrollTo(item) self.setFocus() def setFocus(self): """Sets the focus""" self.view().setFocus() @staticmethod def __getDefaultSaveDir(): """Provides the default directory to save files to""" project = GlobalData().project if project.isLoaded(): return project.getProjectDir() return QDir.currentPath() def __selectFile(self, extension): """Picks a file of a certain extension""" dialog = QFileDialog(self, 'Save flowchart as') dialog.setFileMode(QFileDialog.AnyFile) dialog.setLabelText(QFileDialog.Accept, "Save") dialog.setNameFilter(extension.upper() + " files (*." + extension.lower() + ")") urls = [] for dname in QDir.drives(): urls.append(QUrl.fromLocalFile(dname.absoluteFilePath())) urls.append(QUrl.fromLocalFile(QDir.homePath())) project = GlobalData().project if project.isLoaded(): urls.append(QUrl.fromLocalFile(project.getProjectDir())) dialog.setSidebarUrls(urls) suggestedFName = self.__parentWidget.getFileName() if '.' in suggestedFName: dotIndex = suggestedFName.rindex('.') suggestedFName = suggestedFName[:dotIndex] dialog.setDirectory(self.__getDefaultSaveDir()) dialog.selectFile(suggestedFName + "." + extension.lower()) dialog.setOption(QFileDialog.DontConfirmOverwrite, False) dialog.setOption(QFileDialog.DontUseNativeDialog, True) if dialog.exec_() != QDialog.Accepted: return None fileNames = dialog.selectedFiles() fileName = os.path.abspath(str(fileNames[0])) if os.path.isdir(fileName): logging.error("A file must be selected") return None if "." not in fileName: fileName += "." + extension.lower() # Check permissions to write into the file or to a directory if os.path.exists(fileName): # Check write permissions for the file if not os.access(fileName, os.W_OK): logging.error("There is no write permissions for " + fileName) return None else: # Check write permissions to the directory dirName = os.path.dirname(fileName) if not os.access(dirName, os.W_OK): logging.error("There is no write permissions for the " "directory " + dirName) return None if os.path.exists(fileName): res = QMessageBox.warning( self, "Save flowchart as", "<p>The file <b>" + fileName + "</b> already exists.</p>", QMessageBox.StandardButtons(QMessageBox.Abort | QMessageBox.Save), QMessageBox.Abort) if res == QMessageBox.Abort or res == QMessageBox.Cancel: return None # All prerequisites are checked, return a file name return fileName def onSaveAsSVG(self): """Triggered on the 'Save as SVG' button""" fileName = self.__selectFile("svg") if fileName is None: return False try: self.__saveAsSVG(fileName) except Exception as excpt: logging.error(str(excpt)) return False return True def __saveAsSVG(self, fileName): """Saves the flowchart as an SVG file""" generator = QSvgGenerator() generator.setFileName(fileName) generator.setSize(QSize(self.scene().width(), self.scene().height())) painter = QPainter(generator) self.scene().render(painter) painter.end() def onSaveAsPDF(self): """Triggered on the 'Save as PDF' button""" fileName = self.__selectFile("pdf") if fileName is None: return False try: self.__saveAsPDF(fileName) except Exception as excpt: logging.error(str(excpt)) return False return True def __saveAsPDF(self, fileName): """Saves the flowchart as an PDF file""" printer = QPrinter() printer.setOutputFormat(QPrinter.PdfFormat) printer.setPaperSize(QSizeF(self.scene().width(), self.scene().height()), QPrinter.Point) printer.setFullPage(True) printer.setOutputFileName(fileName) painter = QPainter(printer) self.scene().render(painter) painter.end() def onSaveAsPNG(self): """Triggered on the 'Save as PNG' button""" fileName = self.__selectFile("png") if fileName is None: return False try: self.__saveAsPNG(fileName) except Exception as excpt: logging.error(str(excpt)) return False return True def __getPNG(self): """Renders the scene as PNG""" image = QImage(self.scene().width(), self.scene().height(), QImage.Format_ARGB32_Premultiplied) painter = QPainter(image) # It seems that the better results are without antialiasing # painter.setRenderHint( QPainter.Antialiasing ) self.scene().render(painter) painter.end() return image def __saveAsPNG(self, fileName): """Saves the flowchart as an PNG file""" image = self.__getPNG() image.save(fileName, "PNG") def copyToClipboard(self): """Copies the rendered scene to the clipboard as an image""" image = self.__getPNG() clip = QApplication.clipboard() clip.setImage(image) def getScrollbarPositions(self): """Provides the scrollbar positions""" hScrollBar = self.view().horizontalScrollBar() vScrollBar = self.view().verticalScrollBar() return hScrollBar.value(), vScrollBar.value() def setScrollbarPositions(self, hPos, vPos): """Sets the scrollbar positions for the view""" self.view().horizontalScrollBar().setValue(hPos) self.view().verticalScrollBar().setValue(vPos) def __onHideDocstrings(self): """Triggered when a hide docstring button is pressed""" Settings()['hidedocstrings'] = not Settings()['hidedocstrings'] def __onHideDocstringsChanged(self): """Signalled by settings""" selection = self.scene().serializeSelection() firstOnScreen = self.scene().getFirstLogicalItem() settings = Settings() self.__hideDocstrings.setChecked(settings['hidedocstrings']) if self.__checkNeedRedraw(): self.scene().restoreSelectionByID(selection) self.__restoreScroll(firstOnScreen) def __onHideComments(self): """Triggered when a hide comments button is pressed""" Settings()['hidecomments'] = not Settings()['hidecomments'] def __onHideCommentsChanged(self): """Signalled by settings""" selection = self.scene().serializeSelection() firstOnScreen = self.scene().getFirstLogicalItem() settings = Settings() self.__hideComments.setChecked(settings['hidecomments']) if self.__checkNeedRedraw(): self.scene().restoreSelectionByID(selection) self.__restoreScroll(firstOnScreen) def __onHideExcepts(self): """Triggered when a hide except blocks button is pressed""" Settings()['hideexcepts'] = not Settings()['hideexcepts'] def __onHideExceptsChanged(self): """Signalled by settings""" selection = self.scene().serializeSelection() firstOnScreen = self.scene().getFirstLogicalItem() settings = Settings() self.__hideExcepts.setChecked(settings['hideexcepts']) if self.__checkNeedRedraw(): self.scene().restoreSelectionByTooltip(selection) self.__restoreScroll(firstOnScreen) def __checkNeedRedraw(self): """Redraws the scene if necessary when a display setting is changed""" editorsManager = self.__mainWindow.editorsManagerWidget.editorsManager if self.__parentWidget == editorsManager.currentWidget(): self.updateNavigationToolbar('') self.process() return True return False def dirty(self): """True if some other tab has switched display settings""" settings = Settings() return self.__displayProps[0] != settings['hidedocstrings'] or \ self.__displayProps[1] != settings['hidecomments'] or \ self.__displayProps[2] != settings['hideexcepts'] or \ self.__displayProps[3] != settings['smartZoom'] def onSmartZoomLevelUp(self): """Triggered when an upper smart zoom level was requested""" Settings().onSmartZoomIn() def onSmartZoomLevelDown(self): """Triggered when an lower smart zoom level was requested""" Settings().onSmartZoomOut() def setSmartZoomLevel(self, smartZoomLevel): """Sets the new smart zoom level""" maxSmartZoom = Settings().MAX_SMART_ZOOM if smartZoomLevel < 0 or smartZoomLevel > maxSmartZoom: return self.__levelIndicator.setText('<b>' + str(smartZoomLevel) + '</b>') self.__levelIndicator.setToolTip( getSmartZoomDescription(smartZoomLevel)) self.__levelUpButton.setEnabled(smartZoomLevel < maxSmartZoom) self.__levelDownButton.setEnabled(smartZoomLevel > 0) self.smartViews.setCurrentIndex(smartZoomLevel) def __onSmartZoomChanged(self): """Triggered when a smart zoom changed""" selection = self.scene().serializeSelection() firstOnScreen = self.scene().getFirstLogicalItem() self.setSmartZoomLevel(Settings()['smartZoom']) if self.__checkNeedRedraw(): self.scene().restoreSelectionByTooltip(selection) self.__restoreScroll(firstOnScreen) def __restoreScroll(self, toItem): """Restores the view scrolling to the best possible position""" if toItem: lineRange = toItem.getLineRange() absPosRange = toItem.getAbsPosRange() item, _ = self.scene().getNearestItem(absPosRange[0], lineRange[0], 0) if item: self.view().scrollTo(item, True) self.view().horizontalScrollBar().setValue(0) def validateCollapsedGroups(self, fileName): """Checks that there are no collapsed groups which are invalid""" if self.__navBar.getCurrentState() != self.__navBar.STATE_OK_UTD: return collapsedGroups = getCollapsedGroups(fileName) if collapsedGroups: toBeDeleted = [] for groupId in collapsedGroups: for validId, start, end in self.__validGroups: del start del end if validId == groupId: break else: toBeDeleted.append(groupId) if toBeDeleted: for groupId in toBeDeleted: collapsedGroups.remove(groupId) setCollapsedGroups(fileName, collapsedGroups) else: setCollapsedGroups(fileName, []) def getDocItemByAnchor(self, anchor): """Provides the graphics item for the given anchor if so""" return self.scene().getDocItemByAnchor(anchor) @staticmethod def isDebugMode(): """True if it is a debug mode""" return GlobalData().skin['debug']
class 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"""