class MDWidget(QWidget): """The MD rendered content widget which goes along with the text editor""" sigEscapePressed = pyqtSignal() def __init__(self, editor, parent): QWidget.__init__(self, parent) self.setVisible(False) self.__editor = editor self.__parentWidget = parent self.__connected = False 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.__topBar = None # Create the update timer self.__updateTimer = QTimer(self) self.__updateTimer.setSingleShot(True) self.__updateTimer.timeout.connect(self.process) vLayout.addWidget(self.__createTopBar()) vLayout.addWidget(self.__createMDView()) hLayout.addLayout(vLayout) hLayout.addWidget(self.__createToolbar()) self.setLayout(hLayout) # Connect to the change file type signal self.__mainWindow = GlobalData().mainWindow editorsManager = self.__mainWindow.em editorsManager.sigFileTypeChanged.connect(self.__onFileTypeChanged) 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) # Some control buttons could be added later self.printButton = QAction(getIcon('printer.png'), 'Print', self) self.printButton.triggered.connect(self.__onPrint) self.__toolbar.addAction(self.printButton) return self.__toolbar def __createTopBar(self): """Creates the top bar""" self.__topBar = MDTopBar(self) return self.__topBar def __createMDView(self): """Creates the graphics view""" self.mdView = MDViewer(self) self.mdView.sigEscapePressed.connect(self.__onEsc) return self.mdView def __onPrint(self): """Print the markdown page""" dialog = QPrintDialog(self) if dialog.exec_() == QDialog.Accepted: printer = dialog.printer() self.mdView.print_(printer) def __onEsc(self): """Triggered when Esc is pressed""" self.sigEscapePressed.emit() def process(self): """Parses the content and displays the results""" if not self.__connected: self.__connectEditorSignals() renderedText, errors, warnings = renderMarkdown( self.getUUID(), self.__editor.text, self.getFileName()) if errors: self.__topBar.updateInfoIcon(self.__topBar.STATE_BROKEN_UTD) self.__topBar.setErrors(errors) return if renderedText is None: self.__topBar.updateInfoIcon(self.__topBar.STATE_BROKEN_UTD) self.__topBar.setErrors(['Unknown markdown rendering error']) return # That will clear the error tooltip as well self.__topBar.updateInfoIcon(self.__topBar.STATE_OK_UTD) if warnings: self.__topBar.setWarnings(warnings) else: self.__topBar.clearWarnings() hsbValue, vsbValue = self.getScrollbarPositions() self.mdView.setHtml(renderedText) self.setScrollbarPositions(hsbValue, vsbValue) def __onFileTypeChanged(self, fileName, uuid, newFileType): """Triggered when a buffer content type has changed""" if self.getUUID() != uuid: return if not isMarkdownMime(newFileType): self.__disconnectEditorSignals() self.__updateTimer.stop() self.setVisible(False) self.__topBar.updateInfoIcon(self.__topBar.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 _, _, _, hPos, vPos = getFilePosition(fileName) self.setScrollbarPositions(hPos, vPos) def terminate(self): """Called when a tab is to be closed""" self.mdView.terminate() self.mdView.deleteLater() if self.__updateTimer.isActive(): self.__updateTimer.stop() self.__updateTimer.deleteLater() self.__disconnectEditorSignals() editorsManager = self.__mainWindow.em editorsManager.sigFileTypeChanged.disconnect(self.__onFileTypeChanged) self.printButton.triggered.disconnect(self.__onPrint) self.printButton.deleteLater() self.__topBar.deleteLater() self.__toolbar.deleteLater() self.__editor = None self.__parentWidget = 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.__topBar.getCurrentState() in [ self.__topBar.STATE_OK_UTD, self.__topBar.STATE_OK_CHN, self.__topBar.STATE_UNKNOWN ]: self.__topBar.updateInfoIcon(self.__topBar.STATE_OK_CHN) else: self.__topBar.updateInfoIcon(self.__topBar.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 getScrollbarPositions(self): """Provides the scrollbar positions""" return self.mdView.getScrollbarPositions() def setScrollbarPositions(self, hPos, vPos): """Sets the scrollbar positions for the view""" self.mdView.setScrollbarPositions(hPos, vPos) def getFileName(self): return self.__parentWidget.getFileName() def getUUID(self): return self.__parentWidget.getUUID()
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 TextEditorTabWidget(QWidget): """Plain text editor tab widget""" sigReloadRequest = pyqtSignal() reloadAllNonModifiedRequest = pyqtSignal() sigTabRunChanged = pyqtSignal(bool) def __init__(self, parent, debugger): QWidget.__init__(self, parent) extendInstance(self, MainWindowTabWidgetBase) MainWindowTabWidgetBase.__init__(self) self.__navigationBar = None self.__editor = TextEditor(self, debugger) self.__fileName = "" self.__shortName = "" self.__createLayout() self.__editor.redoAvailable.connect(self.__redoAvailable) self.__editor.undoAvailable.connect(self.__undoAvailable) self.__editor.modificationChanged.connect(self.modificationChanged) self.__editor.sigCFlowSyncRequested.connect(self.cflowSyncRequested) self.__editor.languageChanged.connect(self.__languageChanged) self.__diskModTime = None self.__diskSize = None self.__reloadDlgShown = False self.__debugMode = False self.__vcsStatus = None def onTextZoomChanged(self): """Triggered when a text zoom is changed""" self.__editor.onTextZoomChanged() def onFlowZoomChanged(self): """Triggered when a flow zoom is changed""" self.__flowUI.onFlowZoomChanged() def getNavigationBar(self): """Provides a reference to the navigation bar""" return self.__navigationBar def shouldAcceptFocus(self): """True if it can accept the focus""" return self.__outsideChangesBar.isHidden() def readFile(self, fileName): """Reads the text from a file""" self.__editor.readFile(fileName) self.setFileName(fileName) self.__editor.restoreBreakpoints() # Memorize the modification date path = os.path.realpath(fileName) self.__diskModTime = os.path.getmtime(path) self.__diskSize = os.path.getsize(path) def writeFile(self, fileName): """Writes the text to a file""" if self.__editor.writeFile(fileName): # Memorize the modification date path = os.path.realpath(fileName) self.__diskModTime = os.path.getmtime(path) self.__diskSize = os.path.getsize(path) self.setFileName(fileName) self.__editor.restoreBreakpoints() return True return False def __createLayout(self): """Creates the toolbar and layout""" # 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 printButton = QAction(getIcon('printer.png'), 'Print (Ctrl+P)', self.toolbar) printButton.triggered.connect(self.__onPrint) printButton.setObjectName('printButton') printPreviewButton = QAction(getIcon('printpreview.png'), 'Print preview', self.toolbar) printPreviewButton.triggered.connect(self.__onPrintPreview) printPreviewButton.setEnabled(False) printPreviewButton.setVisible(False) printPreviewButton.setObjectName('printPreviewButton') printSpacer = QWidget(self.toolbar) printSpacer.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) printSpacer.setFixedHeight(8) printSpacer.setObjectName('printSpacer') # Imports diagram and its menu importsMenu = QMenu(self) self.importsDlgAct = importsMenu.addAction( getIcon('detailsdlg.png'), 'Fine tuned imports diagram') self.importsDlgAct.triggered.connect(self.onImportDgmTuned) self.importsDiagramButton = QToolButton(self.toolbar) self.importsDiagramButton.setIcon(getIcon('importsdiagram.png')) self.importsDiagramButton.setToolTip('Generate imports diagram') self.importsDiagramButton.setPopupMode(QToolButton.DelayedPopup) self.importsDiagramButton.setMenu(importsMenu) self.importsDiagramButton.setFocusPolicy(Qt.NoFocus) self.importsDiagramButton.clicked.connect(self.onImportDgm) self.importsDiagramButton.setEnabled(False) self.importsDiagramButton.setObjectName('importsDiagramButton') # Run script and its menu runScriptMenu = QMenu(self) self.runScriptDlgAct = runScriptMenu.addAction( getIcon('detailsdlg.png'), 'Set run/debug parameters') self.runScriptDlgAct.triggered.connect(self.onRunScriptDlg) self.runScriptButton = QToolButton(self.toolbar) self.runScriptButton.setIcon(getIcon('run.png')) self.runScriptButton.setToolTip('Run script') self.runScriptButton.setPopupMode(QToolButton.DelayedPopup) self.runScriptButton.setMenu(runScriptMenu) self.runScriptButton.setFocusPolicy(Qt.NoFocus) self.runScriptButton.clicked.connect(self.onRunScript) self.runScriptButton.setEnabled(False) self.runScriptButton.setObjectName('runScriptButton') # Profile script and its menu profileScriptMenu = QMenu(self) self.profileScriptDlgAct = profileScriptMenu.addAction( getIcon('detailsdlg.png'), 'Set profile parameters') self.profileScriptDlgAct.triggered.connect(self.onProfileScriptDlg) self.profileScriptButton = QToolButton(self.toolbar) self.profileScriptButton.setIcon(getIcon('profile.png')) self.profileScriptButton.setToolTip('Profile script') self.profileScriptButton.setPopupMode(QToolButton.DelayedPopup) self.profileScriptButton.setMenu(profileScriptMenu) self.profileScriptButton.setFocusPolicy(Qt.NoFocus) self.profileScriptButton.clicked.connect(self.onProfileScript) self.profileScriptButton.setEnabled(False) self.profileScriptButton.setObjectName('profileScriptButton') # Debug script and its menu debugScriptMenu = QMenu(self) self.debugScriptDlgAct = debugScriptMenu.addAction( getIcon('detailsdlg.png'), 'Set run/debug parameters') self.debugScriptDlgAct.triggered.connect(self.onDebugScriptDlg) self.debugScriptButton = QToolButton(self.toolbar) self.debugScriptButton.setIcon(getIcon('debugger.png')) self.debugScriptButton.setToolTip('Debug script') self.debugScriptButton.setPopupMode(QToolButton.DelayedPopup) self.debugScriptButton.setMenu(debugScriptMenu) self.debugScriptButton.setFocusPolicy(Qt.NoFocus) self.debugScriptButton.clicked.connect(self.onDebugScript) self.debugScriptButton.setEnabled(False) self.debugScriptButton.setObjectName('debugScriptButton') # Disassembling disasmScriptMenu = QMenu(self) disasmScriptMenu.addAction(getIcon(''), 'Disassembly (no optimization)', self.__editor._onDisasm0) disasmScriptMenu.addAction(getIcon(''), 'Disassembly (optimization level 1)', self.__editor._onDisasm1) disasmScriptMenu.addAction(getIcon(''), 'Disassembly (optimization level 2)', self.__editor._onDisasm2) self.disasmScriptButton = QToolButton(self.toolbar) self.disasmScriptButton.setIcon(getIcon('disassembly.png')) self.disasmScriptButton.setToolTip('Disassembly script') self.disasmScriptButton.setPopupMode(QToolButton.DelayedPopup) self.disasmScriptButton.setMenu(disasmScriptMenu) self.disasmScriptButton.setFocusPolicy(Qt.NoFocus) self.disasmScriptButton.clicked.connect(self.__editor._onDisasm0) self.disasmScriptButton.setEnabled(False) self.disasmScriptButton.setObjectName('disasmScriptButton') # Dead code self.deadCodeScriptButton = QAction(getIcon('deadcode.png'), 'Find dead code', self.toolbar) self.deadCodeScriptButton.triggered.connect(self.__onDeadCode) self.deadCodeScriptButton.setEnabled(False) self.deadCodeScriptButton.setObjectName('deadCodeScriptButton') undoSpacer = QWidget(self.toolbar) undoSpacer.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) undoSpacer.setFixedHeight(8) undoSpacer.setObjectName('undoSpacer') self.__undoButton = QAction(getIcon('undo.png'), 'Undo (Ctrl+Z)', self.toolbar) self.__undoButton.setShortcut('Ctrl+Z') self.__undoButton.triggered.connect(self.__editor.onUndo) self.__undoButton.setEnabled(False) self.__undoButton.setObjectName('undoButton') self.__redoButton = QAction(getIcon('redo.png'), 'Redo (Ctrl+Y)', self.toolbar) self.__redoButton.setShortcut('Ctrl+Y') self.__redoButton.triggered.connect(self.__editor.onRedo) self.__redoButton.setEnabled(False) self.__redoButton.setObjectName('redoButton') spacer = QWidget(self.toolbar) spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) spacer.setObjectName('spacer') self.removeTrailingSpacesButton = QAction(getIcon('trailingws.png'), 'Remove trailing spaces', self.toolbar) self.removeTrailingSpacesButton.triggered.connect( self.onRemoveTrailingWS) self.removeTrailingSpacesButton.setObjectName( 'removeTrailingSpacesButton') self.expandTabsButton = QAction(getIcon('expandtabs.png'), 'Expand tabs (4 spaces)', self.toolbar) self.expandTabsButton.triggered.connect(self.onExpandTabs) self.expandTabsButton.setObjectName('expandTabsButton') # Add items to the toolbar self.toolbar.addAction(printPreviewButton) self.toolbar.addAction(printButton) self.toolbar.addWidget(printSpacer) self.toolbar.addWidget(self.importsDiagramButton) self.toolbar.addWidget(self.runScriptButton) self.toolbar.addWidget(self.profileScriptButton) self.toolbar.addWidget(self.debugScriptButton) self.toolbar.addWidget(self.disasmScriptButton) self.toolbar.addAction(self.deadCodeScriptButton) self.toolbar.addWidget(undoSpacer) self.toolbar.addAction(self.__undoButton) self.toolbar.addAction(self.__redoButton) self.toolbar.addWidget(spacer) self.toolbar.addAction(self.removeTrailingSpacesButton) self.toolbar.addAction(self.expandTabsButton) self.importsBar = ImportListWidget(self.__editor) self.importsBar.hide() self.__outsideChangesBar = OutsideChangeWidget(self.__editor) self.__outsideChangesBar.sigReloadRequest.connect(self.__onReload) self.__outsideChangesBar.reloadAllNonModifiedRequest.connect( self.reloadAllNonModified) self.__outsideChangesBar.hide() hLayout = QHBoxLayout() hLayout.setContentsMargins(0, 0, 0, 0) hLayout.setSpacing(0) vLayout = QVBoxLayout() vLayout.setContentsMargins(0, 0, 0, 0) vLayout.setSpacing(0) self.__navigationBar = NavigationBar(self.__editor, self) vLayout.addWidget(self.__navigationBar) vLayout.addWidget(self.__editor) hLayout.addLayout(vLayout) hLayout.addWidget(self.toolbar) widget = QWidget(self) widget.setLayout(hLayout) self.__splitter = QSplitter(Qt.Horizontal, self) self.__flowUI = FlowUIWidget(self.__editor, self) self.__mdView = MDWidget(self.__editor, self) self.__renderLayout = QVBoxLayout() self.__renderLayout.setContentsMargins(0, 0, 0, 0) self.__renderLayout.setSpacing(0) self.__renderLayout.addWidget(self.__flowUI) self.__renderLayout.addWidget(self.__mdView) self.__renderWidget = QWidget(self) self.__renderWidget.setLayout(self.__renderLayout) self.__splitter.addWidget(widget) self.__splitter.addWidget(self.__renderWidget) containerLayout = QHBoxLayout() containerLayout.setContentsMargins(0, 0, 0, 0) containerLayout.setSpacing(0) containerLayout.addWidget(self.__splitter) self.setLayout(containerLayout) self.__renderWidget.setVisible(False) self.__splitter.setSizes(Settings()['flowSplitterSizes']) self.__splitter.splitterMoved.connect(self.flowSplitterMoved) Settings().sigFlowSplitterChanged.connect(self.otherFlowSplitterMoved) def flowSplitterMoved(self, pos, index): """Splitter has been moved""" del pos # unused argument del index # unused argument Settings()['flowSplitterSizes'] = list(self.__splitter.sizes()) def otherFlowSplitterMoved(self): """Other window has changed the splitter position""" self.__splitter.setSizes(Settings()['flowSplitterSizes']) def updateStatus(self): """Updates the toolbar buttons status""" self.__updateRunDebugButtons() isPythonFile = isPythonMime(self.__editor.mime) self.importsDiagramButton.setEnabled( isPythonFile and GlobalData().graphvizAvailable) self.__editor.diagramsMenu.setEnabled( self.importsDiagramButton.isEnabled()) self.__editor.toolsMenu.setEnabled(self.runScriptButton.isEnabled()) def onNavigationBar(self): """Triggered when navigation bar focus is requested""" if self.__navigationBar.isVisible(): self.__navigationBar.setFocusToLastCombo() return True def __onPrint(self): """Triggered when the print button is pressed""" self.__editor._onShortcutPrint() def __onPrintPreview(self): """Triggered when the print preview button is pressed""" pass def __onDeadCode(self): """Triggered when vulture analysis is requested""" GlobalData().mainWindow.tabDeadCodeClicked() def __redoAvailable(self, available): """Reports redo ops available""" self.__redoButton.setEnabled(available) def __undoAvailable(self, available): """Reports undo ops available""" self.__undoButton.setEnabled(available) def __languageChanged(self, _=None): """Language changed""" isPython = self.__editor.isPythonBuffer() isMarkdown = self.__editor.isMarkdownBuffer() self.disasmScriptButton.setEnabled(isPython) self.__renderWidget.setVisible(not Settings()['floatingRenderer'] and (isPython or isMarkdown)) # Arguments: modified def modificationChanged(self, _=None): """Triggered when the content is changed""" self.__updateRunDebugButtons() def __updateRunDebugButtons(self): """Enables/disables the run and debug buttons as required""" enable = isPythonMime(self.__editor.mime) and \ not self.isModified() and \ not self.__debugMode and \ os.path.isabs(self.__fileName) if enable != self.runScriptButton.isEnabled(): self.runScriptButton.setEnabled(enable) self.profileScriptButton.setEnabled(enable) self.debugScriptButton.setEnabled(enable) self.deadCodeScriptButton.setEnabled(enable) self.sigTabRunChanged.emit(enable) def isTabRunEnabled(self): """Tells the status of run-like buttons""" return self.runScriptButton.isEnabled() def replaceAll(self, newText): """Replaces the current buffer content with a new text""" # Unfortunately, the setText() clears the undo history so it cannot be # used. The selectAll() and replacing selected text do not suite # because after undo the cursor does not jump to the previous position. # So, there is an ugly select -> replace manipulation below... with self.__editor: origLine, origPos = self.__editor.cursorPosition self.__editor.setSelection(0, 0, origLine, origPos) self.__editor.removeSelectedText() self.__editor.insert(newText) self.__editor.setCurrentPosition(len(newText)) line, pos = self.__editor.cursorPosition lastLine = self.__editor.lines() self.__editor.setSelection(line, pos, lastLine - 1, len(self.__editor.text(lastLine - 1))) self.__editor.removeSelectedText() self.__editor.cursorPosition = origLine, origPos # These two for the proper cursor positioning after redo self.__editor.insert("s") self.__editor.cursorPosition = origLine, origPos + 1 self.__editor.deleteBack() self.__editor.cursorPosition = origLine, origPos def onRemoveTrailingWS(self): """Triggers when the trailing spaces should be wiped out""" self.__editor.removeTrailingWhitespaces() def onExpandTabs(self): """Expands tabs if there are any""" self.__editor.expandTabs(4) def setFocus(self): """Overridden setFocus""" if self.__outsideChangesBar.isHidden(): self.__editor.setFocus() else: self.__outsideChangesBar.setFocus() def onImportDgmTuned(self): """Runs the settings dialog first""" if self.isModified(): what = ImportsDiagramDialog.SingleBuffer if not os.path.isabs(self.getFileName()): logging.warning("Imports diagram can only be generated for " "a file. Save the editor buffer " "and try again.") return else: what = ImportsDiagramDialog.SingleFile dlg = ImportsDiagramDialog(what, self.getFileName(), self) if dlg.exec_() == QDialog.Accepted: # Should proceed with the diagram generation self.__generateImportDiagram(what, dlg.options) # Arguments: action def onImportDgm(self, _=None): """Runs the generation process with default options""" if self.isModified(): what = ImportsDiagramDialog.SingleBuffer if not os.path.isabs(self.getFileName()): logging.warning("Imports diagram can only be generated for " "a file. Save the editor buffer " "and try again.") return else: what = ImportsDiagramDialog.SingleFile self.__generateImportDiagram(what, ImportDiagramOptions()) def __generateImportDiagram(self, what, options): """Show the generation progress and display the diagram""" if self.isModified(): progressDlg = ImportsDiagramProgress(what, options, self.getFileName(), self.__editor.text) tooltip = "Generated for modified buffer (" + \ self.getFileName() + ")" else: progressDlg = ImportsDiagramProgress(what, options, self.getFileName()) tooltip = "Generated for file " + self.getFileName() if progressDlg.exec_() == QDialog.Accepted: GlobalData().mainWindow.openDiagram(progressDlg.scene, tooltip) def onOpenImport(self): """Triggered when Ctrl+I is received""" if isPythonMime(self.__editor.mime): # Take all the file imports and resolve them fileImports = getImportsList(self.__editor.text) if not fileImports: GlobalData().mainWindow.showStatusBarMessage( "There are no imports") else: self.__onImportList(self.__fileName, fileImports) def __onImportList(self, fileName, imports): """Works with a list of imports""" # It has already been checked that the file is a Python one resolvedList, errors = resolveImports(fileName, imports) del errors # errors are OK here if resolvedList: # Display the import selection widget self.importsBar.showResolvedImports(resolvedList) else: GlobalData().mainWindow.showStatusBarMessage( "Could not resolve any imports") def resizeEvent(self, event): """Resizes the import selection dialogue if necessary""" self.__editor.hideCompleter() QWidget.resizeEvent(self, event) self.resizeBars() def resizeBars(self): """Resize the bars if they are shown""" if not self.importsBar.isHidden(): self.importsBar.resize() if not self.__outsideChangesBar.isHidden(): self.__outsideChangesBar.resize() self.__editor.resizeCalltip() def showOutsideChangesBar(self, allEnabled): """Shows the bar for the editor for the user to choose the action""" self.setReloadDialogShown(True) self.__outsideChangesBar.showChoice(self.isModified(), allEnabled) def __onReload(self): """Triggered when a request to reload the file is received""" self.sigReloadRequest.emit() def reload(self): """Called (from the editors manager) to reload the file""" # Re-read the file with updating the file timestamp self.readFile(self.__fileName) # Hide the bars, just in case both of them if not self.importsBar.isHidden(): self.importsBar.hide() if not self.__outsideChangesBar.isHidden(): self.__outsideChangesBar.hide() # Set the shown flag self.setReloadDialogShown(False) def reloadAllNonModified(self): """Request to reload all the non-modified files""" self.reloadAllNonModifiedRequest.emit() @staticmethod def onRunScript(action=None): """Runs the script""" del action # unused argument GlobalData().mainWindow.onRunTab() @staticmethod def onRunScriptDlg(): """Shows the run parameters dialogue""" GlobalData().mainWindow.onRunTabDlg() @staticmethod def onProfileScript(action=None): """Profiles the script""" del action # unused argument GlobalData().mainWindow.onProfileTab() @staticmethod def onProfileScriptDlg(): """Shows the profile parameters dialogue""" GlobalData().mainWindow.onProfileTabDlg() @staticmethod def onDebugScript(action=None): """Starts debugging""" del action # unused argument GlobalData().mainWindow.onDebugTab() @staticmethod def onDebugScriptDlg(): """Shows the debug parameters dialogue""" GlobalData().mainWindow.onDebugTabDlg() def getCFEditor(self): """Provides a reference to the control flow widget""" return self.__flowUI def cflowSyncRequested(self, absPos, line, pos): """Highlight the item closest to the absPos""" self.__flowUI.highlightAtAbsPos(absPos, line, pos) def passFocusToFlow(self): """Sets the focus to the graphics part""" if isPythonMime(self.__editor.mime): self.__flowUI.setFocus() return True return False def getMDView(self): """Provides a reference to the MD rendered view""" return self.__mdView def terminate(self): """Called just before the tab is closed""" self.__splitter.splitterMoved.disconnect(self.flowSplitterMoved) Settings().sigFlowSplitterChanged.disconnect( self.otherFlowSplitterMoved) self.__cleanupLayout() self.__navigationBar.terminate() self.__navigationBar.deleteLater() self.__mdView.terminate() self.__mdView.deleteLater() self.__flowUI.terminate() self.__flowUI.deleteLater() self.__editor.terminate() self.__editor.deleteLater() def __cleanupLayout(self): """Disconnects QT widget signals and destroys the UI items""" self.__editor.redoAvailable.disconnect(self.__redoAvailable) self.__editor.undoAvailable.disconnect(self.__undoAvailable) self.__editor.modificationChanged.disconnect(self.modificationChanged) self.__editor.sigCFlowSyncRequested.disconnect(self.cflowSyncRequested) self.__editor.languageChanged.disconnect(self.__languageChanged) printButton = self.toolbar.findChild(QAction, 'printButton') printButton.triggered.disconnect(self.__onPrint) printButton.deleteLater() printPreviewButton = self.toolbar.findChild(QAction, 'printPreviewButton') printPreviewButton.triggered.connect(self.__onPrintPreview) printPreviewButton.deleteLater() self.importsDlgAct.triggered.disconnect(self.onImportDgmTuned) self.importsDlgAct.deleteLater() self.importsDiagramButton.menu().deleteLater() self.importsDiagramButton.clicked.disconnect(self.onImportDgm) self.importsDiagramButton.deleteLater() self.runScriptDlgAct.triggered.disconnect(self.onRunScriptDlg) self.runScriptDlgAct.deleteLater() self.runScriptButton.menu().deleteLater() self.runScriptButton.clicked.disconnect(self.onRunScript) self.runScriptButton.deleteLater() self.profileScriptDlgAct.triggered.disconnect(self.onProfileScriptDlg) self.profileScriptDlgAct.deleteLater() self.profileScriptButton.menu().deleteLater() self.profileScriptButton.clicked.disconnect(self.onProfileScript) self.profileScriptButton.deleteLater() self.debugScriptDlgAct.triggered.disconnect(self.onDebugScriptDlg) self.debugScriptDlgAct.deleteLater() self.debugScriptButton.menu().deleteLater() self.debugScriptButton.clicked.disconnect(self.onDebugScript) self.debugScriptButton.deleteLater() self.disasmScriptButton.clicked.disconnect(self.__editor._onDisasm0) self.disasmScriptButton.menu().deleteLater() self.disasmScriptButton.deleteLater() self.deadCodeScriptButton.triggered.disconnect(self.__onDeadCode) self.deadCodeScriptButton.deleteLater() self.__undoButton.triggered.disconnect(self.__editor.onUndo) self.__undoButton.deleteLater() self.__redoButton.triggered.disconnect(self.__editor.onRedo) self.__redoButton.deleteLater() self.removeTrailingSpacesButton.triggered.disconnect( self.onRemoveTrailingWS) self.removeTrailingSpacesButton.deleteLater() self.expandTabsButton.triggered.disconnect(self.onExpandTabs) self.expandTabsButton.deleteLater() self.__renderWidget.deleteLater() self.toolbar.deleteLater() # Mandatory interface part is below def getEditor(self): """Provides the editor widget""" return self.__editor def isModified(self): """Tells if the file is modified""" return self.__editor.document().isModified() def getRWMode(self): """Tells if the file is read only""" if not os.path.exists(self.__fileName): return None return 'RW' if QFileInfo(self.__fileName).isWritable() else 'RO' def getMime(self): """Provides the buffer mime""" return self.__editor.mime @staticmethod def getType(): """Tells the widget type""" return MainWindowTabWidgetBase.PlainTextEditor def getLanguage(self): """Tells the content language""" editorLanguage = self.__editor.language() if editorLanguage: return editorLanguage return self.__editor.mime if self.__editor.mime else 'n/a' def getFileName(self): """Tells what file name of the widget content""" return self.__fileName def setFileName(self, name): """Sets the file name""" self.__fileName = name self.__shortName = os.path.basename(name) def getEol(self): """Tells the EOL style""" return self.__editor.getEolIndicator() def getLine(self): """Tells the cursor line""" line, _ = self.__editor.cursorPosition return line def getPos(self): """Tells the cursor column""" _, pos = self.__editor.cursorPosition return pos def getEncoding(self): """Tells the content encoding""" if self.__editor.explicitUserEncoding: return self.__editor.explicitUserEncoding return self.__editor.encoding def getShortName(self): """Tells the display name""" return self.__shortName def setShortName(self, name): """Sets the display name""" self.__shortName = name def isDiskFileModified(self): """Return True if the loaded file is modified""" if not os.path.isabs(self.__fileName): return False if not os.path.exists(self.__fileName): return True path = os.path.realpath(self.__fileName) return self.__diskModTime != os.path.getmtime(path) or \ self.__diskSize != os.path.getsize(path) def doesFileExist(self): """Returns True if the loaded file still exists""" return os.path.exists(self.__fileName) def setReloadDialogShown(self, value=True): """Memorizes if the reloading dialogue has already been displayed""" self.__reloadDlgShown = value def getReloadDialogShown(self): """Tells if the reload dialog has already been shown""" return self.__reloadDlgShown and \ not self.__outsideChangesBar.isVisible() def updateModificationTime(self, fileName): """Updates the modification time""" path = os.path.realpath(fileName) self.__diskModTime = os.path.getmtime(path) self.__diskSize = os.path.getsize(path) def setDebugMode(self, debugOn, disableEditing): """Called to switch debug/development""" self.__debugMode = debugOn self.__editor.setDebugMode(debugOn, disableEditing) if debugOn: if disableEditing: # Undo/redo self.__undoButton.setEnabled(False) self.__redoButton.setEnabled(False) # Spaces/tabs/line self.removeTrailingSpacesButton.setEnabled(False) self.expandTabsButton.setEnabled(False) else: # Undo/redo self.__undoButton.setEnabled( self.__editor.document().isUndoAvailable()) self.__redoButton.setEnabled( self.__editor.document().isRedoAvailable()) # Spaces/tabs self.removeTrailingSpacesButton.setEnabled(True) self.expandTabsButton.setEnabled(True) # Run/debug buttons self.__updateRunDebugButtons() def isLineBreakable(self, line=None, enforceRecalc=False, enforceSure=False): """True if a breakpoint could be placed on the current line""" return self.__editor.isLineBreakable() def getVCSStatus(self): """Provides the VCS status""" return self.__vcsStatus def setVCSStatus(self, newStatus): """Sets the new VCS status""" self.__vcsStatus = newStatus # Floating renderer support def popRenderingWidgets(self): """Pops the rendering widgets""" self.__renderLayout.removeWidget(self.__flowUI) self.__renderLayout.removeWidget(self.__mdView) self.__renderWidget.setVisible(False) return [self.__flowUI, self.__mdView] def pushRenderingWidgets(self, widgets): """Returns back the rendering widgets""" for widget in widgets: self.__renderLayout.addWidget(widget) self.__languageChanged() # Sets the widget visibility