class GroupTitlePopup(QFrame): """Frameless panel to show the group title""" def __init__(self, parent): QFrame.__init__(self, parent) self.setWindowFlags(Qt.SplashScreen | Qt.WindowStaysOnTopHint | Qt.X11BypassWindowManagerHint) self.setFrameShape(QFrame.StyledPanel) self.setLineWidth(1) self.__titleLabel = None self.__createLayout() def __createLayout(self): """Creates the widget layout""" verticalLayout = QVBoxLayout(self) verticalLayout.setContentsMargins(0, 0, 0, 0) self.__titleLabel = QLabel() self.__titleLabel.setAutoFillBackground(True) self.__titleLabel.setFrameShape(QFrame.StyledPanel) self.__titleLabel.setStyleSheet('padding: 2px') verticalLayout.addWidget(self.__titleLabel) def setTitleForGroup(self, group): """Sets the title of the group""" self.__titleLabel.setFont(group.canvas.settings.monoFont) self.__titleLabel.setText(group.getTitle()) def resize(self, controlItem): """Moves the popup to the proper position""" # calculate the positions above the group # Taken from here: # https://stackoverflow.com/questions/9871749/find-screen-position-of-a-qgraphicsitem scene = controlItem.ref.scene() view = scene.views()[0] sceneP = controlItem.mapToScene(controlItem.boundingRect().topLeft()) viewP = view.mapFromScene(sceneP) pos = view.viewport().mapToGlobal(viewP) self.move(pos.x(), pos.y() - self.height() - 2) QApplication.processEvents() def show(self, controlItem): """Shows the title above the group control""" # Use the palette from the group bgColor, fgColor, _ = controlItem.ref.getColors() palette = self.__titleLabel.palette() palette.setColor(QPalette.Background, bgColor) palette.setColor(QPalette.Foreground, fgColor) self.__titleLabel.setPalette(palette) # That's a trick: resizing works correctly only if the item is shown # So move it outside of the screen, show it so it is invisible and then # resize and move to the proper position screenHeight = GlobalData().screenHeight self.move(0, screenHeight + 128) QFrame.show(self) QApplication.processEvents() self.resize(controlItem)
class CallTraceViewer(QWidget): """Implements the call trace viewer""" def __init__(self, debugger, parent=None): QWidget.__init__(self, parent) self.__debugger = debugger self.__createLayout() self.__debugger.sigClientCallTrace.connect(self.__onCallTrace) GlobalData().project.sigProjectChanged.connect(self.__onProjectChanged) def __onProjectChanged(self, what): """Triggered when a project is changed""" if what == CodimensionProject.CompleteProject: self.clear() self.calltraceList.onProjectChanged() def __createLayout(self): """Creates the widget layout""" verticalLayout = QVBoxLayout(self) verticalLayout.setContentsMargins(0, 0, 0, 0) verticalLayout.setSpacing(0) self.__calltraceLabel = QLabel("Call Trace", self) self.headerFrame = QFrame() self.headerFrame.setObjectName('calltraceheader') self.headerFrame.setStyleSheet('QFrame#calltraceheader {' + getLabelStyle(self.__calltraceLabel) + '}') self.headerFrame.setFixedHeight(HEADER_HEIGHT) headerLayout = QHBoxLayout() headerLayout.setContentsMargins(0, 0, 0, 0) headerLayout.addSpacing(3) headerLayout.addWidget(self.__calltraceLabel) self.headerFrame.setLayout(headerLayout) self.calltraceList = CallTraceBrowser(self) self.__startButton = QAction(getIcon('calltracestart.png'), "Start call tracing", self) self.__startButton.triggered.connect(self.__onStart) self.__startButton.setEnabled(not Settings()['calltrace']) self.__stopButton = QAction(getIcon('calltracestop.png'), "Stop call tracing", self) self.__stopButton.triggered.connect(self.__onStop) self.__stopButton.setEnabled(Settings()['calltrace']) self.__resizeButton = QAction(getIcon('resizecolumns.png'), "Resize the columns to their contents", self) self.__resizeButton.triggered.connect(self.__onResize) self.__resizeButton.setEnabled(True) self.__clearButton = QAction(getIcon('trash.png'), "Clear", self) self.__clearButton.triggered.connect(self.__onClear) self.__clearButton.setEnabled(False) self.__copyButton = QAction(getIcon('copymenu.png'), "Copy to clipboard", self) self.__copyButton.triggered.connect(self.__onCopy) self.__copyButton.setEnabled(False) # Toolbar self.toolbar = QToolBar() self.toolbar.setOrientation(Qt.Horizontal) self.toolbar.setMovable(False) self.toolbar.setAllowedAreas(Qt.TopToolBarArea) self.toolbar.setIconSize(QSize(16, 16)) self.toolbar.setFixedHeight(28) self.toolbar.setContentsMargins(0, 0, 0, 0) self.toolbar.addAction(self.__startButton) self.toolbar.addAction(self.__stopButton) fixedSpacer2 = QWidget() fixedSpacer2.setFixedWidth(15) self.toolbar.addWidget(fixedSpacer2) self.toolbar.addAction(self.__resizeButton) self.toolbar.addAction(self.__copyButton) expandingSpacer = QWidget() expandingSpacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) fixedSpacer4 = QWidget() fixedSpacer4.setFixedWidth(5) self.toolbar.addWidget(fixedSpacer4) self.toolbar.addWidget(expandingSpacer) self.toolbar.addAction(self.__clearButton) verticalLayout.addWidget(self.headerFrame) verticalLayout.addWidget(self.toolbar) verticalLayout.addWidget(self.calltraceList) def __onCallTrace(self, isCall, fromFile, fromLine, fromFunction, toFile, toLine, toFunction): """Call trace message received""" self.calltraceList.addCallTrace(isCall, fromFile, fromLine, fromFunction, toFile, toLine, toFunction) self.__clearButton.setEnabled(True) self.__copyButton.setEnabled(True) self.__updateHeader() def __updateHeader(self): """Updates the header""" count = self.calltraceList.count if count: self.__calltraceLabel.setText('Call Trace (' + str(count) + ')') else: self.__calltraceLabel.setText('Call Trace') def __onStart(self): """Start collecting calltrace""" self.__startButton.setEnabled(False) self.__stopButton.setEnabled(True) Settings()['calltrace'] = True self.__debugger.startCalltrace() def __onStop(self): """Stop collecting calltrace""" self.__startButton.setEnabled(True) self.__stopButton.setEnabled(False) Settings()['calltrace'] = False self.__debugger.stopCalltrace() def __onResize(self): """Resize the columns to its width""" for column in range(self.calltraceList.columnCount()): self.calltraceList.resizeColumnToContents(column) def __onClear(self): """Clears the view""" self.calltraceList.clear() self.__clearButton.setEnabled(False) self.__copyButton.setEnabled(False) self.__updateHeader() def clear(self): """Clears the view""" self.__onClear() def __onCopy(self): """Copy the content as text to clipboard""" content = [] lhsMaxLength = 0 try: item = self.calltraceList.topLevelItem(0) while item is not None: call = '<-' if item.data(0, Qt.UserRole): call = '->' lhs = item.text(1) lhsLength = len(lhs) lhsMaxLength = max(lhsMaxLength, lhsLength) content.append([lhs, lhsLength, call + ' ' + item.text(2)]) item = self.calltraceList.itemBelow(item) for index in range(len(content)): content[index] = \ content[index][0] + \ ' ' * (lhsMaxLength - content[index][1] + 1) + \ content[index][2] QApplication.clipboard().setText('\n'.join(content)) except Exception as exc: logging.error('Error copying the call trace to clipboard: ' + str(exc))
class StackViewer(QWidget): """Implements the stack viewer for a debugger""" def __init__(self, debugger, parent=None): QWidget.__init__(self, parent) self.__debugger = debugger self.currentStack = None self.currentFrame = 0 self.__contextItem = None self.__createPopupMenu() self.__createLayout() if not Settings()['showStackViewer']: self.__onShowHide(True) def __createPopupMenu(self): """Creates the popup menu""" self.__framesMenu = QMenu() self.__setCurrentMenuItem = self.__framesMenu.addAction( "Set current (single click)", self.__onSetCurrent) self.__jumpMenuItem = self.__framesMenu.addAction( "Set current and jump to the source (double click)", self.__onSetCurrentAndJump) def __createLayout(self): """Creates the widget layout""" verticalLayout = QVBoxLayout(self) verticalLayout.setContentsMargins(0, 0, 0, 0) verticalLayout.setSpacing(0) self.headerFrame = QFrame() self.headerFrame.setObjectName('stackheader') self.headerFrame.setStyleSheet('QFrame#stackheader {' + getLabelStyle(self) + '}') self.headerFrame.setFixedHeight(HEADER_HEIGHT) self.__stackLabel = QLabel("Stack") expandingSpacer = QSpacerItem(10, 10, QSizePolicy.Expanding) self.__showHideButton = QToolButton() self.__showHideButton.setAutoRaise(True) self.__showHideButton.setIcon(getIcon('less.png')) self.__showHideButton.setFixedSize(HEADER_BUTTON, HEADER_BUTTON) self.__showHideButton.setToolTip("Hide frames list") self.__showHideButton.setFocusPolicy(Qt.NoFocus) self.__showHideButton.clicked.connect(self.__onShowHide) headerLayout = QHBoxLayout() headerLayout.setContentsMargins(0, 0, 0, 0) headerLayout.addSpacing(3) headerLayout.addWidget(self.__stackLabel) headerLayout.addSpacerItem(expandingSpacer) headerLayout.addWidget(self.__showHideButton) self.headerFrame.setLayout(headerLayout) self.__framesList = QTreeWidget(self) self.__framesList.setSortingEnabled(False) # I might not need that because of two reasons: # - the window has no focus # - the window has custom current indicator # self.__framesList.setAlternatingRowColors(True) self.__framesList.setRootIsDecorated(False) self.__framesList.setItemsExpandable(False) self.__framesList.setUniformRowHeights(True) self.__framesList.setSelectionMode(QAbstractItemView.NoSelection) self.__framesList.setSelectionBehavior(QAbstractItemView.SelectRows) self.__framesList.setItemDelegate(NoOutlineHeightDelegate(4)) self.__framesList.setFocusPolicy(Qt.NoFocus) self.__framesList.setContextMenuPolicy(Qt.CustomContextMenu) self.__framesList.itemClicked.connect(self.__onFrameClicked) self.__framesList.itemDoubleClicked.connect( self.__onFrameDoubleClicked) self.__framesList.customContextMenuRequested.connect( self.__showContextMenu) self.__framesList.setHeaderLabels( ["", "File:line", "Function", "Arguments", "Full path"]) verticalLayout.addWidget(self.headerFrame) verticalLayout.addWidget(self.__framesList) def __onShowHide(self, startup=False): """Triggered when show/hide button is clicked""" if startup or self.__framesList.isVisible(): self.__framesList.setVisible(False) self.__showHideButton.setIcon(getIcon('more.png')) self.__showHideButton.setToolTip("Show frames list") self.__minH = self.minimumHeight() self.__maxH = self.maximumHeight() self.setMinimumHeight(self.headerFrame.height()) self.setMaximumHeight(self.headerFrame.height()) Settings()['showStackViewer'] = False else: self.__framesList.setVisible(True) self.__showHideButton.setIcon(getIcon('less.png')) self.__showHideButton.setToolTip("Hide frames list") self.setMinimumHeight(self.__minH) self.setMaximumHeight(self.__maxH) Settings()['showStackViewer'] = True def clear(self): """Clears the content""" self.__framesList.clear() self.currentStack = None self.__stackLabel.setText("Stack") def __resizeColumns(self): """Resize the files list columns""" self.__framesList.header().setStretchLastSection(True) self.__framesList.header().resizeSections(QHeaderView.ResizeToContents) self.__framesList.header().resizeSection(0, 22) self.__framesList.header().setSectionResizeMode(0, QHeaderView.Fixed) def populate(self, stack): """Sets the new call stack and selects the first item in it""" self.clear() self.currentStack = stack self.currentFrame = 0 frameNumber = 0 for item in stack: fName = item[0] lineNo = item[1] funcName = '' funcArgs = '' if len(item) >= 3: funcName = item[2] if len(item) >= 4: funcArgs = item[3] if funcName.startswith('<'): funcName = '' funcArgs = '' item = StackFrameItem(fName, lineNo, funcName, funcArgs, frameNumber) self.__framesList.addTopLevelItem(item) frameNumber += 1 self.__resizeColumns() self.__framesList.topLevelItem(0).setCurrent(True) self.__stackLabel.setText("Stack (total: " + str(len(stack)) + ")") def getFrameNumber(self): """Provides the current frame number""" return self.currentFrame def __onFrameClicked(self, item, column): """Triggered when a frame is clicked""" del column # unused argument if item.isCurrent(): return # Hide the current indicator self.__framesList.topLevelItem(self.currentFrame).setCurrent(False) # Show the new indicator self.currentFrame = item.getFrameNumber() for index in range(self.__framesList.topLevelItemCount()): item = self.__framesList.topLevelItem(index) if item.getFrameNumber() == self.currentFrame: item.setCurrent(True) self.__debugger.remoteClientVariables(1, self.currentFrame) # globals self.__debugger.remoteClientVariables(0, self.currentFrame) # locals def __onFrameDoubleClicked(self, item, column): """Triggered when a frame is double clicked""" del column # unused argument # The frame has been switched already because the double click # signal always comes after the single click one fileName = item.getFilename() lineNumber = item.getLineNumber() editorsManager = GlobalData().mainWindow.editorsManager() editorsManager.openFile(fileName, lineNumber) editor = editorsManager.currentWidget().getEditor() editor.gotoLine(lineNumber) editorsManager.currentWidget().setFocus() def __showContextMenu(self, coord): """Shows the frames list context menu""" self.__contextItem = self.__framesList.itemAt(coord) if self.__contextItem is not None: self.__setCurrentMenuItem.setEnabled( not self.__contextItem.isCurrent()) self.__framesMenu.popup(QCursor.pos()) def __onSetCurrent(self): """Context menu item handler""" self.__onFrameClicked(self.__contextItem, 0) def __onSetCurrentAndJump(self): """Context menu item handler""" self.__onFrameClicked(self.__contextItem, 0) self.__onFrameDoubleClicked(self.__contextItem, 0) def switchControl(self, isInIDE): """Switches the UI depending where the control flow is""" self.__framesList.setEnabled(isInIDE)
class ThreadsViewer(QWidget): """Implements the threads viewer for a debugger""" def __init__(self, debugger, parent=None): QWidget.__init__(self, parent) self.__debugger = debugger self.__createLayout() if not Settings()['showThreadViewer']: self.__onShowHide(True) def __createLayout(self): """Creates the widget layout""" verticalLayout = QVBoxLayout(self) verticalLayout.setContentsMargins(0, 0, 0, 0) verticalLayout.setSpacing(0) self.headerFrame = QFrame() self.headerFrame.setObjectName('threadheader') self.headerFrame.setStyleSheet('QFrame#threadheader {' + getLabelStyle(self) + '}') self.headerFrame.setFixedHeight(HEADER_HEIGHT) self.__threadsLabel = QLabel("Threads") expandingSpacer = QSpacerItem(10, 10, QSizePolicy.Expanding) self.__showHideButton = QToolButton() self.__showHideButton.setAutoRaise(True) self.__showHideButton.setIcon(getIcon('less.png')) self.__showHideButton.setFixedSize(HEADER_BUTTON, HEADER_BUTTON) self.__showHideButton.setToolTip("Hide threads list") self.__showHideButton.setFocusPolicy(Qt.NoFocus) self.__showHideButton.clicked.connect(self.__onShowHide) headerLayout = QHBoxLayout() headerLayout.setContentsMargins(0, 0, 0, 0) headerLayout.addSpacing(3) headerLayout.addWidget(self.__threadsLabel) headerLayout.addSpacerItem(expandingSpacer) headerLayout.addWidget(self.__showHideButton) self.headerFrame.setLayout(headerLayout) self.__threadsList = QTreeWidget() self.__threadsList.setSortingEnabled(False) # I might not need that because of two reasons: # - the window has no focus # - the window has custom current indicator # self.__threadsList.setAlternatingRowColors( True ) self.__threadsList.setRootIsDecorated(False) self.__threadsList.setItemsExpandable(False) self.__threadsList.setUniformRowHeights(True) self.__threadsList.setSelectionMode(QAbstractItemView.NoSelection) self.__threadsList.setSelectionBehavior(QAbstractItemView.SelectRows) self.__threadsList.setItemDelegate(NoOutlineHeightDelegate(4)) self.__threadsList.setFocusPolicy(Qt.NoFocus) self.__threadsList.itemClicked.connect(self.__onThreadClicked) self.__threadsList.setHeaderLabels(["", "Name", "State", "TID"]) verticalLayout.addWidget(self.headerFrame) verticalLayout.addWidget(self.__threadsList) def __onShowHide(self, startup=False): """Triggered when show/hide button is clicked""" if startup or self.__threadsList.isVisible(): self.__threadsList.setVisible(False) self.__showHideButton.setIcon(getIcon('more.png')) self.__showHideButton.setToolTip("Show threads list") self.__minH = self.minimumHeight() self.__maxH = self.maximumHeight() self.setMinimumHeight(self.headerFrame.height()) self.setMaximumHeight(self.headerFrame.height()) Settings()['showThreadViewer'] = False else: self.__threadsList.setVisible(True) self.__showHideButton.setIcon(getIcon('less.png')) self.__showHideButton.setToolTip("Hide threads list") self.setMinimumHeight(self.__minH) self.setMaximumHeight(self.__maxH) Settings()['showThreadViewer'] = True def __resizeColumns(self): """Resize the files list columns""" self.__threadsList.header().setStretchLastSection(True) self.__threadsList.header().resizeSections( QHeaderView.ResizeToContents) self.__threadsList.header().resizeSection(0, 22) self.__threadsList.header().setSectionResizeMode(0, QHeaderView.Fixed) def clear(self): """Clears the content""" self.__threadsList.clear() self.__threadsLabel.setText("Threads") def populate(self, currentThreadID, threadList): """Populates the thread list from the client""" self.clear() for thread in threadList: if thread['broken']: state = "Waiting at breakpoint" else: state = "Running" item = ThreadItem(thread['id'], thread['name'], state) if thread['id'] == currentThreadID: item.setCurrent(True) self.__threadsList.addTopLevelItem(item) self.__resizeColumns() self.__threadsLabel.setText("Threads (total: " + str(len(threadList)) + ")") def switchControl(self, isInIDE): """Switches the UI depending where the control flow is""" self.__threadsList.setEnabled(isInIDE) # Arguments: item, column def __onThreadClicked(self, item, _): """Triggered when a thread is clicked""" if item.isCurrent(): return for index in range(self.__threadsList.topLevelItemCount()): listItem = self.__threadsList.topLevelItem(index) if listItem.isCurrent(): listItem.setCurrent(False) break item.setCurrent(True) self.__debugger.remoteSetThread(item.getTID())
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 SVNAnnotateProgress(QDialog): """Minimalistic progress dialog""" def __init__(self, client, path, revStart, revEnd, revPeg, parent=None): QDialog.__init__(self, parent) self.__cancelRequest = False self.__inProcess = False self.__client = client self.__path = path self.__revStart = revStart self.__revEnd = revEnd self.__revPeg = revPeg # Transition data self.annotation = None self.revisionsInfo = None self.__createLayout() self.setWindowTitle("SVN Annotate") QTimer.singleShot(0, self.__process) def keyPressEvent(self, event): """Processes the ESC key specifically""" if event.key() == Qt.Key_Escape: self.__cancelRequest = True self.__infoLabel.setText("Cancelling...") QApplication.processEvents() def __createLayout(self): """Creates the dialog layout""" self.resize(450, 20) self.setSizeGripEnabled(True) verticalLayout = QVBoxLayout(self) self.__infoLabel = QLabel("Annotating '" + self.__path + "'...") verticalLayout.addWidget(self.__infoLabel) buttonBox = QDialogButtonBox(self) buttonBox.setOrientation(Qt.Horizontal) buttonBox.setStandardButtons(QDialogButtonBox.Close) verticalLayout.addWidget(buttonBox) buttonBox.rejected.connect(self.__onClose) def __onClose(self): """Triggered when the close button is clicked""" self.__cancelRequest = True self.__infoLabel.setText("Cancelling...") QApplication.processEvents() def closeEvent(self, event): """Window close event handler""" if self.__inProcess: self.__cancelRequest = True self.__infoLabel.setText("Cancelling...") QApplication.processEvents() event.ignore() else: event.accept() def __cancelCallback(self): """Called by pysvn regularly""" QApplication.processEvents() return self.__cancelRequest def __notifyCallback(self, event): """Called by pysvn. event is a dictionary""" message = None if event['path']: action = notifyActionToString(event['action']) if action is not None and action != "unknown": message = action + " " + event['path'] if event['mime_type'] == "application/octet-stream": message += " (binary)" if message: self.__infoLabel.setText(message) QApplication.processEvents() def __process(self): """Update process""" QApplication.processEvents() self.__client.callback_cancel = self.__cancelCallback self.__client.callback_notify = self.__notifyCallback try: self.__inProcess = True self.annotation = self.__client.annotate(self.__path, self.__revStart, self.__revEnd, self.__revPeg) self.__collectRevisionInfo() self.__inProcess = False except pysvn.ClientError as exc: errorCode = exc.args[1][0][1] if errorCode == pysvn.svn_err.cancelled: logging.info("Annotating of '" + self.__path + "' cancelled") else: message = exc.args[0] logging.error(message) self.__inProcess = False self.close() return except Exception as exc: logging.error(str(exc)) self.__inProcess = False self.close() return except: logging.error("Unknown error") self.__inProcess = False self.close() return if self.__cancelRequest: self.close() else: self.accept() def __collectRevisionInfo(self): """Collects information about revision messages""" self.__infoLabel.setText("Collecting revision messages...") QApplication.processEvents() revisions = set() for item in self.annotation: if item['revision'].kind == pysvn.opt_revision_kind.number: revisions.add(item['revision'].number) self.revisionsInfo = {} minRevision = min(revisions) maxRevision = max(revisions) revStart = pysvn.Revision(pysvn.opt_revision_kind.number, minRevision) revEnd = pysvn.Revision(pysvn.opt_revision_kind.number, maxRevision) revs = self.__client.log(self.__path, revision_start=revStart, revision_end=revEnd) for rev in revs: if rev['revision'].kind == pysvn.opt_revision_kind.number: number = rev['revision'].number if number in revisions: self.revisionsInfo[number] = {'message': rev['message']}
class SVNPluginLogDialog(QDialog): """SVN plugin log dialog""" NODIFF = '<html><body bgcolor="#ffffe6"></body></html>' def __init__(self, plugin, client, path, logInfo, parent=None): QDialog.__init__(self, parent) self.__plugin = plugin self.__client = client self.__path = path self.__logInfo = logInfo self.__lhsSelected = None self.__rhsSelected = None self.__createLayout() self.setWindowTitle("SVN Log") lastIndex = len(self.__logInfo) - 1 index = 0 for log in self.__logInfo: newItem = LogItem(log) self.__logView.addTopLevelItem(newItem) if index != lastIndex: rev = log.revision.number nextRev = self.__logInfo[index + 1].revision.number diffButton = self.__createDiffButton( log.revision, self.__logInfo[index + 1].revision) if rev is not None and nextRev is not None: diffButton.setToolTip( "Click to see diff to the older revision (r." + str(rev) + " to r." + str(nextRev) + ")") else: diffButton.setEnabled(False) diffButton.setToolTip( "Could not determine current or previous revision") else: diffButton = self.__createDiffButton(None, None) diffButton.setEnabled(False) diffButton.setToolTip( "Diff to previous revision is not avalable for the first revision" ) self.__logView.setItemWidget(newItem, DIFFTONEXT_COL, diffButton) index += 1 self.__resizeLogView() self.__sortLogView() self.__logView.setFocus() def __createDiffButton(self, rev, prevRev): """Creates a diff button for a path""" button = DiffButton() button.rev = rev button.prevRev = prevRev self.connect(button, SIGNAL('CustomClick'), self.onDiffBetween) return button def __resizeLogView(self): """Resizes the plugins table""" self.__logView.header().setStretchLastSection(True) self.__logView.header().resizeSections(QHeaderView.ResizeToContents) self.__logView.header().resizeSection(SELECT_COL, 28) self.__logView.header().setResizeMode(SELECT_COL, QHeaderView.Fixed) self.__logView.header().resizeSection(DIFFTONEXT_COL, 24) self.__logView.header().setResizeMode(DIFFTONEXT_COL, QHeaderView.Fixed) def __sortLogView(self): """Sorts the log table""" self.__logView.sortItems(self.__logView.sortColumn(), self.__logView.header().sortIndicatorOrder()) def __createLayout(self): """Creates the dialog layout""" self.resize(640, 480) self.setSizeGripEnabled(True) vboxLayout = QVBoxLayout(self) # Revisions to compare compareGroupbox = QGroupBox(self) compareGroupbox.setTitle("Revisions to compare") sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( compareGroupbox.sizePolicy().hasHeightForWidth()) compareGroupbox.setSizePolicy(sizePolicy) revisionLayout = QHBoxLayout(compareGroupbox) self.__lhsRevisionLabel = QLabel() self.__lhsRevisionLabel.setFrameStyle(QFrame.StyledPanel) self.__lhsResetButton = QToolButton() self.__lhsResetButton.setIcon( getIcon(pluginHomeDir + 'svnclearrev.png')) self.__lhsResetButton.setFocusPolicy(Qt.NoFocus) self.__lhsResetButton.setEnabled(False) self.__lhsResetButton.setToolTip("Reset revision to compare") self.__lhsResetButton.clicked.connect(self.__onLHSReset) self.__rhsRevisionLabel = QLabel() self.__rhsRevisionLabel.setFrameStyle(QFrame.StyledPanel) self.__rhsResetButton = QToolButton() self.__rhsResetButton.setIcon( getIcon(pluginHomeDir + 'svnclearrev.png')) self.__rhsResetButton.setFocusPolicy(Qt.NoFocus) self.__rhsResetButton.setEnabled(False) self.__rhsResetButton.setToolTip("Reset revision to compare") self.__rhsResetButton.clicked.connect(self.__onRHSReset) lhsLayout = QHBoxLayout() lhsLayout.addWidget(self.__lhsRevisionLabel) lhsLayout.addWidget(self.__lhsResetButton) rhsLayout = QHBoxLayout() rhsLayout.addWidget(self.__rhsRevisionLabel) rhsLayout.addWidget(self.__rhsResetButton) bothLayout = QVBoxLayout() bothLayout.addLayout(lhsLayout) bothLayout.addLayout(rhsLayout) revisionLayout.addLayout(bothLayout) self.__diffButton = QToolButton() self.__diffButton.setText("Diff") self.__diffButton.setFocusPolicy(Qt.NoFocus) self.__diffButton.setEnabled(False) self.__diffButton.clicked.connect(self.__onDiff) revisionLayout.addWidget(self.__diffButton) vboxLayout.addWidget(compareGroupbox) # Log table logHeaderFrame = QFrame() logHeaderFrame.setFrameStyle(QFrame.StyledPanel) logHeaderFrame.setAutoFillBackground(True) self.__setLightPalette(logHeaderFrame) logHeaderFrame.setFixedHeight(24) logHeaderLayout = QHBoxLayout() logHeaderLayout.setContentsMargins(3, 0, 0, 0) logHeaderLayout.addWidget(QLabel("Subversion log of " + self.__path)) logHeaderFrame.setLayout(logHeaderLayout) vboxLayout.addWidget(logHeaderFrame) self.__logView = QTreeWidget() self.__logView.setAlternatingRowColors(True) self.__logView.setRootIsDecorated(False) self.__logView.setItemsExpandable(False) self.__logView.setSortingEnabled(True) self.__logView.setItemDelegate(NoOutlineHeightDelegate(4)) self.__logViewHeader = QTreeWidgetItem( ["", "", "Revision", "Date", "Author", "Message"]) self.__logView.setHeaderItem(self.__logViewHeader) self.__logView.header().setSortIndicator(REVISION_COL, Qt.AscendingOrder) self.__logView.itemChanged.connect(self.__onLogViewChanged) vboxLayout.addWidget(self.__logView) # Diff part diffHeaderFrame = QFrame() diffHeaderFrame.setFrameStyle(QFrame.StyledPanel) diffHeaderFrame.setAutoFillBackground(True) self.__setLightPalette(diffHeaderFrame) diffHeaderFrame.setFixedHeight(24) diffLabel = QLabel("Diff") diffExpandingSpacer = QSpacerItem(10, 10, QSizePolicy.Expanding) self.__showHideDiffButton = QToolButton() self.__showHideDiffButton.setAutoRaise(True) self.__showHideDiffButton.setIcon(getIcon('less.png')) self.__showHideDiffButton.setFixedSize(20, 20) self.__showHideDiffButton.setToolTip("Show diff") self.__showHideDiffButton.setFocusPolicy(Qt.NoFocus) self.__showHideDiffButton.clicked.connect(self.__onShowHideDiff) diffLayout = QHBoxLayout() diffLayout.setContentsMargins(3, 0, 0, 0) diffLayout.addWidget(diffLabel) diffLayout.addSpacerItem(diffExpandingSpacer) diffLayout.addWidget(self.__showHideDiffButton) diffHeaderFrame.setLayout(diffLayout) self.__diffViewer = DiffTabWidget() self.__diffViewer.setHTML(self.NODIFF) self.__diffViewer.setVisible(False) vboxLayout.addWidget(diffHeaderFrame) vboxLayout.addWidget(self.__diffViewer) # Buttons at the bottom buttonBox = QDialogButtonBox(self) buttonBox.setOrientation(Qt.Horizontal) buttonBox.setStandardButtons(QDialogButtonBox.Ok) buttonBox.button(QDialogButtonBox.Ok).setDefault(True) buttonBox.accepted.connect(self.close) vboxLayout.addWidget(buttonBox) @staticmethod def __setLightPalette(frame): """Creates a lighter palette for the widget background""" palette = frame.palette() background = palette.color(QPalette.Background) background.setRgb(min(background.red() + 30, 255), min(background.green() + 30, 255), min(background.blue() + 30, 255)) palette.setColor(QPalette.Background, background) frame.setPalette(palette) def __onShowHideDiff(self): """On/off the diff section""" if self.__diffViewer.isVisible(): self.__diffViewer.setVisible(False) self.__showHideDiffButton.setIcon(getIcon('less.png')) self.__showHideDiffButton.setToolTip("Show diff") else: self.__diffViewer.setVisible(True) self.__showHideDiffButton.setIcon(getIcon('more.png')) self.__showHideDiffButton.setToolTip("Hide diff") def onDiffBetween(self, rev, prevRev): """Called when diff is requested between revisions""" if not rev or not prevRev: return QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) try: lhsContent = self.__client.cat(self.__path, prevRev) rhsContent = self.__client.cat(self.__path, rev) except Exception as exc: QApplication.restoreOverrideCursor() logging.error(str(exc)) return except: QApplication.restoreOverrideCursor() logging.error("Unknown error while retrieving " + self.__path + " content from the repository.") return QApplication.restoreOverrideCursor() diff = difflib.unified_diff(lhsContent.splitlines(), rhsContent.splitlines()) nodiffMessage = self.__path + " has no difference from revision " + \ str(prevRev.number) + " to revision " + str(rev.number) if diff is None: logging.info(nodiffMessage) return # There are changes, so replace the text and tell about the changes diffAsText = '\n'.join(list(diff)) if diffAsText.strip() == '': logging.info(nodiffMessage) return lhs = "--- revision " + str(prevRev.number) diffAsText = diffAsText.replace("--- ", lhs, 1) rhs = "+++ revision " + str(rev.number) diffAsText = diffAsText.replace("+++ ", rhs, 1) self.__diffViewer.setHTML(parse_from_memory(diffAsText, False, True)) if not self.__diffViewer.isVisible(): self.__onShowHideDiff() def __onDiff(self): """Show diff between revisions""" self.onDiffBetween(self.__rhsSelected.revision, self.__lhsSelected.revision) def __onLogViewChanged(self, item, column): """Revision selected for diff""" if item.checkState(SELECT_COL) == Qt.Checked: # An item has been selected if self.__lhsSelected is None: self.__lhsSelected = item.logInfo self.__normalizeSelected() return if self.__rhsSelected is None: self.__rhsSelected = item.logInfo self.__normalizeSelected() return # Both of the places have been occupied. Pick the one to update. if item.logInfo.date > self.__rhsSelected.date: self.__rhsSelected = item.logInfo else: self.__lhsSelected = item.logInfo self.__normalizeSelected() else: # An item has been de-selected if self.__lhsSelected is not None: if self.__lhsSelected.revision.number == item.logInfo.revision.number: self.__lhsSelected = None elif self.__rhsSelected is not None: if self.__rhsSelected.revision.number == item.logInfo.revision.number: self.__rhsSelected = None self.__normalizeSelected() return def __onLHSReset(self): """Revision removed from diff""" if self.__lhsSelected is not None: self.__deselectRevision(self.__lhsSelected.revision.number) self.__lhsSelected = None self.__lhsRevisionLabel.setText("") self.__diffButton.setEnabled(False) self.__lhsResetButton.setEnabled(False) def __onRHSReset(self): """Revision removed from diff""" if self.__rhsSelected is not None: self.__deselectRevision(self.__rhsSelected.revision.number) self.__rhsSelected = None self.__rhsRevisionLabel.setText("") self.__diffButton.setEnabled(False) self.__rhsResetButton.setEnabled(False) def __deselectRevision(self, revNumber): """Deselects a revision in the list""" index = 0 while index < self.__logView.topLevelItemCount(): item = self.__logView.topLevelItem(index) if item.logInfo.revision.number == revNumber: item.setCheckState(SELECT_COL, Qt.Unchecked) break index += 1 def __normalizeSelected(self): """Puts the earliest revision first""" if self.__lhsSelected is not None and self.__rhsSelected is not None: # It might be necessary to exchange the versions if self.__rhsSelected.date < self.__lhsSelected.date: temp = self.__rhsSelected self.__rhsSelected = self.__lhsSelected self.__lhsSelected = temp self.__diffButton.setEnabled(True) else: self.__diffButton.setEnabled(False) if self.__lhsSelected is None: self.__lhsRevisionLabel.setText("") self.__lhsRevisionLabel.setToolTip("") self.__lhsResetButton.setEnabled(False) else: self.__lhsRevisionLabel.setText( str(self.__lhsSelected.revision.number) + " (" + timestampToString(self.__lhsSelected.date) + ")") self.__lhsRevisionLabel.setToolTip(str(self.__lhsSelected.message)) self.__lhsResetButton.setEnabled(True) if self.__rhsSelected is None: self.__rhsRevisionLabel.setText("") self.__rhsRevisionLabel.setToolTip("") self.__rhsResetButton.setEnabled(False) else: self.__rhsRevisionLabel.setText( str(self.__rhsSelected.revision.number) + " (" + timestampToString(self.__rhsSelected.date) + ")") self.__rhsRevisionLabel.setToolTip(str(self.__rhsSelected.message)) self.__rhsResetButton.setEnabled(True)
class ControlFlowNavigationBar(QFrame): """Navigation bar at the top of the flow UI widget""" STATE_OK_UTD = 0 # Parsed OK, control flow up to date STATE_OK_CHN = 1 # Parsed OK, control flow changed STATE_BROKEN_UTD = 2 # Parsed with errors, control flow up to date STATE_BROKEN_CHN = 3 # Parsed with errors, control flow changed STATE_UNKNOWN = 4 def __init__(self, parent): QFrame.__init__(self, parent) self.__infoIcon = None self.__warningsIcon = None self.__layout = None self.__pathLabel = None self.__createLayout() self.__currentIconState = self.STATE_UNKNOWN def __createLayout(self): """Creates the layout""" self.setFixedHeight(24) self.__layout = QHBoxLayout(self) self.__layout.setContentsMargins(0, 0, 0, 0) # Create info icon self.__infoIcon = QLabel(self) self.__infoIcon.setPixmap(getPixmap('cfunknown.png')) self.__layout.addWidget(self.__infoIcon) self.__warningsIcon = QLabel(self) self.__warningsIcon.setPixmap(getPixmap('cfwarning.png')) self.__layout.addWidget(self.__warningsIcon) self.clearWarnings() labelStylesheet = 'QLabel {' + getLabelStyle(self.__infoIcon) + '}' # Create the path label self.__pathLabel = QLabel(self) self.__pathLabel.setStyleSheet(labelStylesheet) self.__pathLabel.setTextFormat(Qt.PlainText) self.__pathLabel.setAlignment(Qt.AlignLeft) self.__pathLabel.setWordWrap(False) self.__pathLabel.setTextInteractionFlags(Qt.NoTextInteraction) self.__pathLabel.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.__layout.addWidget(self.__pathLabel) self.__spacer = QWidget() self.__spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.__spacer.setMinimumWidth(0) self.__layout.addWidget(self.__spacer) # Create the selection label self.__selectionLabel = QLabel(self) self.__selectionLabel.setStyleSheet(labelStylesheet) self.__selectionLabel.setTextFormat(Qt.PlainText) self.__selectionLabel.setAlignment(Qt.AlignCenter) self.__selectionLabel.setWordWrap(False) self.__selectionLabel.setTextInteractionFlags(Qt.NoTextInteraction) self.__selectionLabel.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.__selectionLabel.setMinimumWidth(40) self.__layout.addWidget(self.__selectionLabel) self.setSelectionLabel(0, None) def clearWarnings(self): """Clears the warnings""" self.__warningsIcon.setVisible(False) self.__warningsIcon.setToolTip("") def setWarnings(self, warnings): """Sets the warnings""" self.__warningsIcon.setToolTip('Control flow parser warnings:\n' + '\n'.join(warnings)) self.__warningsIcon.setVisible(True) def clearErrors(self): """Clears all the errors""" self.__infoIcon.setToolTip('') def setErrors(self, errors): """Sets the errors""" self.__infoIcon.setToolTip('Control flow parser errors:\n' + '\n'.join(errors)) def updateInfoIcon(self, state): """Updates the information icon""" if state == self.__currentIconState: return if state == self.STATE_OK_UTD: self.__infoIcon.setPixmap(getPixmap('cfokutd.png')) self.__infoIcon.setToolTip("Control flow is up to date") self.__currentIconState = self.STATE_OK_UTD elif state == self.STATE_OK_CHN: self.__infoIcon.setPixmap(getPixmap('cfokchn.png')) self.__infoIcon.setToolTip("Control flow is not up to date; " "will be updated on idle") self.__currentIconState = self.STATE_OK_CHN elif state == self.STATE_BROKEN_UTD: self.__infoIcon.setPixmap(getPixmap('cfbrokenutd.png')) self.__infoIcon.setToolTip("Control flow might be invalid " "due to invalid python code") self.__currentIconState = self.STATE_BROKEN_UTD elif state == self.STATE_BROKEN_CHN: self.__infoIcon.setPixmap(getPixmap('cfbrokenchn.png')) self.__infoIcon.setToolTip("Control flow might be invalid; " "will be updated on idle") self.__currentIconState = self.STATE_BROKEN_CHN else: # STATE_UNKNOWN self.__infoIcon.setPixmap(getPixmap('cfunknown.png')) self.__infoIcon.setToolTip("Control flow state is unknown") self.__currentIconState = self.STATE_UNKNOWN def getCurrentState(self): """Provides the current state""" return self.__currentIconState def setPath(self, txt): """Sets the path label content""" self.__pathLabel.setText(txt) def setPathVisible(self, switchOn): """Sets the path visible""" self.__pathLabel.setVisible(switchOn) self.__spacer.setVisible(not switchOn) def setSelectionLabel(self, text, tooltip): """Sets selection label""" self.__selectionLabel.setText(str(text)) if tooltip: self.__selectionLabel.setToolTip("Selected items:\n" + str(tooltip)) else: self.__selectionLabel.setToolTip("Number of selected items") def resizeEvent(self, event): """Editor has resized""" QFrame.resizeEvent(self, event)
class NotUsedAnalysisProgress(QDialog): """Progress of the not used analysis""" Functions = 0 Classes = 1 Globals = 2 def __init__(self, what, sourceModel, parent=None): QDialog.__init__(self, parent) if what not in [self.Functions, self.Classes, self.Globals]: raise Exception("Unsupported unused analysis type: " + str(what)) self.__cancelRequest = False self.__inProgress = False self.__what = what # what is in source model self.__srcModel = sourceModel # source model of globals or # functions or classes # Avoid pylint complains self.__progressBar = None self.__infoLabel = None self.__foundLabel = None self.__found = 0 # Number of found self.__createLayout() self.setWindowTitle(self.__formTitle()) QTimer.singleShot(0, self.__process) def keyPressEvent(self, event): """Processes the ESC key specifically""" if event.key() == Qt.Key_Escape: self.__onClose() else: QDialog.keyPressEvent(self, event) def __formTitle(self): """Forms the progress dialog title""" title = "Unused " if self.__what == self.Functions: title += 'function' elif self.__what == self.Classes: title += 'class' else: title += 'globlal variable' return title + " analysis" def __formInfoLabel(self, name): """Forms the info label""" if self.__what == self.Functions: return 'Function: ' + name if self.__what == self.Classes: return 'Class: ' + name return 'Globlal variable: ' + name def __whatAsString(self): """Provides 'what' as string""" if self.__what == self.Functions: return 'function' if self.__what == self.Classes: return 'class' return 'global variable' def __updateFoundLabel(self): """Updates the found label""" text = "Found: " + str(self.__found) + " candidate" if self.__found != 1: text += "s" self.__foundLabel.setText(text) def __createLayout(self): """Creates the dialog layout""" self.resize(450, 20) self.setSizeGripEnabled(True) verticalLayout = QVBoxLayout(self) # Note label noteLabel = QLabel( "<b>Note</b>: the analysis is " "suggestive and not precise. " "Use the results with caution.\n", self) verticalLayout.addWidget(noteLabel) # Info label self.__infoLabel = QLabel(self) verticalLayout.addWidget(self.__infoLabel) # Progress bar self.__progressBar = QProgressBar(self) self.__progressBar.setValue(0) self.__progressBar.setOrientation(Qt.Horizontal) verticalLayout.addWidget(self.__progressBar) # Found label self.__foundLabel = QLabel(self) verticalLayout.addWidget(self.__foundLabel) # Buttons buttonBox = QDialogButtonBox(self) buttonBox.setOrientation(Qt.Horizontal) buttonBox.setStandardButtons(QDialogButtonBox.Close) verticalLayout.addWidget(buttonBox) buttonBox.rejected.connect(self.__onClose) def __onClose(self): """triggered when the close button is clicked""" self.__cancelRequest = True if not self.__inProgress: self.close() def __process(self): """Analysis process""" self.__inProgress = True mainWindow = GlobalData().mainWindow editorsManager = mainWindow.editorsManagerWidget.editorsManager # True - only project files modified = editorsManager.getModifiedList(True) if modified: modNames = [modItem[0] for modItem in modified] label = "File" if len(modified) >= 2: label += "s" label += ": " logging.warning("The analisys is performed for the content " "of saved files. The unsaved modifications will " "not be taken into account. " + label + ", ".join(modNames)) self.__updateFoundLabel() self.__progressBar.setRange(0, len(self.__srcModel.rootItem.childItems)) QApplication.processEvents() QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) count = 0 candidates = [] for treeItem in self.__srcModel.rootItem.childItems: if self.__cancelRequest: break name = str(treeItem.data(0)).split('(')[0] path = os.path.realpath(treeItem.getPath()) lineNumber = int(treeItem.data(2)) pos = treeItem.sourceObj.pos count += 1 self.__progressBar.setValue(count) self.__infoLabel.setText(self.__formInfoLabel(name)) QApplication.processEvents() # Analyze the name found = False definitions = getOccurrences(None, path, lineNumber, pos) if len(definitions) == 1: found = True index = getSearchItemIndex(candidates, path) if index < 0: widget = mainWindow.getWidgetForFileName(path) if widget is None: uuid = "" else: uuid = widget.getUUID() newItem = ItemToSearchIn(path, uuid) candidates.append(newItem) index = len(candidates) - 1 candidates[index].addMatch(name, lineNumber) if found: self.__found += 1 self.__updateFoundLabel() QApplication.processEvents() if self.__found == 0: # The analysis could be interrupted if not self.__cancelRequest: logging.info("No unused candidates found") else: mainWindow.displayFindInFiles("", candidates) QApplication.restoreOverrideCursor() self.__infoLabel.setText('Done') self.__inProgress = False self.accept()
class VariablesViewer(QWidget): """Implements the variables viewer for a debugger""" # First group of filters FilterGlobalAndLocal = 0 FilterGlobalOnly = 1 FilterLocalOnly = 2 def __init__(self, debugger, parent=None): QWidget.__init__(self, parent) self.__debugger = debugger self.__browser = VariablesBrowser(debugger, self) self.__createLayout() self.setTabOrder(self.__browser, self.__execStatement) self.setTabOrder(self.__execStatement, self.__execButton) self.__updateFilter() def __createLayout(self): """Creates the widget layout""" verticalLayout = QVBoxLayout(self) verticalLayout.setContentsMargins(0, 0, 0, 0) verticalLayout.setSpacing(0) headerFrame = QFrame() headerFrame.setObjectName('varsheader') headerFrame.setStyleSheet('QFrame#varsheader {' + getLabelStyle(self) + '}') headerFrame.setFixedHeight(HEADER_HEIGHT) self.__headerLabel = QLabel("Variables") expandingSpacer = QSpacerItem(10, 10, QSizePolicy.Expanding) self.__filterMenu = QMenu(self) self.__showAllAct = self.__filterMenu.addAction('Show all variables') self.__showAllAct.setData('showall') self.__filterMenu.addSeparator() self.__filters = [] for title, settingName, _ in VARIABLE_FILTERS: action = self.__filterMenu.addAction(title) action.setCheckable(True) action.setData(settingName) self.__filters.append(action) self.__filterMenu.aboutToShow.connect(self.__filterMenuAboutToShow) self.__filterMenu.triggered.connect(self.__filterMenuTriggered) self.__filterButton = QToolButton(self) self.__filterButton.setIcon(getIcon('dbgvarflt.png')) self.__filterButton.setToolTip('Variable filter') self.__filterButton.setPopupMode(QToolButton.InstantPopup) self.__filterButton.setMenu(self.__filterMenu) self.__filterButton.setFocusPolicy(Qt.NoFocus) self.__filterButton.setFixedSize(HEADER_BUTTON, HEADER_BUTTON) self.__execStatement = CDMComboBox(True) self.__execStatement.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.__execStatement.lineEdit().setToolTip("Execute statement") self.__execStatement.setFixedHeight(26) self.__execStatement.editTextChanged.connect( self.__execStatementChanged) self.__execStatement.enterClicked.connect(self.__onEnterInExec) self.__execButton = QPushButton("Exec") self.__execButton.setEnabled(False) self.__execButton.setFixedHeight(26) self.__execButton.clicked.connect(self.__onExec) headerLayout = QHBoxLayout() headerLayout.setContentsMargins(0, 0, 0, 0) headerLayout.setSpacing(0) headerLayout.addSpacing(3) headerLayout.addWidget(self.__headerLabel) headerLayout.addSpacerItem(expandingSpacer) headerLayout.addWidget(self.__filterButton) headerFrame.setLayout(headerLayout) execLayout = QGridLayout() execLayout.setContentsMargins(1, 1, 1, 1) execLayout.setSpacing(1) execLayout.addWidget(self.__execStatement, 0, 0) execLayout.addWidget(self.__execButton, 0, 1) verticalLayout.addWidget(headerFrame) verticalLayout.addWidget(self.__browser) verticalLayout.addLayout(execLayout) def __filterMenuAboutToShow(self): """Debug variable filter menu is about to show""" for flt in self.__filters: flt.setChecked(Settings()[flt.data()]) def __filterMenuTriggered(self, act): """A filter has been changed""" name = act.data() if name == 'showall': for _, settingName, _ in VARIABLE_FILTERS: Settings()[settingName] = True else: Settings()[name] = not Settings()[name] self.__updateFilter() def updateVariables(self, areGlobals, frameNumber, variables): """Triggered when a new set of variables is received""" self.__browser.showVariables(areGlobals, variables, frameNumber) self.__updateHeaderLabel() def updateVariable(self, areGlobals, variables): """Triggered when a new variable has been received""" self.__browser.showVariable(areGlobals, variables) self.__updateHeaderLabel() def __updateHeaderLabel(self): """Updates the header text""" shown, total = self.__browser.getShownAndTotalCounts() if shown == 0 and total == 0: self.__headerLabel.setText("Variables") else: self.__headerLabel.setText("Variables (" + str(shown) + " of " + str(total) + ")") def __updateFilter(self): """Updates the current filter""" self.__browser.filterChanged() self.__updateHeaderLabel() def clear(self): """Clears the content""" self.__browser.clear() self.__updateHeaderLabel() def clearAll(self): """Clears everything including the history""" self.clear() self.__execStatement.lineEdit().setText("") self.__execStatement.clear() def __execStatementChanged(self, text): """Triggered when a exec statement is changed""" text = str(text).strip() self.__execButton.setEnabled(text != "") def __onEnterInExec(self): """Enter/return clicked in exec""" self.__onExec() def __onExec(self): """Triggered when the Exec button is clicked""" text = self.__execStatement.currentText().strip() if text != "": currentFrame = GlobalData().mainWindow.getCurrentFrameNumber() self.__debugger.remoteExecuteStatement(text, currentFrame) self.__debugger.remoteClientVariables(1, currentFrame) # globals self.__debugger.remoteClientVariables(0, currentFrame) # locals def switchControl(self, isInIDE): """Switches the UI depending where the control flow is""" self.__browser.setEnabled(isInIDE) self.__filterButton.setEnabled(isInIDE) self.__execStatement.setEnabled(isInIDE) if isInIDE: text = self.__execStatement.currentText().strip() self.__execButton.setEnabled(text != "") else: self.__execButton.setEnabled(False)
def __createLayout(self): """Creates the dialog layout""" self.resize(600, 300) self.setSizeGripEnabled(True) verticalLayout = QVBoxLayout(self) gridLayout = QGridLayout() # Combo box for the text to search findLabel = QLabel(self) findLabel.setText("Find text:") self.findCombo = QComboBox(self) self.__tuneCombo(self.findCombo) self.findCombo.lineEdit().setToolTip( "Regular expression to search for") self.findCombo.editTextChanged.connect(self.__someTextChanged) self.findCombo.currentIndexChanged[int].connect( self.__whatIndexChanged) gridLayout.addWidget(findLabel, 0, 0, 1, 1) gridLayout.addWidget(self.findCombo, 0, 1, 1, 1) verticalLayout.addLayout(gridLayout) # Check boxes horizontalCBLayout = QHBoxLayout() self.caseCheckBox = QCheckBox(self) self.caseCheckBox.setText("Match &case") horizontalCBLayout.addWidget(self.caseCheckBox) self.wordCheckBox = QCheckBox(self) self.wordCheckBox.setText("Match whole &word") horizontalCBLayout.addWidget(self.wordCheckBox) self.regexpCheckBox = QCheckBox(self) self.regexpCheckBox.setText("Regular &expression") horizontalCBLayout.addWidget(self.regexpCheckBox) verticalLayout.addLayout(horizontalCBLayout) # Files groupbox filesGroupbox = QGroupBox(self) filesGroupbox.setTitle("Find in") sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( filesGroupbox.sizePolicy().hasHeightForWidth()) filesGroupbox.setSizePolicy(sizePolicy) gridLayoutFG = QGridLayout(filesGroupbox) self.projectRButton = QRadioButton(filesGroupbox) self.projectRButton.setText("&Project") gridLayoutFG.addWidget(self.projectRButton, 0, 0) self.projectRButton.clicked.connect(self.__projectClicked) self.openFilesRButton = QRadioButton(filesGroupbox) self.openFilesRButton.setText("&Opened files only") gridLayoutFG.addWidget(self.openFilesRButton, 1, 0) self.openFilesRButton.clicked.connect(self.__openFilesOnlyClicked) self.dirRButton = QRadioButton(filesGroupbox) self.dirRButton.setText("&Directory tree") gridLayoutFG.addWidget(self.dirRButton, 2, 0) self.dirRButton.clicked.connect(self.__dirClicked) self.dirEditCombo = QComboBox(filesGroupbox) self.__tuneCombo(self.dirEditCombo) self.dirEditCombo.lineEdit().setToolTip("Directory to search in") gridLayoutFG.addWidget(self.dirEditCombo, 2, 1) self.dirEditCombo.editTextChanged.connect(self.__someTextChanged) self.dirSelectButton = QPushButton(filesGroupbox) self.dirSelectButton.setText("...") gridLayoutFG.addWidget(self.dirSelectButton, 2, 2) self.dirSelectButton.clicked.connect(self.__selectDirClicked) filterLabel = QLabel(filesGroupbox) filterLabel.setText("Files filter:") gridLayoutFG.addWidget(filterLabel, 3, 0) self.filterCombo = QComboBox(filesGroupbox) self.__tuneCombo(self.filterCombo) self.filterCombo.lineEdit().setToolTip("File names regular expression") gridLayoutFG.addWidget(self.filterCombo, 3, 1) self.filterCombo.editTextChanged.connect(self.__someTextChanged) verticalLayout.addWidget(filesGroupbox) # File label self.fileLabel = FitPathLabel(parent=self) self.fileLabel.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Fixed) verticalLayout.addWidget(self.fileLabel) # Progress bar self.progressBar = QProgressBar(self) self.progressBar.setValue(0) self.progressBar.setOrientation(Qt.Horizontal) verticalLayout.addWidget(self.progressBar) # Buttons at the bottom buttonBox = QDialogButtonBox(self) buttonBox.setOrientation(Qt.Horizontal) buttonBox.setStandardButtons(QDialogButtonBox.Cancel) self.findButton = buttonBox.addButton("Find", QDialogButtonBox.AcceptRole) self.findButton.setDefault(True) self.findButton.clicked.connect(self.__process) verticalLayout.addWidget(buttonBox) buttonBox.rejected.connect(self.__onClose)
class SVNStatusProgress(QDialog): """Minimalistic progress dialog""" def __init__(self, plugin, client, path, update, parent=None): QDialog.__init__(self, parent) self.__cancelRequest = False self.__inProcess = False self.__plugin = plugin self.__client = client self.__path = path self.__update = update # Transition data self.statusList = None self.__createLayout() self.setWindowTitle("SVN Status") QTimer.singleShot(0, self.__process) def keyPressEvent(self, event): """Processes the ESC key specifically""" if event.key() == Qt.Key_Escape: self.__cancelRequest = True self.__infoLabel.setText("Cancelling...") QApplication.processEvents() def __createLayout(self): """Creates the dialog layout""" self.resize(450, 20) self.setSizeGripEnabled(True) verticalLayout = QVBoxLayout(self) self.__infoLabel = QLabel("Getting status of '" + self.__path + "'...") verticalLayout.addWidget(self.__infoLabel) buttonBox = QDialogButtonBox(self) buttonBox.setOrientation(Qt.Horizontal) buttonBox.setStandardButtons(QDialogButtonBox.Close) verticalLayout.addWidget(buttonBox) buttonBox.rejected.connect(self.__onClose) def __onClose(self): """Triggered when the close button is clicked""" self.__cancelRequest = True self.__infoLabel.setText("Cancelling...") QApplication.processEvents() def closeEvent(self, event): """Window close event handler""" if self.__inProcess: self.__cancelRequest = True self.__infoLabel.setText("Cancelling...") QApplication.processEvents() event.ignore() else: event.accept() def __cancelCallback(self): """Called by pysvn regularly""" QApplication.processEvents() return self.__cancelRequest def __notifyCallback(self, event): """Called by pysvn. event is a dictionary""" message = None if event['path']: action = notifyActionToString(event['action']) if action is not None and action != "unknown": message = action + " " + event['path'] if event['mime_type'] == "application/octet-stream": message += " (binary)" if message: self.__infoLabel.setText(message) QApplication.processEvents() def __process(self): """Update process""" self.__client.callback_cancel = self.__cancelCallback self.__client.callback_notify = self.__notifyCallback try: statusList = self.__client.status(self.__path, update=self.__update, depth=pysvn.depth.infinity) if not statusList: # Try again, may be it is because the depth statusList = self.__client.status(self.__path, update=self.__update, depth=pysvn.depth.empty) if not statusList and self.__update == True: # Try again, may be it is because of update statusList = self.__client.status(self.__path, update=False, depth=pysvn.depth.empty) self.statusList = [] for status in statusList: reportPath = status.path if not status.path.endswith(os.path.sep): if status.entry is None: if os.path.isdir(status.path): reportPath += os.path.sep elif status.entry.kind == pysvn.node_kind.dir: reportPath += os.path.sep self.statusList.append((reportPath, self.__plugin.convertSVNStatus(status), None)) except pysvn.ClientError as exc: errorCode = exc.args[1][0][1] if errorCode == pysvn.svn_err.wc_not_working_copy: self.statusList = [(self.__path, self.NOT_UNDER_VCS, None),] else: message = exc.args[0] self.statusList = [(self.__path, IND_ERROR, message),] except Exception as exc: self.statusList = [(self.__path, IND_ERROR, "Error: " + str(exc)),] except: self.statusList = [(self.__path, IND_ERROR, "Unknown error"),] if self.__cancelRequest: self.close() else: self.accept()
class WatchPointViewer(QWidget): """Implements the watch point viewer for a debugger""" def __init__(self, parent, wpointModel): QWidget.__init__(self, parent) self.__currentItem = None self.__createPopupMenu() self.__createLayout(wpointModel) GlobalData().project.sigProjectChanged.connect(self.__onProjectChanged) if Settings()['showWatchPointViewer'] == False: self.__onShowHide(True) def __createPopupMenu(self): """Creates the popup menu""" # self.__excptMenu = QMenu() # self.__removeMenuItem = self.__excptMenu.addAction( # "Remove from ignore list", self.__onRemoveFromIgnore ) return def __createLayout(self, wpointModel): """Creates the widget layout""" verticalLayout = QVBoxLayout(self) verticalLayout.setContentsMargins(0, 0, 0, 0) verticalLayout.setSpacing(0) self.headerFrame = QFrame() self.headerFrame.setFrameStyle(QFrame.StyledPanel) self.headerFrame.setAutoFillBackground(True) headerPalette = self.headerFrame.palette() headerBackground = headerPalette.color(QPalette.Background) headerBackground.setRgb(min(headerBackground.red() + 30, 255), min(headerBackground.green() + 30, 255), min(headerBackground.blue() + 30, 255)) headerPalette.setColor(QPalette.Background, headerBackground) self.headerFrame.setPalette(headerPalette) self.headerFrame.setFixedHeight(24) self.__watchpointLabel = QLabel("Watchpoints") expandingSpacer = QSpacerItem(10, 10, QSizePolicy.Expanding) fixedSpacer = QSpacerItem(3, 3) self.__showHideButton = QToolButton() self.__showHideButton.setAutoRaise(True) self.__showHideButton.setIcon(getIcon('less.png')) self.__showHideButton.setFixedSize(20, 20) self.__showHideButton.setToolTip("Hide ignored exceptions list") self.__showHideButton.setFocusPolicy(Qt.NoFocus) self.__showHideButton.clicked.connect(self.__onShowHide) headerLayout = QHBoxLayout() headerLayout.setContentsMargins(1, 1, 1, 1) headerLayout.addSpacerItem(fixedSpacer) headerLayout.addWidget(self.__watchpointLabel) headerLayout.addSpacerItem(expandingSpacer) headerLayout.addWidget(self.__showHideButton) self.headerFrame.setLayout(headerLayout) self.__wpointsList = WatchPointView(self, wpointModel) self.__enableButton = QToolButton() self.__enableButton.setIcon(getIcon('add.png')) self.__enableButton.setFixedSize(24, 24) self.__enableButton.setToolTip("Enable/disable the watchpoint") self.__enableButton.setFocusPolicy(Qt.NoFocus) self.__enableButton.setEnabled(False) self.__enableButton.clicked.connect(self.__onEnableDisable) expandingSpacer = QSpacerItem(10, 10, QSizePolicy.Expanding) self.__jumpToCodeButton = QToolButton() self.__jumpToCodeButton.setIcon(getIcon('gotoline.png')) self.__jumpToCodeButton.setFixedSize(24, 24) self.__jumpToCodeButton.setToolTip("Jump to the code") self.__jumpToCodeButton.setFocusPolicy(Qt.NoFocus) self.__jumpToCodeButton.setEnabled(False) self.__jumpToCodeButton.clicked.connect(self.__onJumpToCode) toolbarLayout = QHBoxLayout() toolbarLayout.addWidget(self.__enableButton) toolbarLayout.addSpacerItem(expandingSpacer) toolbarLayout.addWidget(self.__jumpToCodeButton) self.__wpointsList.sigSelectionChanged.connect( self.__onSelectionChanged) verticalLayout.addWidget(self.headerFrame) verticalLayout.addLayout(toolbarLayout) verticalLayout.addWidget(self.__wpointsList) def clear(self): """Clears the content""" # self.__wpointsList.clear() self.__updateTitle() self.__jumpToCodeButton.setEnabled(False) self.__currentItem = None def __onJumpToCode(self): """Jumps to the corresponding source code line""" return def __onShowHide(self, startup=False): """Triggered when show/hide button is clicked""" if startup or self.__wpointsList.isVisible(): self.__wpointsList.setVisible(False) self.__enableButton.setVisible(False) self.__jumpToCodeButton.setVisible(False) self.__showHideButton.setIcon(getIcon('more.png')) self.__showHideButton.setToolTip("Show watchpoints list") self.__minH = self.minimumHeight() self.__maxH = self.maximumHeight() self.setMinimumHeight(self.headerFrame.height()) self.setMaximumHeight(self.headerFrame.height()) Settings()['showWatchPointViewer'] = False else: self.__wpointsList.setVisible(True) self.__enableButton.setVisible(True) self.__jumpToCodeButton.setVisible(True) self.__showHideButton.setIcon(getIcon('less.png')) self.__showHideButton.setToolTip("Hide watchpoints list") self.setMinimumHeight(self.__minH) self.setMaximumHeight(self.__maxH) Settings()['showWatchPointViewer'] = True def __onSelectionChanged(self, index): """Triggered when the current item is changed""" if index.isValid(): pass else: pass return selected = list(self.__exceptionsList.selectedItems()) if selected: self.__currentItem = selected[0] self.__removeButton.setEnabled(True) else: self.__currentItem = None self.__removeButton.setEnabled(False) def __updateTitle(self): """Updates the section title""" count = self.getTotalCount() if count == 0: self.__watchpointLabel.setText("Watchpoints") else: self.__watchpointLabel.setText("Watchpoints (total: " + str(count) + ")") def getTotalCount(self): """Provides the total number of watch points""" count = 0 return count def __onProjectChanged(self, what): """Triggered when a project is changed""" if what == CodimensionProject.CompleteProject: self.clear() def __onEnableDisable(self): """Triggered when a breakpoint should be enabled/disabled""" pass
class ClientExceptionsViewer(QWidget): """Implements the client exceptions viewer for a debugger""" sigClientExceptionsCleared = pyqtSignal() def __init__(self, parent, ignoredExceptionsViewer): QWidget.__init__(self, parent) self.__ignoredExceptionsViewer = ignoredExceptionsViewer self.__currentItem = None self.__createPopupMenu() self.__createLayout() GlobalData().project.sigProjectChanged.connect(self.__onProjectChanged) def setFocus(self): """Sets the widget focus""" self.exceptionsList.setFocus() def __createPopupMenu(self): """Creates the popup menu""" self.__excptMenu = QMenu() self.__addToIgnoreMenuItem = self.__excptMenu.addAction( "Add to ignore list", self.__onAddToIgnore) self.__jumpToCodeMenuItem = self.__excptMenu.addAction( "Jump to code", self.__onJumpToCode) def __createLayout(self): """Creates the widget layout""" verticalLayout = QVBoxLayout(self) verticalLayout.setContentsMargins(0, 0, 0, 0) verticalLayout.setSpacing(0) self.__excptLabel = QLabel("Exceptions", self) self.headerFrame = QFrame() self.headerFrame.setObjectName('excpt') self.headerFrame.setStyleSheet('QFrame#excpt {' + getLabelStyle(self.__excptLabel) + '}') self.headerFrame.setFixedHeight(HEADER_HEIGHT) headerLayout = QHBoxLayout() headerLayout.setContentsMargins(0, 0, 0, 0) headerLayout.addSpacing(3) headerLayout.addWidget(self.__excptLabel) self.headerFrame.setLayout(headerLayout) self.exceptionsList = QTreeWidget(self) self.exceptionsList.setSortingEnabled(False) self.exceptionsList.setAlternatingRowColors(True) self.exceptionsList.setRootIsDecorated(True) self.exceptionsList.setItemsExpandable(True) self.exceptionsList.setUniformRowHeights(True) self.exceptionsList.setSelectionMode(QAbstractItemView.SingleSelection) self.exceptionsList.setSelectionBehavior(QAbstractItemView.SelectRows) self.exceptionsList.setItemDelegate(NoOutlineHeightDelegate(4)) self.exceptionsList.setContextMenuPolicy(Qt.CustomContextMenu) self.__addToIgnoreButton = QAction( getIcon('add.png'), "Add exception to the list of ignored", self) self.__addToIgnoreButton.triggered.connect(self.__onAddToIgnore) self.__addToIgnoreButton.setEnabled(False) expandingSpacer = QWidget() expandingSpacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.__jumpToCodeButton = QAction( getIcon('gotoline.png'), "Jump to the code", self) self.__jumpToCodeButton.triggered.connect(self.__onJumpToCode) self.__jumpToCodeButton.setEnabled(False) self.__delAllButton = QAction( getIcon('trash.png'), "Delete all the client exceptions", self) self.__delAllButton.triggered.connect(self.__onDelAll) self.__delAllButton.setEnabled(False) self.toolbar = QToolBar() self.toolbar.setOrientation(Qt.Horizontal) self.toolbar.setMovable(False) self.toolbar.setAllowedAreas(Qt.TopToolBarArea) self.toolbar.setIconSize(QSize(16, 16)) self.toolbar.setFixedHeight(28) self.toolbar.setContentsMargins(0, 0, 0, 0) self.toolbar.addAction(self.__addToIgnoreButton) self.toolbar.addAction(self.__jumpToCodeButton) self.toolbar.addWidget(expandingSpacer) self.toolbar.addAction(self.__delAllButton) self.exceptionsList.itemDoubleClicked.connect( self.__onExceptionDoubleClicked) self.exceptionsList.customContextMenuRequested.connect( self.__showContextMenu) self.exceptionsList.itemSelectionChanged.connect( self.__onSelectionChanged) self.exceptionsList.setHeaderLabels(["Exception", "Function", "Arguments"]) verticalLayout.addWidget(self.headerFrame) verticalLayout.addWidget(self.toolbar) verticalLayout.addWidget(self.exceptionsList) def clear(self): """Clears the content""" self.exceptionsList.clear() self.__updateExceptionsLabel() self.__addToIgnoreButton.setEnabled(False) self.__jumpToCodeButton.setEnabled(False) self.__delAllButton.setEnabled(False) self.__currentItem = None self.sigClientExceptionsCleared.emit() def __onExceptionDoubleClicked(self, item, column): """Triggered when an exception is double clicked""" del item # unused argument del column # unused argument if self.__currentItem is not None: if self.__currentItem.getType() == STACK_FRAME_ITEM: self.__onJumpToCode() return # This is an exception item itself. # Open a separate dialog window with th detailed info. def __showContextMenu(self, coord): """Shows the frames list context menu""" self.__currentItem = self.exceptionsList.itemAt(coord) self.__addToIgnoreMenuItem.setEnabled( self.__addToIgnoreButton.isEnabled()) self.__jumpToCodeMenuItem.setEnabled( self.__jumpToCodeButton.isEnabled()) if self.__currentItem is not None: self.__excptMenu.popup(QCursor.pos()) def __onAddToIgnore(self): """Adds an exception into the ignore list""" if self.__currentItem is not None: self.__ignoredExceptionsViewer.addExceptionFilter( str(self.__currentItem.getExceptionType())) self.__addToIgnoreButton.setEnabled(False) def __onJumpToCode(self): """Jumps to the corresponding source code line""" if self.__currentItem is not None: if self.__currentItem.getType() == STACK_FRAME_ITEM: fileName = self.__currentItem.getFileName() if '<' not in fileName and '>' not in fileName: lineNumber = self.__currentItem.getLineNumber() editorsManager = GlobalData().mainWindow.editorsManager() editorsManager.openFile(fileName, lineNumber) editor = editorsManager.currentWidget().getEditor() editor.gotoLine(lineNumber) editorsManager.currentWidget().setFocus() def __onDelAll(self): """Triggered when all the exceptions should be deleted""" self.clear() def addException(self, exceptionType, exceptionMessage, stackTrace): """Adds the exception to the view""" for index in range(self.exceptionsList.topLevelItemCount()): item = self.exceptionsList.topLevelItem(index) if item.equal(exceptionType, exceptionMessage, stackTrace): item.incrementCounter() self.exceptionsList.clearSelection() self.exceptionsList.setCurrentItem(item) self.__updateExceptionsLabel() return item = ExceptionItem(self.exceptionsList, exceptionType, exceptionMessage, stackTrace) self.exceptionsList.clearSelection() self.exceptionsList.setCurrentItem(item) self.__updateExceptionsLabel() self.__delAllButton.setEnabled(True) def __updateExceptionsLabel(self): """Updates the exceptions header label""" total = self.getTotalCount() if total > 0: self.__excptLabel.setText("Exceptions (total: " + str(total) + ")") else: self.__excptLabel.setText("Exceptions") def getTotalCount(self): """Provides the total number of exceptions""" count = 0 for index in range(self.exceptionsList.topLevelItemCount()): count += self.exceptionsList.topLevelItem(index).getCount() return count def __onProjectChanged(self, what): """Triggered when a project is changed""" if what == CodimensionProject.CompleteProject: self.clear() def __onSelectionChanged(self): """Triggered when the current item is changed""" selected = list(self.exceptionsList.selectedItems()) if selected: self.__currentItem = selected[0] if self.__currentItem.getType() == STACK_FRAME_ITEM: fileName = self.__currentItem.getFileName() if '<' in fileName or '>' in fileName: self.__jumpToCodeButton.setEnabled(False) else: self.__jumpToCodeButton.setEnabled(True) self.__addToIgnoreButton.setEnabled(False) else: self.__jumpToCodeButton.setEnabled(False) excType = str(self.__currentItem.getExceptionType()) if self.__ignoredExceptionsViewer.isIgnored(excType) or \ " " in excType or excType.startswith("unhandled"): self.__addToIgnoreButton.setEnabled(False) else: self.__addToIgnoreButton.setEnabled(True) else: self.__currentItem = None self.__addToIgnoreButton.setEnabled(False) self.__jumpToCodeButton.setEnabled(False)
class SVNLogProgress(QDialog): """Minimalistic progress dialog""" def __init__(self, client, path, parent=None): QDialog.__init__(self, parent) self.__cancelRequest = False self.__inProcess = False self.__client = client self.__path = path # Transition data self.logInfo = None self.__createLayout() self.setWindowTitle("SVN Log") QTimer.singleShot(0, self.__process) def keyPressEvent(self, event): """Processes the ESC key specifically""" if event.key() == Qt.Key_Escape: self.__cancelRequest = True self.__infoLabel.setText("Cancelling...") QApplication.processEvents() def __createLayout(self): """Creates the dialog layout""" self.resize(450, 20) self.setSizeGripEnabled(True) verticalLayout = QVBoxLayout(self) self.__infoLabel = QLabel("Retrieving log of '" + self.__path + "'...") verticalLayout.addWidget(self.__infoLabel) buttonBox = QDialogButtonBox(self) buttonBox.setOrientation(Qt.Horizontal) buttonBox.setStandardButtons(QDialogButtonBox.Close) verticalLayout.addWidget(buttonBox) buttonBox.rejected.connect(self.__onClose) def __onClose(self): """Triggered when the close button is clicked""" self.__cancelRequest = True self.__infoLabel.setText("Cancelling...") QApplication.processEvents() def closeEvent(self, event): """Window close event handler""" if self.__inProcess: self.__cancelRequest = True self.__infoLabel.setText("Cancelling...") QApplication.processEvents() event.ignore() else: event.accept() def __cancelCallback(self): """Called by pysvn regularly""" QApplication.processEvents() return self.__cancelRequest def __process(self): """Update process""" self.__client.callback_cancel = self.__cancelCallback try: self.logInfo = self.__client.log(self.__path) except Exception as exc: logging.error(str(exc)) self.close() return except: logging.error("Unknown error while retrieving log of " + self.__path) self.close() return if self.__cancelRequest: self.close() else: self.accept()
class ImportsDiagramProgress(QDialog): """Progress of the diagram generator""" def __init__(self, what, options, path="", buf="", parent=None): QDialog.__init__(self, parent) self.__cancelRequest = False self.__inProgress = False self.__what = what self.__options = options self.__path = path # could be a dir or a file self.__buf = buf # content in case of a modified file # Working process data self.__participantFiles = [] # Collected list of files self.__projectImportDirs = [] self.__projectImportsCache = {} # utils.settings -> /full/path/to.py self.__dirsToImportsCache = {} # /dir/path -> { my.mod: path.py, ... } self.dataModel = ImportDiagramModel() self.scene = QGraphicsScene() # Avoid pylint complains self.progressBar = None self.infoLabel = None self.__createLayout() self.setWindowTitle('Imports/dependencies diagram generator') QTimer.singleShot(0, self.__process) def keyPressEvent(self, event): """Processes the ESC key specifically""" if event.key() == Qt.Key_Escape: self.__onClose() else: QDialog.keyPressEvent(self, event) def __createLayout(self): """Creates the dialog layout""" self.resize(450, 20) self.setSizeGripEnabled(True) verticalLayout = QVBoxLayout(self) # Info label self.infoLabel = QLabel(self) verticalLayout.addWidget(self.infoLabel) # Progress bar self.progressBar = QProgressBar(self) self.progressBar.setValue(0) self.progressBar.setOrientation(Qt.Horizontal) verticalLayout.addWidget(self.progressBar) # Buttons buttonBox = QDialogButtonBox(self) buttonBox.setOrientation(Qt.Horizontal) buttonBox.setStandardButtons(QDialogButtonBox.Close) verticalLayout.addWidget(buttonBox) buttonBox.rejected.connect(self.__onClose) def __onClose(self): """triggered when the close button is clicked""" self.__cancelRequest = True if not self.__inProgress: self.close() def __buildParticipants(self): """Builds a list of participating files and dirs""" if self.__what in [ ImportsDiagramDialog.SingleBuffer, ImportsDiagramDialog.SingleFile ]: # File exists but could be modified self.__path = os.path.realpath(self.__path) self.__participantFiles.append(self.__path) return if self.__what == ImportsDiagramDialog.ProjectFiles: self.__scanProjectDirs() return # This is a recursive directory self.__path = os.path.realpath(self.__path) self.__scanDirForPythonFiles(self.__path + os.path.sep) def __scanDirForPythonFiles(self, path): """Scans the directory for the python files recursively""" for item in os.listdir(path): if item in [".svn", ".cvs", '.git', '.hg']: continue if os.path.isdir(path + item): self.__scanDirForPythonFiles(path + item + os.path.sep) continue if isPythonFile(path + item): self.__participantFiles.append(os.path.realpath(path + item)) def __scanProjectDirs(self): """Populates participant lists from the project files""" for fName in GlobalData().project.filesList: if isPythonFile(fName): self.__participantFiles.append(fName) def __addBoxInfo(self, box, info): """Adds information to the given box if so configured""" if info.docstring is not None: box.docstring = info.docstring.text if self.__options.includeClasses: for klass in info.classes: box.classes.append(klass) if self.__options.includeFuncs: for func in info.functions: box.funcs.append(func) if self.__options.includeGlobs: for glob in info.globals: box.globs.append(glob) if self.__options.includeConnText: for imp in info.imports: box.imports.append(imp) def __addDocstringBox(self, info, fName, modBoxName): """Adds a docstring box if needed""" if self.__options.includeDocs: if info.docstring is not None: docBox = DgmDocstring() docBox.docstring = info.docstring docBox.refFile = fName # Add the box and its connection docBoxName = self.dataModel.addDocstringBox(docBox) conn = DgmConnection() conn.kind = DgmConnection.ModuleDoc conn.source = modBoxName conn.target = docBoxName self.dataModel.addConnection(conn) # Add rank for better layout rank = DgmRank() rank.firstObj = modBoxName rank.secondObj = docBoxName self.dataModel.addRank(rank) def __getSytemWideImportDocstring(self, path): """Provides the system wide module docstring""" if isPythonFile(path): try: info = GlobalData().briefModinfoCache.get(path) if info.docstring is not None: return info.docstring.text except: pass return '' @staticmethod def __getModuleTitle(fName): """Extracts a module name out of the file name""" baseTitle = os.path.basename(fName).split('.')[0] if baseTitle != "__init__": return baseTitle # __init__ is not very descriptive. Add a top level dir. dirName = os.path.dirname(fName) topDir = os.path.basename(dirName) return topDir + "(" + baseTitle + ")" @staticmethod def __isLocalOrProject(fName, resolvedPath): """True if the module is a project one or is in the nested dirs""" if resolvedPath is None: return False if not os.path.isabs(resolvedPath): return False if GlobalData().project.isProjectFile(resolvedPath): return True resolvedDir = os.path.dirname(resolvedPath) baseDir = os.path.dirname(fName) return resolvedDir.startswith(baseDir) def __addSingleFileToDataModel(self, info, fName): """Adds a single file to the data model""" if fName.endswith('__init__.py'): if not info.classes and not info.functions and \ not info.globals and not info.imports: # Skip dummy init files return modBox = DgmModule() modBox.refFile = fName modBox.kind = DgmModule.ModuleOfInterest modBox.title = self.__getModuleTitle(fName) self.__addBoxInfo(modBox, info) modBoxName = self.dataModel.addModule(modBox) self.__addDocstringBox(info, fName, modBoxName) # Analyze what was imported resolvedImports, errors = resolveImports(fName, info.imports) if errors: message = 'Errors while analyzing ' + fName + ':' for err in errors: message += '\n ' + err logging.warning(message) for item in resolvedImports: importName = item[0] # from name resolvedPath = item[1] # 'built-in', None or absolute path importedNames = item[2] # list of strings impBox = DgmModule() impBox.title = importName if self.__isLocalOrProject(fName, resolvedPath): impBox.kind = DgmModule.OtherProjectModule impBox.refFile = resolvedPath if isPythonFile(resolvedPath): otherInfo = GlobalData().briefModinfoCache.get( resolvedPath) self.__addBoxInfo(impBox, otherInfo) else: if resolvedPath is None: # e.g. 'import sys' will have None for the path impBox.kind = DgmModule.UnknownModule elif os.path.isabs(resolvedPath): impBox.kind = DgmModule.SystemWideModule impBox.refFile = resolvedPath impBox.docstring = \ self.__getSytemWideImportDocstring(resolvedPath) else: # e.g. 'import time' will have 'built-in' in the path impBox.kind = DgmModule.BuiltInModule impBoxName = self.dataModel.addModule(impBox) impConn = DgmConnection() impConn.kind = DgmConnection.ModuleDependency impConn.source = modBoxName impConn.target = impBoxName if self.__options.includeConnText: for impWhat in importedNames: if impWhat: impConn.labels.append(impWhat) self.dataModel.addConnection(impConn) def __process(self): """Accumulation process""" # Intermediate working data self.__participantFiles = [] self.__projectImportDirs = [] self.__projectImportsCache = {} self.dataModel.clear() self.__inProgress = True try: self.infoLabel.setText('Building the list of files to analyze...') QApplication.processEvents() # Build the list of participating python files self.__buildParticipants() self.__projectImportDirs = \ GlobalData().project.getImportDirsAsAbsolutePaths() QApplication.processEvents() if self.__cancelRequest: QApplication.restoreOverrideCursor() self.close() return self.progressBar.setRange(0, len(self.__participantFiles)) index = 1 # Now, parse the files and build the diagram data model if self.__what == ImportsDiagramDialog.SingleBuffer: info = getBriefModuleInfoFromMemory(str(self.__buf)) self.__addSingleFileToDataModel(info, self.__path) else: infoSrc = GlobalData().briefModinfoCache for fName in self.__participantFiles: self.progressBar.setValue(index) self.infoLabel.setText('Analyzing ' + fName + "...") QApplication.processEvents() if self.__cancelRequest: QApplication.restoreOverrideCursor() self.dataModel.clear() self.close() return info = infoSrc.get(fName) self.__addSingleFileToDataModel(info, fName) index += 1 # The import caches and other working data are not needed anymore self.__participantFiles = None self.__projectImportDirs = None self.__projectImportsCache = None # Generating the graphviz layout self.infoLabel.setText('Generating layout using graphviz...') QApplication.processEvents() graph = getGraphFromDescriptionData(self.dataModel.toGraphviz()) graph.normalize(self.physicalDpiX(), self.physicalDpiY()) QApplication.processEvents() if self.__cancelRequest: QApplication.restoreOverrideCursor() self.dataModel.clear() self.close() return # Generate graphics scene self.infoLabel.setText('Generating graphics scene...') QApplication.processEvents() self.__buildGraphicsScene(graph) # Clear the data model self.dataModel = None except Exception as exc: QApplication.restoreOverrideCursor() logging.error(str(exc)) self.__inProgress = False self.__onClose() return QApplication.restoreOverrideCursor() self.infoLabel.setText('Done') QApplication.processEvents() self.__inProgress = False self.accept() def __buildGraphicsScene(self, graph): """Builds the QT graphics scene""" self.scene.clear() self.scene.setSceneRect(0, 0, graph.width, graph.height) for edge in graph.edges: # self.scene.addItem( GraphicsEdge( edge, self ) ) dataModelObj = self.dataModel.findConnection(edge.tail, edge.head) if dataModelObj is None: raise Exception("Cannot find the following connection: " + edge.tail + " -> " + edge.head) if dataModelObj.kind == DgmConnection.ModuleDoc: modObj = self.dataModel.findModule(dataModelObj.source) if modObj is None: raise Exception("Cannot find module object: " + dataModelObj.source) self.scene.addItem(ImportsDgmDocConn(edge, modObj)) continue if dataModelObj.kind == DgmConnection.ModuleDependency: # Find the source module object first modObj = self.dataModel.findModule(dataModelObj.source) if modObj is None: raise Exception("Cannot find module object: " + dataModelObj.source) self.scene.addItem( ImportsDgmDependConn(edge, modObj, dataModelObj)) if edge.label != "": self.scene.addItem(ImportsDgmEdgeLabel(edge, modObj)) continue raise Exception("Unexpected type of connection: " + str(dataModelObj.kind)) for node in graph.nodes: dataModelObj = self.dataModel.findModule(node.name) if dataModelObj is None: dataModelObj = self.dataModel.findDocstring(node.name) if dataModelObj is None: raise Exception("Cannot find object " + node.name) if isinstance(dataModelObj, DgmDocstring): self.scene.addItem( ImportsDgmDocNote(node, dataModelObj.refFile, dataModelObj.docstring)) continue # OK, this is a module rectangle. Switch by type of the module. if dataModelObj.kind == DgmModule.ModuleOfInterest: self.scene.addItem( ImportsDgmModuleOfInterest(node, dataModelObj.refFile, dataModelObj, self.physicalDpiX())) elif dataModelObj.kind == DgmModule.OtherProjectModule: self.scene.addItem( ImportsDgmOtherPrjModule(node, dataModelObj.refFile, dataModelObj, self.physicalDpiX())) elif dataModelObj.kind == DgmModule.SystemWideModule: self.scene.addItem( ImportsDgmSystemWideModule(node, dataModelObj.refFile, dataModelObj.docstring)) elif dataModelObj.kind == DgmModule.BuiltInModule: self.scene.addItem(ImportsDgmBuiltInModule(node)) elif dataModelObj.kind == DgmModule.UnknownModule: self.scene.addItem(ImportsDgmUnknownModule(node)) else: raise Exception("Unexpected type of module: " + str(dataModelObj.kind)) tooltip = dataModelObj.getTooltip() if tooltip: pixmap = getPixmap('diagramdoc.png') docItem = QGraphicsPixmapItem(pixmap) docItem.setToolTip(tooltip) posX = node.posX + node.width / 2.0 - pixmap.width() / 2.0 posY = node.posY - node.height / 2.0 - pixmap.height() / 2.0 docItem.setPos(posX, posY) self.scene.addItem(docItem)
class IgnoredExceptionsViewer(QWidget): """Implements the client exceptions viewer for a debugger""" def __init__(self, parent=None): QWidget.__init__(self, parent) self.__createPopupMenu() self.__createLayout() self.__ignored = [] self.__currentItem = None GlobalData().project.sigProjectChanged.connect(self.__onProjectChanged) if not Settings()['showIgnoredExcViewer']: self.__onShowHide(True) def __createPopupMenu(self): """Creates the popup menu""" self.__excptMenu = QMenu() self.__removeMenuItem = self.__excptMenu.addAction( getIcon('ignexcptdel.png'), "Remove from ignore list", self.__onRemoveFromIgnore) def __createLayout(self): """Creates the widget layout""" verticalLayout = QVBoxLayout(self) verticalLayout.setContentsMargins(0, 0, 0, 0) verticalLayout.setSpacing(0) self.__excptLabel = QLabel("Ignored exception types", self) self.headerFrame = QFrame() self.headerFrame.setObjectName('ignexcpt') self.headerFrame.setStyleSheet('QFrame#ignexcpt {' + getLabelStyle(self.__excptLabel) + '}') self.headerFrame.setFixedHeight(HEADER_HEIGHT) expandingSpacer = QSpacerItem(10, 10, QSizePolicy.Expanding) self.__showHideButton = QToolButton() self.__showHideButton.setAutoRaise(True) self.__showHideButton.setIcon(getIcon('less.png')) self.__showHideButton.setFixedSize(HEADER_BUTTON, HEADER_BUTTON) self.__showHideButton.setToolTip("Hide ignored exceptions list") self.__showHideButton.setFocusPolicy(Qt.NoFocus) self.__showHideButton.clicked.connect(self.__onShowHide) headerLayout = QHBoxLayout() headerLayout.setContentsMargins(0, 0, 0, 0) headerLayout.addSpacing(3) headerLayout.addWidget(self.__excptLabel) headerLayout.addSpacerItem(expandingSpacer) headerLayout.addWidget(self.__showHideButton) self.headerFrame.setLayout(headerLayout) self.exceptionsList = QTreeWidget(self) self.exceptionsList.setSortingEnabled(False) self.exceptionsList.setAlternatingRowColors(True) self.exceptionsList.setRootIsDecorated(False) self.exceptionsList.setItemsExpandable(True) self.exceptionsList.setUniformRowHeights(True) self.exceptionsList.setSelectionMode(QAbstractItemView.SingleSelection) self.exceptionsList.setSelectionBehavior(QAbstractItemView.SelectRows) self.exceptionsList.setItemDelegate(NoOutlineHeightDelegate(4)) self.exceptionsList.setContextMenuPolicy(Qt.CustomContextMenu) self.exceptionsList.customContextMenuRequested.connect( self.__showContextMenu) self.exceptionsList.itemSelectionChanged.connect( self.__onSelectionChanged) self.exceptionsList.setHeaderLabels(["Exception type"]) self.__excTypeEdit = QLineEdit() self.__excTypeEdit.setFixedHeight(26) self.__excTypeEdit.textChanged.connect(self.__onNewFilterChanged) self.__excTypeEdit.returnPressed.connect(self.__onAddExceptionFilter) self.__addButton = QPushButton("Add") # self.__addButton.setFocusPolicy(Qt.NoFocus) self.__addButton.setEnabled(False) self.__addButton.clicked.connect(self.__onAddExceptionFilter) expandingSpacer2 = QWidget() expandingSpacer2.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.__removeButton = QAction(getIcon('delitem.png'), "Remove selected exception type", self) self.__removeButton.triggered.connect(self.__onRemoveFromIgnore) self.__removeButton.setEnabled(False) fixedSpacer1 = QWidget() fixedSpacer1.setFixedWidth(5) self.__removeAllButton = QAction(getIcon('ignexcptdelall.png'), "Remove all the exception types", self) self.__removeAllButton.triggered.connect(self.__onRemoveAllFromIgnore) self.__removeAllButton.setEnabled(False) self.toolbar = QToolBar() self.toolbar.setOrientation(Qt.Horizontal) self.toolbar.setMovable(False) self.toolbar.setAllowedAreas(Qt.TopToolBarArea) self.toolbar.setIconSize(QSize(16, 16)) self.toolbar.setFixedHeight(28) self.toolbar.setContentsMargins(0, 0, 0, 0) self.toolbar.addWidget(expandingSpacer2) self.toolbar.addAction(self.__removeButton) self.toolbar.addWidget(fixedSpacer1) self.toolbar.addAction(self.__removeAllButton) addLayout = QHBoxLayout() addLayout.setContentsMargins(1, 1, 1, 1) addLayout.setSpacing(1) addLayout.addWidget(self.__excTypeEdit) addLayout.addWidget(self.__addButton) verticalLayout.addWidget(self.headerFrame) verticalLayout.addWidget(self.toolbar) verticalLayout.addWidget(self.exceptionsList) verticalLayout.addLayout(addLayout) def clear(self): """Clears the content""" self.exceptionsList.clear() self.__excTypeEdit.clear() self.__addButton.setEnabled(False) self.__ignored = [] self.__currentItem = None self.__updateTitle() def __onShowHide(self, startup=False): """Triggered when show/hide button is clicked""" if startup or self.exceptionsList.isVisible(): self.exceptionsList.setVisible(False) self.__excTypeEdit.setVisible(False) self.__addButton.setVisible(False) self.__removeButton.setVisible(False) self.__removeAllButton.setVisible(False) self.__showHideButton.setIcon(getIcon('more.png')) self.__showHideButton.setToolTip("Show ignored exceptions list") self.__minH = self.minimumHeight() self.__maxH = self.maximumHeight() self.setMinimumHeight(self.headerFrame.height()) self.setMaximumHeight(self.headerFrame.height()) Settings()['showIgnoredExcViewer'] = False else: self.exceptionsList.setVisible(True) self.__excTypeEdit.setVisible(True) self.__addButton.setVisible(True) self.__removeButton.setVisible(True) self.__removeAllButton.setVisible(True) self.__showHideButton.setIcon(getIcon('less.png')) self.__showHideButton.setToolTip("Hide ignored exceptions list") self.setMinimumHeight(self.__minH) self.setMaximumHeight(self.__maxH) Settings()['showIgnoredExcViewer'] = True def __onSelectionChanged(self): """Triggered when the current item is changed""" selected = list(self.exceptionsList.selectedItems()) if selected: self.__currentItem = selected[0] self.__removeButton.setEnabled(True) else: self.__currentItem = None self.__removeButton.setEnabled(False) def __showContextMenu(self, coord): """Shows the frames list context menu""" contextItem = self.exceptionsList.itemAt(coord) if contextItem is not None: self.__currentItem = contextItem self.__excptMenu.popup(QCursor.pos()) def __updateTitle(self): """Updates the section title""" count = self.exceptionsList.topLevelItemCount() if count == 0: self.__excptLabel.setText("Ignored exception types") else: self.__excptLabel.setText("Ignored exception types (total: " + str(count) + ")") self.__removeAllButton.setEnabled(count != 0) def __onProjectChanged(self, what): """Triggered when a project is changed""" if what != CodimensionProject.CompleteProject: return self.clear() project = GlobalData().project if project.isLoaded(): self.__ignored = list(project.exceptionFilters) else: self.__ignored = Settings()['ignoredExceptions'] for exceptionType in self.__ignored: item = QTreeWidgetItem(self.exceptionsList) item.setText(0, exceptionType) self.__updateTitle() def __onNewFilterChanged(self, text): """Triggered when the text is changed""" text = str(text).strip() if text == "": self.__addButton.setEnabled(False) return if " " in text: self.__addButton.setEnabled(False) return if text in self.__ignored: self.__addButton.setEnabled(False) return self.__addButton.setEnabled(True) def __onAddExceptionFilter(self): """Adds an item into the ignored exceptions list""" text = self.__excTypeEdit.text().strip() self.addExceptionFilter(text) def addExceptionFilter(self, excType): """Adds a new item into the ignored exceptions list""" if excType == "": return if " " in excType: return if excType in self.__ignored: return item = QTreeWidgetItem(self.exceptionsList) item.setText(0, excType) project = GlobalData().project if project.isLoaded(): project.addExceptionFilter(excType) else: Settings().addExceptionFilter(excType) self.__ignored.append(excType) self.__updateTitle() def __onRemoveFromIgnore(self): """Removes an item from the ignored exception types list""" if self.__currentItem is None: return text = self.__currentItem.text(0) # Find the item index and remove it index = 0 while True: if self.exceptionsList.topLevelItem(index).text(0) == text: self.exceptionsList.takeTopLevelItem(index) break index += 1 project = GlobalData().project if project.isLoaded(): project.deleteExceptionFilter(text) else: Settings().deleteExceptionFilter(text) self.__ignored.remove(text) self.__updateTitle() def __onRemoveAllFromIgnore(self): """Triggered when all the ignored exceptions should be deleted""" self.clear() project = GlobalData().project if project.isLoaded(): project.setExceptionFilters([]) else: Settings().setExceptionFilters([]) def isIgnored(self, exceptionType): """Returns True if this exception type should be ignored""" return exceptionType in self.__ignored
class NotUsedAnalysisProgress(QDialog): """Progress of the not used analysis""" def __init__(self, path, newSearch=True): QDialog.__init__(self, GlobalData().mainWindow) path = os.path.abspath(path) if not os.path.exists(path): raise Exception('Dead code analysis path must exist. ' 'The provide path "' + path + '" does not.') self.__path = path self.__newSearch = newSearch self.candidates = None self.__cancelRequest = False self.__inProgress = False self.__infoLabel = None self.__foundLabel = None self.__found = 0 # Number of found self.__createLayout() title = 'Dead code analysis for ' if os.path.isdir(path): project = GlobalData().project if project.isLoaded() and project.getProjectDir() == path: title += 'all project files' else: title += 'dir ' + os.path.basename(os.path.normpath(path)) else: title += os.path.basename(path) if not self.__newSearch: title += ' (do again)' self.setWindowTitle(title) self.__updateFoundLabel() def exec_(self): """Executes the dialog""" QTimer.singleShot(1, self.__process) QDialog.exec_(self) def keyPressEvent(self, event): """Processes the ESC key specifically""" if event.key() == Qt.Key_Escape: self.__onClose() else: QDialog.keyPressEvent(self, event) def __updateFoundLabel(self): """Updates the found label""" text = "Found: " + str(self.__found) + " candidate" if self.__found != 1: text += "s" self.__foundLabel.setText(text) def __createLayout(self): """Creates the dialog layout""" self.resize(450, 20) self.setSizeGripEnabled(True) verticalLayout = QVBoxLayout(self) # Note label noteLabel = QLabel( "<b>Note</b>: the analysis is " "suggestive and not precise. " "Use the results with caution.\n", self) verticalLayout.addWidget(noteLabel) # Info label self.__infoLabel = QLabel(self) verticalLayout.addWidget(self.__infoLabel) # Found label self.__foundLabel = QLabel(self) verticalLayout.addWidget(self.__foundLabel) # Buttons buttonBox = QDialogButtonBox(self) buttonBox.setOrientation(Qt.Horizontal) buttonBox.setStandardButtons(QDialogButtonBox.Close) verticalLayout.addWidget(buttonBox) buttonBox.rejected.connect(self.__onClose) def __onClose(self): """triggered when the close button is clicked""" self.__cancelRequest = True if not self.__inProgress: self.close() def __run(self): """Runs vulture""" errTmp = tempfile.mkstemp() errStream = os.fdopen(errTmp[0]) process = Popen(['vulture', self.__path], stdin=PIPE, stdout=PIPE, stderr=errStream) process.stdin.close() processStdout = process.stdout.read() process.stdout.close() errStream.seek(0) err = errStream.read() errStream.close() process.wait() try: os.unlink(errTmp[1]) except: pass return processStdout.decode(DEFAULT_ENCODING), err.strip() def __process(self): """Analysis process""" self.__inProgress = True mainWindow = GlobalData().mainWindow QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) # return code gives really nothing. So the error in running the utility # is detected by the stderr content. # Also, there could be a mix of messages for a project. Some files # could have syntax errors - there will be messages on stderr. The # other files are fine so there will be messages on stdout stdout, stderr = self.__run() self.candidates = [] for line in stdout.splitlines(): line = line.strip() if line: # Line is like file.py:2: unused variable 'a' (60% confidence) try: startIndex = line.find(':') if startIndex < 0: continue endIndex = line.find(':', startIndex + 1) if endIndex < 0: continue fileName = line[:startIndex] startIndex = line.find(':') if startIndex < 0: continue endIndex = line.find(':', startIndex + 1) if endIndex < 0: continue fileName = os.path.abspath(line[:startIndex]) lineno = int(line[startIndex + 1:endIndex]) message = line[endIndex + 1:].strip() except: continue index = getSearchItemIndex(self.candidates, fileName) if index < 0: widget = mainWindow.getWidgetForFileName(fileName) if widget is None: uuid = '' else: uuid = widget.getUUID() newItem = ItemToSearchIn(fileName, uuid) self.candidates.append(newItem) index = len(self.candidates) - 1 self.candidates[index].addMatch('', lineno, message) self.__found += 1 self.__updateFoundLabel() QApplication.processEvents() if self.__newSearch: # Do the action only for the new search. # Redo action will handle the results on its own if self.__found == 0: if stderr: logging.error('Error running vulture for ' + self.__path + ':\n' + stderr) else: logging.info('No unused candidates found') else: mainWindow.displayFindInFiles(VultureSearchProvider.getName(), self.candidates, {'path': self.__path}) QApplication.restoreOverrideCursor() self.__infoLabel.setText('Done') self.__inProgress = False self.accept()
class SVNUpdateProgress(QDialog): """Progress of the svn update command""" def __init__(self, plugin, client, path, recursively, rev, parent=None): QDialog.__init__(self, parent) self.__cancelRequest = False self.__inProcess = False self.__plugin = plugin self.__client = client self.__path = path self.__recursively = recursively self.__rev = rev self.__updatedPaths = [] self.__createLayout() self.setWindowTitle("SVN Update") QTimer.singleShot(0, self.__process) def keyPressEvent(self, event): """Processes the ESC key specifically""" if event.key() == Qt.Key_Escape: self.__cancelRequest = True self.__infoLabel.setText("Cancelling...") QApplication.processEvents() def __createLayout(self): """Creates the dialog layout""" self.resize(450, 20) self.setSizeGripEnabled(True) verticalLayout = QVBoxLayout(self) verticalLayout.addWidget(QLabel("Updating '" + self.__path + "':")) self.__infoLabel = QLabel(self) verticalLayout.addWidget(self.__infoLabel) buttonBox = QDialogButtonBox(self) buttonBox.setOrientation(Qt.Horizontal) buttonBox.setStandardButtons(QDialogButtonBox.Close) verticalLayout.addWidget(buttonBox) buttonBox.rejected.connect(self.__onClose) def __onClose(self): """Triggered when the close button is clicked""" self.__cancelRequest = True self.__infoLabel.setText("Cancelling...") QApplication.processEvents() def closeEvent(self, event): """Window close event handler""" if self.__inProcess: self.__cancelRequest = True self.__infoLabel.setText("Cancelling...") QApplication.processEvents() event.ignore() else: event.accept() def __cancelCallback(self): """Called by pysvn regularly""" QApplication.processEvents() return self.__cancelRequest def __notifyCallback(self, event): """Called by pysvn. event is a dictionary""" if 'path' not in event: return if not event['path']: return message = None path = event['path'] if os.path.isdir(path) and not path.endswith(os.path.sep): path += os.path.sep if event['action'] == pysvn.wc_notify_action.update_completed: message = path + " updated to revision " + str(event['revision'].number) elif event['action'] == pysvn.wc_notify_action.update_started: message = "updating " + path + ":" else: self.__updatedPaths.append(path) action = notifyActionToString(event['action']) if action is not None and action != "unknown": message = " " + action + " " + path if event['mime_type'] == "application/octet-stream": message += " (binary)" if message: self.__infoLabel.setText(message.strip()) logging.info(message) QApplication.processEvents() def __process(self): """Update process""" self.__client.callback_cancel = self.__cancelCallback self.__client.callback_notify = self.__notifyCallback try: self.__inProcess = True self.__client.update(self.__path, self.__recursively, self.__rev) self.__inProcess = False except pysvn.ClientError as exc: errorCode = exc.args[1][0][1] if errorCode == pysvn.svn_err.cancelled: logging.info("Updating cancelled") else: message = exc.args[0] logging.error(message) self.__inProcess = False self.close() return except Exception as exc: logging.error(str(exc)) self.__inProcess = False self.close() return except: logging.error("Unknown error") self.__inProcess = False self.close() return for updatedPath in self.__updatedPaths: self.__plugin.notifyPathChanged(updatedPath) if self.__cancelRequest: self.close() else: self.accept() return
class BreakPointViewer(QWidget): """Implements the break point viewer for a debugger""" def __init__(self, parent, bpointsModel): QWidget.__init__(self, parent) self.__currentItem = None self.__createLayout(bpointsModel) GlobalData().project.sigProjectChanged.connect(self.__onProjectChanged) GlobalData().project.sigProjectAboutToUnload.connect( self.__onProjectAboutToUnload) self.bpointsList.sigSelectionChanged.connect(self.__onSelectionChanged) bpointsModel.sigBreakpoinsChanged.connect(self.__onModelChanged) def setFocus(self): """Sets the widget focus""" self.bpointsList.setFocus() def __createLayout(self, bpointsModel): """Creates the widget layout""" verticalLayout = QVBoxLayout(self) verticalLayout.setContentsMargins(0, 0, 0, 0) verticalLayout.setSpacing(0) self.headerFrame = QFrame() self.headerFrame.setObjectName('bpheader') self.headerFrame.setStyleSheet('QFrame#bpheader {' + getLabelStyle(self) + '}') self.headerFrame.setFixedHeight(HEADER_HEIGHT) self.__breakpointLabel = QLabel("Breakpoints") headerLayout = QHBoxLayout() headerLayout.setContentsMargins(0, 0, 0, 0) headerLayout.addSpacing(3) headerLayout.addWidget(self.__breakpointLabel) self.headerFrame.setLayout(headerLayout) self.bpointsList = BreakPointView(self, bpointsModel) self.__editButton = QAction(getIcon('bpprops.png'), "Edit breakpoint properties", self) self.__editButton.triggered.connect(self.__onEdit) self.__editButton.setEnabled(False) self.__jumpToCodeButton = QAction(getIcon('gotoline.png'), "Jump to the code", self) self.__jumpToCodeButton.triggered.connect(self.__onJumpToCode) self.__jumpToCodeButton.setEnabled(False) self.__enableButton = QAction(getIcon('bpenable.png'), "Enable selected breakpoint", self) self.__enableButton.triggered.connect(self.__onEnableDisable) self.__enableButton.setEnabled(False) self.__disableButton = QAction(getIcon('bpdisable.png'), "Disable selected breakpoint", self) self.__disableButton.triggered.connect(self.__onEnableDisable) self.__disableButton.setEnabled(False) self.__enableAllButton = QAction(getIcon('bpenableall.png'), "Enable all the breakpoint", self) self.__enableAllButton.triggered.connect(self.__onEnableAll) self.__enableAllButton.setEnabled(False) self.__disableAllButton = QAction(getIcon('bpdisableall.png'), "Disable all the breakpoint", self) self.__disableAllButton.triggered.connect(self.__onDisableAll) self.__disableAllButton.setEnabled(False) self.__delButton = QAction(getIcon('delitem.png'), "Delete selected breakpoint", self) self.__delButton.triggered.connect(self.__onDel) self.__delButton.setEnabled(False) self.__delAllButton = QAction(getIcon('bpdelall.png'), "Delete all the breakpoint", self) self.__delAllButton.triggered.connect(self.__onDelAll) self.__delAllButton.setEnabled(False) # Toolbar self.toolbar = QToolBar() self.toolbar.setOrientation(Qt.Horizontal) self.toolbar.setMovable(False) self.toolbar.setAllowedAreas(Qt.TopToolBarArea) self.toolbar.setIconSize(QSize(16, 16)) self.toolbar.setFixedHeight(28) self.toolbar.setContentsMargins(0, 0, 0, 0) self.toolbar.addAction(self.__editButton) self.toolbar.addAction(self.__jumpToCodeButton) fixedSpacer2 = QWidget() fixedSpacer2.setFixedWidth(5) self.toolbar.addWidget(fixedSpacer2) self.toolbar.addAction(self.__enableButton) self.toolbar.addAction(self.__enableAllButton) fixedSpacer3 = QWidget() fixedSpacer3.setFixedWidth(5) self.toolbar.addWidget(fixedSpacer3) self.toolbar.addAction(self.__disableButton) self.toolbar.addAction(self.__disableAllButton) expandingSpacer = QWidget() expandingSpacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) fixedSpacer4 = QWidget() fixedSpacer4.setFixedWidth(5) self.toolbar.addWidget(fixedSpacer4) self.toolbar.addWidget(expandingSpacer) self.toolbar.addAction(self.__delButton) fixedSpacer5 = QWidget() fixedSpacer5.setFixedWidth(5) self.toolbar.addWidget(fixedSpacer5) self.toolbar.addAction(self.__delAllButton) verticalLayout.addWidget(self.headerFrame) verticalLayout.addWidget(self.toolbar) verticalLayout.addWidget(self.bpointsList) def clear(self): """Clears the content""" self.__onDelAll() self.__updateBreakpointsLabel() self.__currentItem = None def __updateBreakpointsLabel(self): """Updates the breakpoints header label""" enableCount, \ disableCount = self.bpointsList.model().sourceModel().getCounts() total = enableCount + disableCount if total > 0: self.__breakpointLabel.setText("Breakpoints (total: " + str(total) + ")") else: self.__breakpointLabel.setText("Breakpoints") def __onProjectChanged(self, what): """Triggered when a project is changed""" if what != CodimensionProject.CompleteProject: return self.clear() model = self.bpointsList.model().sourceModel() project = GlobalData().project if project.isLoaded(): bpoints = project.breakpoints else: bpoints = Settings().breakpoints for bpoint in bpoints: newBpoint = Breakpoint() try: if not newBpoint.deserialize(bpoint): # Non valid continue except: continue # Need to check if it still points to a breakable line line = newBpoint.getLineNumber() fileName = newBpoint.getAbsoluteFileName() breakableLines = getBreakpointLines(fileName, None, True) if breakableLines is not None and line in breakableLines: model.addBreakpoint(newBpoint) else: logging.warning("Breakpoint at " + fileName + ":" + str(line) + " does not point to a breakable " "line anymore (the file is invalid or was " "modified outside of the " "IDE etc.). The breakpoint is deleted.") def __onProjectAboutToUnload(self): """Triggered before the project is unloaded""" self.__serializeBreakpoints() def __serializeBreakpoints(self): """Saves the breakpoints into a file""" model = self.bpointsList.model().sourceModel() project = GlobalData().project if project.isLoaded(): project.breakpoints = model.serialize() else: Settings().breakpoints = model.serialize() def __onSelectionChanged(self, index): """Triggered when the current item is changed""" if index.isValid(): srcModel = self.bpointsList.model().sourceModel() sindex = self.bpointsList.toSourceIndex(index) self.__currentItem = srcModel.getBreakPointByIndex(sindex) else: self.__currentItem = None self.__updateButtons() def __updateButtons(self): """Updates the buttons status""" enableCount, \ disableCount = self.bpointsList.model().sourceModel().getCounts() if self.__currentItem is None: self.__editButton.setEnabled(False) self.__enableButton.setEnabled(False) self.__disableButton.setEnabled(False) self.__jumpToCodeButton.setEnabled(False) self.__delButton.setEnabled(False) else: self.__editButton.setEnabled(True) self.__enableButton.setEnabled(not self.__currentItem.isEnabled()) self.__disableButton.setEnabled(self.__currentItem.isEnabled()) self.__jumpToCodeButton.setEnabled(True) self.__delButton.setEnabled(True) self.__enableAllButton.setEnabled(disableCount > 0) self.__disableAllButton.setEnabled(enableCount > 0) self.__delAllButton.setEnabled(enableCount + disableCount > 0) def __onEnableDisable(self): """Triggered when a breakpoint should be enabled/disabled""" if self.__currentItem is not None: if self.__currentItem.isEnabled(): self.bpointsList.disableBreak() else: self.bpointsList.enableBreak() def __onEdit(self): """Triggered when a breakpoint should be edited""" if self.__currentItem is None: return dlg = BreakpointEditDialog(self.__currentItem) if dlg.exec_() == QDialog.Accepted: newBpoint = dlg.getData() if newBpoint == self.__currentItem: return model = self.bpointsList.model().sourceModel() index = model.getBreakPointIndex( self.__currentItem.getAbsoluteFileName(), self.__currentItem.getLineNumber()) model.setBreakPointByIndex(index, newBpoint) self.bpointsList.layoutDisplay() def __onJumpToCode(self): """Triggered when should jump to source""" if self.__currentItem is None: return self.bpointsList.jumpToCode(self.__currentItem.getAbsoluteFileName(), self.__currentItem.getLineNumber()) def __onEnableAll(self): """Triggered when all the breakpoints should be enabled""" self.bpointsList.enableAllBreaks() def __onDisableAll(self): """Triggered when all the breakpoints should be disabled""" self.bpointsList.disableAllBreaks() def __onDel(self): """Triggered when a breakpoint should be deleted""" if self.__currentItem is not None: self.bpointsList.deleteBreak() def __onDelAll(self): """Triggered when all the breakpoints should be deleted""" self.bpointsList.deleteAllBreaks() def __onModelChanged(self): """Triggered when something has changed in any of the breakpoints""" self.__updateBreakpointsLabel() self.__updateButtons() self.bpointsList.layoutDisplay() self.__serializeBreakpoints()
class MatchTooltip(QFrame): """Custom tooltip""" def __init__(self): QFrame.__init__(self) # Avoid the border around the window self.setWindowFlags(Qt.SplashScreen | Qt.WindowStaysOnTopHint | Qt.X11BypassWindowManagerHint) # Make the frame nice looking self.setFrameShape(QFrame.StyledPanel) self.setLineWidth(2) self.__cellHeight = 20 # default self.__screenWidth = 600 # default # On slow network connections when XServer is used the cursor movement # is delivered with a considerable delay which causes improper tooltip # displaying. This global variable prevents improper displaying. self.__inside = False self.info = None self.location = None self.__createLayout() # The item the tooltip is for self.item = None # The timer which shows the tooltip. The timer is controlled from # outside of the class. self.tooltipTimer = QTimer(self) self.tooltipTimer.setSingleShot(True) self.tooltipTimer.timeout.connect(self.__onTimer) self.startPosition = None def setCellHeight(self, height): """Sets the cell height""" self.__cellHeight = height def setScreenWidth(self, width): """Sets the screen width""" self.__screenWidth = width def setInside(self, inside): """Sets the inside flag""" self.__inside = inside def isInside(self): """Provides the inside flag""" return self.__inside def __createLayout(self): """Creates the tooltip layout""" verticalLayout = QVBoxLayout(self) verticalLayout.setContentsMargins(0, 0, 0, 0) self.info = QLabel() self.info.setAutoFillBackground(True) self.info.setFont(getZoomedMonoFont()) self.info.setFrameShape(QFrame.StyledPanel) self.info.setStyleSheet('padding: 4px') verticalLayout.addWidget(self.info) self.location = QLabel() # verticalLayout.addWidget(self.location) def setText(self, text): """Sets the tooltip text""" self.info.setFont(getZoomedMonoFont()) self.info.setText(text) def setLocation(self, text): """Sets the file name and line at the bottom""" self.location.setText(text) def setModified(self, status): """Sets the required tooltip background""" palette = self.info.palette() if status: # Reddish palette.setColor(QPalette.Background, QColor(255, 227, 227)) else: # Blueish palette.setColor(QPalette.Background, QColor(224, 236, 255)) self.info.setPalette(palette) def setItem(self, item): """Sets the item the tooltip is shown for""" self.item = item def __getTooltipPos(self): """Calculates the tooltip position - above the row""" pos = QCursor.pos() if pos.x() + self.sizeHint().width() >= self.__screenWidth: pos.setX(self.__screenWidth - self.sizeHint().width() - 2) pos.setY(pos.y() - self.__cellHeight - 1 - self.sizeHint().height()) return pos def __onTimer(self): """Triggered by the show tooltip timer""" currentPos = QCursor.pos() if abs(currentPos.x() - self.startPosition.x()) <= 2 and \ abs(currentPos.y() - self.startPosition.y()) <= 2: # No movement since last time, show the tooltip self.show() return # There item has not been changed, but the position within it was # So restart the timer, but for shorter self.startPosition = currentPos self.tooltipTimer.start(400) def startShowTimer(self): """Memorizes the cursor position and starts the timer""" self.tooltipTimer.stop() self.startPosition = QCursor.pos() self.tooltipTimer.start(500) # 0.5 sec def show(self): """Shows the tooltip at the proper position""" QToolTip.hideText() QApplication.processEvents() if self.__inside: self.move(self.__getTooltipPos()) self.raise_() QFrame.show(self)