Пример #1
0
    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'])
Пример #2
0
    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
Пример #3
0
    def __init__(self, path, parent=None):
        QDialog.__init__(self, parent)

        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.__cancelRequest = False
        self.__inProgress = False

        self.__infoLabel = None
        self.__foundLabel = None
        self.__found = 0        # Number of found

        self.__createLayout()
        if os.path.isdir(path):
            self.setWindowTitle('Dead code analysis for all project files')
        else:
            self.setWindowTitle('Dead code analysis for ' +
                                os.path.basename(path))
        self.__updateFoundLabel()
        QTimer.singleShot(0, self.__process)
Пример #4
0
    def __init__(self, verbose, fName, warning):
        QMainWindow.__init__(self)

        self.logWidget = None
        self.view = None
        self.scene = None
        self.fName = fName
        self.verbose = verbose
        self.cFlow = None

        self.resize(1400, 800)

        self.updateWindowTitle()
        self.statusBar()
        self.createToolbar()
        self.createLogWindow()
        self.createGraphicsView()

        self.setCentralWidget(self.view)

        if verbose:
            self.logMessage("Using cdmcfparser version " + VERSION)

        if warning:
            self.logMessage(warning)

        if fName:
            # To yeld the main message processing loop
            kickOffTimer = QTimer()
            kickOffTimer.singleShot(200, self.proceedWithFile)
Пример #5
0
    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)
Пример #6
0
    def __init__(self, editor, parent):
        QFrame.__init__(self, parent)
        self.__editor = editor
        self.__parentWidget = parent

        # It is always not visible at the beginning because there is no
        # editor content at the start
        self.setVisible(False)

        # There is no parser info used to display values
        self.__currentInfo = None
        self.__currentIconState = self.STATE_UNKNOWN
        self.__connected = False

        # List of PathElement starting after the global scope
        self.__path = []

        self.__createLayout()

        # Create the update timer
        self.__updateTimer = QTimer(self)
        self.__updateTimer.setSingleShot(True)
        self.__updateTimer.timeout.connect(self.updateBar)

        # Connect to the change file type signal
        mainWindow = GlobalData().mainWindow
        editorsManager = mainWindow.editorsManagerWidget.editorsManager
        editorsManager.sigFileTypeChanged.connect(self.__onFileTypeChanged)
Пример #7
0
    def __collectGarbage(self, ignored=None):
        """Initiates the delayed garbage collection"""
        del ignored  # unused argument

        # The sigTabClosed is generated before the tab is actually closed but
        # when it is for sure it will be closed. At the on closing a tab the
        # status bar is cleared. So the GC needs to be delayed a bit so that it
        # is not cleared right away
        QTimer.singleShot(5, self.__delayedCollectGarbage)
Пример #8
0
 def __handleStack(self, params):
     """Handles METHOD_STACK"""
     stack = params['stack']
     if self.__stopAtFirstLine:
         topFrame = stack[0]
         self.sigClientLine.emit(topFrame[0], int(topFrame[1]), True)
         self.sigClientStack.emit(stack)
     else:
         self.__stopAtFirstLine = True
         QTimer.singleShot(0, self.remoteContinue)
Пример #9
0
    def __handleLine(self, params):
        """Handles METHOD_LINE"""
        stack = params['stack']
        if self.__stopAtFirstLine:
            topFrame = stack[0]
            self.sigClientLine.emit(topFrame[0], int(topFrame[1]), False)
            self.sigClientStack.emit(stack)
        else:
            self.__stopAtFirstLine = True
            QTimer.singleShot(0, self.remoteContinue)

        self.__changeDebuggerState(self.STATE_IN_IDE)
Пример #10
0
    def __init__(self, bgcolor, fgcolor, bordercolor, parent=None):
        """colors are instances of QColor"""
        QDialog.__init__(self, parent)
        self.setWindowTitle('Custom colors')

        self.__createLayout()
        self.__bgColorButton.setColor(bgcolor)
        self.__bgColorButton.sigColorChanged.connect(self.__onColorChanged)
        self.__fgColorButton.setColor(fgcolor)
        self.__fgColorButton.sigColorChanged.connect(self.__onColorChanged)
        self.__borderColorButton.setColor(bordercolor)
        self.__borderColorButton.sigColorChanged.connect(self.__onColorChanged)

        QTimer.singleShot(1, self.__onColorChanged)
Пример #11
0
    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)
Пример #12
0
    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)
Пример #13
0
    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)
Пример #14
0
    def __init__(self, mainWindow):
        QObject.__init__(self)
        self.__mainWindow = mainWindow
        self.__processes = []
        self.__prologueProcesses = []

        self.__tcpServer = QTcpServer()
        self.__tcpServer.newConnection.connect(self.__newConnection)
        self.__tcpServer.listen(QHostAddress.LocalHost)

        self.__waitTimer = QTimer(self)
        self.__waitTimer.setSingleShot(True)
        self.__waitTimer.timeout.connect(self.__onWaitTimer)

        self.__prologueTimer = QTimer(self)
        self.__prologueTimer.setSingleShot(True)
        self.__prologueTimer.timeout.connect(self.__onPrologueTimer)
Пример #15
0
    def __init__(self, parent, debugger):
        self._parent = parent
        QutepartWrapper.__init__(self, parent)
        EditorContextMenuMixin.__init__(self)

        self.setAttribute(Qt.WA_KeyCompression)
        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)

        skin = GlobalData().skin
        self.setPaper(skin['nolexerPaper'])
        self.setColor(skin['nolexerColor'])
        self.currentLineColor = skin['currentLinePaper']

        self.onTextZoomChanged()
        self.__initMargins(debugger)

        self.cursorPositionChanged.connect(self._onCursorPositionChanged)

        self.__skipChangeCursor = False

        self.__openedLine = None

        self.setFocusPolicy(Qt.StrongFocus)
        self.indentWidth = 4

        self.updateSettings()

        # Completion support
        self.__completionPrefix = ''
        self.__completionLine = -1
        self.__completionPos = -1
        self.__completer = CodeCompleter(self)
        self.__inCompletion = False
        self.__completer.activated.connect(self.insertCompletion)
        self.__lastTabPosition = None

        # Calltip support
        self.__calltip = None
        self.__callPosition = None
        self.__calltipTimer = QTimer(self)
        self.__calltipTimer.setSingleShot(True)
        self.__calltipTimer.timeout.connect(self.__onCalltipTimer)

        self.__initHotKeys()
        self.installEventFilter(self)
Пример #16
0
    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)
Пример #17
0
    def __init__(self, bgcolor, fgcolor, bordercolor, parent=None):
        """colors are instances of QColor"""
        QDialog.__init__(self, parent)
        self.setWindowTitle('Custom colors')

        self.__createLayout()
        self.__bgColorButton.setColor(bgcolor)
        self.__bgColorButton.sigColorChanged.connect(self.__onColorChanged)
        self.__fgColorButton.setColor(fgcolor)
        self.__fgColorButton.sigColorChanged.connect(self.__onColorChanged)
        if bordercolor is None:
            self.__borderColorButton.setEnabled(False)
            self.__borderColorButton.setToolTip(
                'Border colors are not supported for docstrings')
            self.__borderColorButton.setIcon(getIcon('warning.png'))
        else:
            self.__borderColorButton.setColor(bordercolor)
            self.__borderColorButton.sigColorChanged.connect(
                self.__onColorChanged)

        QTimer.singleShot(1, self.__onColorChanged)
Пример #18
0
    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)
Пример #19
0
    def __init__(self):
        QObject.__init__(self)

        self.dirCache = VCSStatusCache()  # Path -> VCSStatus
        self.fileCache = VCSStatusCache()  # Path -> VCSStatus
        self.activePlugins = {}  # Plugin ID -> VCSPluginDescriptor
        self.systemIndicators = {}  # ID -> VCSIndicator

        self.__firstFreeIndex = 0

        self.__readSettingsIndicators()
        self.__dirRequestLoopTimer = QTimer(self)
        self.__dirRequestLoopTimer.setSingleShot(True)
        self.__dirRequestLoopTimer.timeout.connect(
            self.__onDirRequestLoopTimer)

        GlobalData().project.sigProjectChanged.connect(self.__onProjectChanged)
        GlobalData().project.sigFSChanged.connect(self.__onFSChanged)
        GlobalData().pluginManager.sigPluginActivated.connect(
            self.__onPluginActivated)

        # Plugin deactivation must be done via dismissPlugin(...)
        return
Пример #20
0
    def __init__(self, editor, parent):
        QWidget.__init__(self, parent)

        self.setVisible(False)

        self.__editor = editor
        self.__parentWidget = parent
        self.__connected = False

        hLayout = QHBoxLayout()
        hLayout.setContentsMargins(0, 0, 0, 0)
        hLayout.setSpacing(0)

        vLayout = QVBoxLayout()
        vLayout.setContentsMargins(0, 0, 0, 0)
        vLayout.setSpacing(0)

        # Make pylint happy
        self.__toolbar = None
        self.__topBar = None

        # Create the update timer
        self.__updateTimer = QTimer(self)
        self.__updateTimer.setSingleShot(True)
        self.__updateTimer.timeout.connect(self.process)

        vLayout.addWidget(self.__createTopBar())
        vLayout.addWidget(self.__createMDView())

        hLayout.addLayout(vLayout)
        hLayout.addWidget(self.__createToolbar())
        self.setLayout(hLayout)

        # Connect to the change file type signal
        self.__mainWindow = GlobalData().mainWindow
        editorsManager = self.__mainWindow.editorsManagerWidget.editorsManager
        editorsManager.sigFileTypeChanged.connect(self.__onFileTypeChanged)
Пример #21
0
    def populateBinary(self, source, encoding, filename):
        """Populates the disassembly tree"""
        self.__navBar.clearWarnings()
        self.serializeScrollAndSelection()
        try:
            optLevel = Settings()['disasmLevel']
            if source is None:
                props, binContent = getFileBinary(filename, optLevel)
            else:
                props, binContent = getBufferBinary(source, encoding, filename,
                                                    optLevel)

            self.__textEdit.clear()

            self.__populate(binContent, props)
            self.__setupLabel(props)

            self.__navBar.updateInfoIcon(self.__navBar.STATE_OK_UTD)

            QTimer.singleShot(0, self.restoreScrollAndSelection)
        except Exception as exc:
            self.__navBar.updateInfoIcon(self.__navBar.STATE_BROKEN_UTD)
            self.__navBar.setErrors('Binary view populating error:\n' +
                                    str(exc))
Пример #22
0
 def exec_(self):
     """Executes the dialog"""
     QTimer.singleShot(1, self.__process)
     QDialog.exec_(self)
Пример #23
0
def codimensionMain():
    """The codimension driver"""
    # Parse command line arguments
    helpMessaege = """
%prog [options] [project file | python files]
Runs codimension UI"""
    parser = OptionParser(helpMessaege, version="%prog " + VER)

    parser.add_option("--debug",
                      action="store_true",
                      dest="debug",
                      default=False,
                      help="switch on debug and info messages (default: Off)")
    parser.add_option("--clean-start",
                      action="store_true",
                      dest="cleanStart",
                      default=False,
                      help="do not restore previous IDE state (default: Off)")

    options, args = parser.parse_args()

    # Configure logging
    setupLogging(options.debug)

    # The default exception handler can be replaced
    sys.excepthook = exceptionHook

    # Create global data singleton.
    # It's unlikely to throw any exceptions.
    globalData = GlobalData()
    globalData.version = VER

    # Loading settings - they have to be loaded before the application is
    # created. This is because the skin name is saved there.
    settings = Settings()
    copySkin()

    # Load the skin
    globalData.skin = Skin()
    globalData.skin.load(SETTINGS_DIR + "skins" + os.path.sep +
                         settings['skin'])

    global __delayedWarnings
    __delayedWarnings += settings.validateZoom(globalData.skin.minTextZoom,
                                               globalData.skin.minCFlowZoom)

    # QT on UBUNTU has a bug - the main menu bar does not generate the
    # 'aboutToHide' signal (though 'aboutToShow' is generated properly. This
    # prevents codimension working properly so this hack below disables the
    # global menu bar for codimension and makes it working properly.
    os.environ["QT_X11_NO_NATIVE_MENUBAR"] = "1"

    # Create QT application
    codimensionApp = CodimensionApplication(sys.argv, settings['style'])
    globalData.application = codimensionApp

    logging.debug("Starting codimension v.%s", VER)

    try:
        # Process command line arguments
        projectFile = processCommandLineArgs(args)
    except Exception as exc:
        logging.error(str(exc))
        parser.print_help()
        return 1

    # Show splash screen
    splash = SplashScreen()
    globalData.splash = splash

    screenSize = codimensionApp.desktop().screenGeometry()
    globalData.screenWidth = screenSize.width()
    globalData.screenHeight = screenSize.height()

    splash.showMessage("Importing packages...")
    from ui.mainwindow import CodimensionMainWindow

    splash.showMessage("Generating main window...")
    mainWindow = CodimensionMainWindow(splash, settings)
    codimensionApp.setMainWindow(mainWindow)
    globalData.mainWindow = mainWindow
    codimensionApp.lastWindowClosed.connect(codimensionApp.quit)

    # Loading project if given or the recent one
    needSignal = True
    if options.cleanStart:
        # Codimension will not load anything.
        pass
    elif projectFile:
        splash.showMessage("Loading project...")
        globalData.project.loadProject(projectFile)
        needSignal = False
    elif args:
        # There are arguments and they are python files
        # The project should not be loaded but the files should
        # be opened
        for fName in args:
            mainWindow.openFile(os.path.abspath(fName), -1)
    elif settings['projectLoaded']:
        if not settings['recentProjects']:
            # Some project was loaded but now it is not available.
            pass
        else:
            splash.showMessage("Loading recent project...")
            if os.path.exists(settings['recentProjects'][0]):
                globalData.project.loadProject(settings['recentProjects'][0])
                needSignal = False
            else:
                __delayedWarnings.append(
                    "Cannot open the most recent project: " +
                    settings['recentProjects'][0] + ". Ignore and continue.")
    else:
        mainWindow.editorsManagerWidget.editorsManager.restoreTabs(
            settings.tabStatus)

    # Signal for triggering browsers layout
    if needSignal:
        globalData.project.sigProjectChanged.emit(
            CodimensionProject.CompleteProject)

    mainWindow.show()
    mainWindow.restoreWindowPosition()
    mainWindow.restoreSplitterSizes()

    # The editors positions can be restored properly only when the editors have
    # actually been drawn. Otherwise the first visible line is unknown.
    # So, I load the project first and let object browsers initialize
    # themselves and then manually call the main window handler to restore the
    # editors. The last step is to connect the signal.
    mainWindow.onProjectChanged(CodimensionProject.CompleteProject)
    globalData.project.sigProjectChanged.connect(mainWindow.onProjectChanged)

    # Launch the user interface
    QTimer.singleShot(1, launchUserInterface)

    # Run the application main cycle
    retVal = codimensionApp.exec_()
    return retVal
Пример #24
0
class MDWidget(QWidget):
    """The MD rendered content widget which goes along with the text editor"""
    def __init__(self, editor, parent):
        QWidget.__init__(self, parent)

        self.setVisible(False)

        self.__editor = editor
        self.__parentWidget = parent
        self.__connected = False

        hLayout = QHBoxLayout()
        hLayout.setContentsMargins(0, 0, 0, 0)
        hLayout.setSpacing(0)

        vLayout = QVBoxLayout()
        vLayout.setContentsMargins(0, 0, 0, 0)
        vLayout.setSpacing(0)

        # Make pylint happy
        self.__toolbar = None
        self.__topBar = None

        # Create the update timer
        self.__updateTimer = QTimer(self)
        self.__updateTimer.setSingleShot(True)
        self.__updateTimer.timeout.connect(self.process)

        vLayout.addWidget(self.__createTopBar())
        vLayout.addWidget(self.__createMDView())

        hLayout.addLayout(vLayout)
        hLayout.addWidget(self.__createToolbar())
        self.setLayout(hLayout)

        # Connect to the change file type signal
        self.__mainWindow = GlobalData().mainWindow
        editorsManager = self.__mainWindow.editorsManagerWidget.editorsManager
        editorsManager.sigFileTypeChanged.connect(self.__onFileTypeChanged)

    def __createToolbar(self):
        """Creates the toolbar"""
        self.__toolbar = QToolBar(self)
        self.__toolbar.setOrientation(Qt.Vertical)
        self.__toolbar.setMovable(False)
        self.__toolbar.setAllowedAreas(Qt.RightToolBarArea)
        self.__toolbar.setIconSize(QSize(16, 16))
        self.__toolbar.setFixedWidth(30)
        self.__toolbar.setContentsMargins(0, 0, 0, 0)

        # Some control buttons could be added later

        return self.__toolbar

    def __createTopBar(self):
        """Creates the top bar"""
        self.__topBar = MDTopBar(self)
        return self.__topBar

    def __createMDView(self):
        """Creates the graphics view"""
        self.mdView = MDViewer(self)
        return self.mdView

    def process(self):
        """Parses the content and displays the results"""
        if not self.__connected:
            self.__connectEditorSignals()

        renderedText, errors, warnings = renderMarkdown(self.__editor.text)
        if errors:
            self.__topBar.updateInfoIcon(self.__topBar.STATE_BROKEN_UTD)
            self.__topBar.setErrors(errors)
            return
        if renderedText is None:
            self.__topBar.updateInfoIcon(self.__topBar.STATE_BROKEN_UTD)
            self.__topBar.setErrors(['Unknown morkdown rendering error'])
            return

        # That will clear the error tooltip as well
        self.__topBar.updateInfoIcon(self.__topBar.STATE_OK_UTD)

        if warnings:
            self.__topBar.setWarnings(warnings)
        else:
            self.__topBar.clearWarnings()

        hsbValue, vsbValue = self.getScrollbarPositions()
        self.mdView.setHtml(renderedText)
        self.setScrollbarPositions(hsbValue, vsbValue)

    def __onFileTypeChanged(self, fileName, uuid, newFileType):
        """Triggered when a buffer content type has changed"""
        if self.__parentWidget.getUUID() != uuid:
            return

        if not isMarkdownMime(newFileType):
            self.__disconnectEditorSignals()
            self.__updateTimer.stop()
            self.setVisible(False)
            self.__topBar.updateInfoIcon(self.__topBar.STATE_UNKNOWN)
            return

        # Update the bar and show it
        self.setVisible(True)
        self.process()

        # The buffer type change event comes when the content is loaded first
        # time. So this is a good point to restore the position
        _, _, _, hPos, vPos = getFilePosition(fileName)
        self.setScrollbarPositions(hPos, vPos)

    def __connectEditorSignals(self):
        """When it is a python file - connect to the editor signals"""
        if not self.__connected:
            self.__editor.cursorPositionChanged.connect(
                self.__cursorPositionChanged)
            self.__editor.textChanged.connect(self.__onBufferChanged)
            self.__connected = True

    def __disconnectEditorSignals(self):
        """Disconnect the editor signals when the file is not a python one"""
        if self.__connected:
            self.__editor.cursorPositionChanged.disconnect(
                self.__cursorPositionChanged)
            self.__editor.textChanged.disconnect(self.__onBufferChanged)
            self.__connected = False

    def __cursorPositionChanged(self):
        """Cursor position changed"""
        # The timer should be reset only in case if the redrawing was delayed
        if self.__updateTimer.isActive():
            self.__updateTimer.stop()
            self.__updateTimer.start(IDLE_TIMEOUT)

    def __onBufferChanged(self):
        """Triggered to update status icon and to restart the timer"""
        self.__updateTimer.stop()
        if self.__topBar.getCurrentState() in [
                self.__topBar.STATE_OK_UTD, self.__topBar.STATE_OK_CHN,
                self.__topBar.STATE_UNKNOWN
        ]:
            self.__topBar.updateInfoIcon(self.__topBar.STATE_OK_CHN)
        else:
            self.__topBar.updateInfoIcon(self.__topBar.STATE_BROKEN_CHN)
        self.__updateTimer.start(IDLE_TIMEOUT)

    def redrawNow(self):
        """Redraw the diagram regardless of the timer"""
        if self.__updateTimer.isActive():
            self.__updateTimer.stop()
        self.process()

    def getScrollbarPositions(self):
        """Provides the scrollbar positions"""
        hScrollBar = self.mdView.horizontalScrollBar()
        hsbValue = hScrollBar.value() if hScrollBar else 0

        vScrollBar = self.mdView.verticalScrollBar()
        vsbValue = vScrollBar.value() if vScrollBar else 0
        return hsbValue, vsbValue

    def setScrollbarPositions(self, hPos, vPos):
        """Sets the scrollbar positions for the view"""
        hsb = self.mdView.horizontalScrollBar()
        if hsb:
            hsb.setValue(hPos)
        vsb = self.mdView.verticalScrollBar()
        if vsb:
            vsb.setValue(vPos)

    def getFileName(self):
        return self.__parentWidget.getFileName()
Пример #25
0
class RunManager(QObject):
    """Manages the external running processes"""

    # script path, output file, start time, finish time, redirected
    sigProfilingResults = pyqtSignal(str, str, str, str, bool)
    sigDebugSessionPrologueStarted = pyqtSignal(object, str, object, object)
    sigIncomingMessage = pyqtSignal(str, str, object)
    sigProcessFinished = pyqtSignal(str, int)

    def __init__(self, mainWindow):
        QObject.__init__(self)
        self.__mainWindow = mainWindow
        self.__processes = []
        self.__prologueProcesses = []

        self.__tcpServer = QTcpServer()
        self.__tcpServer.newConnection.connect(self.__newConnection)
        self.__tcpServer.listen(QHostAddress.LocalHost)

        self.__waitTimer = QTimer(self)
        self.__waitTimer.setSingleShot(True)
        self.__waitTimer.timeout.connect(self.__onWaitTimer)

        self.__prologueTimer = QTimer(self)
        self.__prologueTimer.setSingleShot(True)
        self.__prologueTimer.timeout.connect(self.__onPrologueTimer)

    def __newConnection(self):
        """Handles new incoming connections"""
        clientSocket = self.__tcpServer.nextPendingConnection()
        clientSocket.setSocketOption(QAbstractSocket.KeepAliveOption, 1)
        clientSocket.setSocketOption(QAbstractSocket.LowDelayOption, 1)
        QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
        try:
            self.__waitForHandshake(clientSocket)
        except:
            QApplication.restoreOverrideCursor()
            raise
        QApplication.restoreOverrideCursor()

    def __waitForHandshake(self, clientSocket):
        """Waits for the message with the proc ID"""
        if clientSocket.waitForReadyRead(1000):
            try:
                method, procuuid, params, jsonStr = getParsedJSONMessage(
                    clientSocket)
                if IDE_DEBUG:
                    print("Run manager (wait for handshake) received: " +
                          str(jsonStr))
                if method != METHOD_PROC_ID_INFO:
                    logging.error('Unexpected message at the handshake stage. '
                                  'Expected: ' + METHOD_PROC_ID_INFO +
                                  '. Received: ' + str(method))
                    self.__safeSocketClose(clientSocket)
                    return None
            except (TypeError, ValueError) as exc:
                self.__mainWindow.showStatusBarMessage(
                    'Unsolicited connection to the RunManager. Ignoring...')
                self.__safeSocketClose(clientSocket)
                return None

            procIndex = self.__getProcessIndex(procuuid)
            if procIndex is not None:
                self.__onProcessStarted(procuuid)
                self.__processes[procIndex].procWrapper.setSocket(clientSocket)
            return params

    @staticmethod
    def __safeSocketClose(clientSocket):
        """No exception socket close"""
        try:
            clientSocket.close()
        except Exception as exc:
            logging.error('Run manager safe socket close: ' + str(exc))

    def __pickWidget(self, procuuid, kind):
        """Picks the widget for a process"""
        consoleReuse = Settings()['ioconsolereuse']
        if consoleReuse == NO_REUSE:
            widget = IOConsoleWidget(procuuid, kind)
            self.__mainWindow.addIOConsole(widget, kind)
            return widget

        widget = None
        consoles = self.__mainWindow.getIOConsoles()
        for console in consoles:
            if console.kind == kind:
                procIndex = self.__getProcessIndex(console.procuuid)
                if procIndex is None:
                    widget = console
                    widget.onReuse(procuuid)
                    self.__mainWindow.onReuseConsole(widget, kind)
                    if consoleReuse == CLEAR_AND_REUSE:
                        widget.clear()
                    break
        if widget is None:
            widget = IOConsoleWidget(procuuid, kind)
            self.__mainWindow.addIOConsole(widget, kind)
        return widget

    def __updateParameters(self, path, kind):
        """Displays the dialog and updates the parameters if needed"""
        params = getRunParameters(path)
        profilerParams = None
        debuggerParams = None
        if kind == PROFILE:
            profilerParams = Settings().getProfilerSettings()
        elif kind == DEBUG:
            debuggerParams = Settings().getDebuggerSettings()
        dlg = RunDialog(path, params, profilerParams, debuggerParams, kind,
                        self.__mainWindow)
        if dlg.exec_() == QDialog.Accepted:
            addRunParams(path, dlg.runParams)
            if kind == PROFILE:
                if dlg.profilerParams != profilerParams:
                    Settings().setProfilerSettings(dlg.profilerParams)
            elif kind == DEBUG:
                if dlg.debuggerParams != debuggerParams:
                    Settings().setDebuggerSettings(dlg.debuggerParams)
            return True
        return False

    def __prepareRemoteProcess(self, path, kind):
        """Prepares the data structures to start a remote proces"""
        redirected = getRunParameters(path)['redirected']
        remoteProc = RemoteProcess()
        remoteProc.kind = kind
        remoteProc.procWrapper = RemoteProcessWrapper(
            path, self.__tcpServer.serverPort(), redirected, kind)
        remoteProc.procWrapper.state = STATE_PROLOGUE
        if redirected or kind == DEBUG:
            self.__prologueProcesses.append(
                (remoteProc.procWrapper.procuuid, time.time()))
            if not self.__prologueTimer.isActive():
                self.__prologueTimer.start(1000)
        if redirected:
            remoteProc.widget = self.__pickWidget(
                remoteProc.procWrapper.procuuid, kind)
            remoteProc.widget.appendIDEMessage('Starting script ' + path +
                                               '...')

            remoteProc.procWrapper.sigClientStdout.connect(
                remoteProc.widget.appendStdoutMessage)
            remoteProc.procWrapper.sigClientStderr.connect(
                remoteProc.widget.appendStderrMessage)
            remoteProc.procWrapper.sigClientInput.connect(
                remoteProc.widget.input)

            remoteProc.widget.sigUserInput.connect(self.__onUserInput)

        remoteProc.procWrapper.sigFinished.connect(self.__onProcessFinished)
        remoteProc.procWrapper.sigIncomingMessage.connect(
            self.__onIncomingMessage)
        self.__processes.append(remoteProc)
        return remoteProc

    def run(self, path, needDialog):
        """Runs the given script regardless if it is redirected"""
        if needDialog:
            if not self.__updateParameters(path, RUN):
                return

        remoteProc = self.__prepareRemoteProcess(path, RUN)
        try:
            remoteProc.procWrapper.start()
            if not remoteProc.procWrapper.redirected:
                remoteProc.procWrapper.startTime = datetime.now()
                if not self.__waitTimer.isActive():
                    self.__waitTimer.start(1000)
        except Exception as exc:
            self.__onProcessFinished(remoteProc.procWrapper.procuuid,
                                     FAILED_TO_START)
            logging.error(str(exc))

    def profile(self, path, needDialog):
        """Profiles the given script regardless if it is redirected"""
        if needDialog:
            if not self.__updateParameters(path, PROFILE):
                return

        remoteProc = self.__prepareRemoteProcess(path, PROFILE)
        try:
            remoteProc.procWrapper.start()
            if not remoteProc.procWrapper.redirected:
                remoteProc.procWrapper.startTime = datetime.now()
                if not self.__waitTimer.isActive():
                    self.__waitTimer.start(1000)
        except Exception as exc:
            self.__onProcessFinished(remoteProc.procWrapper.procuuid,
                                     FAILED_TO_START)
            logging.error(str(exc))

    def debug(self, path, needDialog):
        """Debugs the given script regardless if it is redirected"""
        if needDialog:
            if not self.__updateParameters(path, DEBUG):
                return

        remoteProc = self.__prepareRemoteProcess(path, DEBUG)

        # The run parameters could be changed by another run after the
        # debugging has started so they need to be saved per session
        self.sigDebugSessionPrologueStarted.emit(
            remoteProc.procWrapper, path, getRunParameters(path),
            Settings().getDebuggerSettings())
        try:
            remoteProc.procWrapper.start()
            if not remoteProc.procWrapper.redirected:
                remoteProc.procWrapper.startTime = datetime.now()
                if not self.__waitTimer.isActive():
                    self.__waitTimer.start(1000)
        except Exception as exc:
            self.__onProcessFinished(remoteProc.procWrapper.procuuid,
                                     FAILED_TO_START)
            logging.error(str(exc))

    def killAll(self):
        """Kills all the processes if needed"""
        index = len(self.__processes) - 1
        while index >= 0:
            item = self.__processes[index]
            if item.procWrapper.redirected:
                item.procWrapper.stop()
            index -= 1

        # Wait till all the processes stopped
        count = self.__getDetachedCount()
        while count > 0:
            time.sleep(0.01)
            QApplication.processEvents()
            count = self.__getDetachedCount()

    def __getDetachedCount(self):
        """Return the number of detached processes still running"""
        count = 0
        index = len(self.__processes) - 1
        while index >= 0:
            if self.__processes[index].procWrapper.redirected:
                count += 1
            index -= 1
        return count

    def kill(self, procuuid):
        """Kills a single process"""
        index = self.__getProcessIndex(procuuid)
        if index is None:
            return
        item = self.__processes[index]
        if not item.procWrapper.redirected:
            return
        item.procWrapper.stop()

    def __getProcessIndex(self, procuuid):
        """Returns a process index in the list"""
        for index, item in enumerate(self.__processes):
            if item.procWrapper.procuuid == procuuid:
                return index
        return None

    def __onProcessFinished(self, procuuid, retCode):
        """Triggered when a redirected process has finished"""
        index = self.__getProcessIndex(procuuid)
        if index is not None:
            item = self.__processes[index]
            item.procWrapper.finishTime = datetime.now()

            needProfileSignal = False
            if retCode == KILLED:
                msg = "Script killed"
                tooltip = "killed"
            elif retCode == DISCONNECTED:
                msg = "Connection lost to the script process"
                tooltip = "connection lost"
            elif retCode == FAILED_TO_START:
                msg = "Script failed to start"
                tooltip = "failed to start"
            elif retCode == STOPPED_BY_REQUEST:
                # Debugging only: user clicked 'stop'
                msg = "Script finished by the user request"
                tooltip = "stopped by user"
                item.procWrapper.wait()
            elif retCode == UNHANDLED_EXCEPTION:
                # Debugging only: unhandled exception
                msg = "Script finished due to an unhandled exception"
                tooltip = "unhandled exception"
                item.procWrapper.wait()
            elif retCode == SYNTAX_ERROR_AT_START:
                msg = "Failure to run due to a syntax error"
                tooltip = "syntax error"
                item.procWrapper.wait()
            else:
                msg = "Script finished with exit code " + str(retCode)
                tooltip = "finished, exit code " + str(retCode)
                item.procWrapper.wait()
                if item.kind == PROFILE:
                    needProfileSignal = True

            if item.widget:
                item.widget.scriptFinished()
                item.widget.appendIDEMessage(msg)
                self.__mainWindow.updateIOConsoleTooltip(procuuid, tooltip)
                self.__mainWindow.onConsoleFinished(item.widget)
                item.widget.sigUserInput.disconnect(self.__onUserInput)

            if needProfileSignal:
                self.__sendProfileResultsSignal(item.procWrapper)

            del self.__processes[index]

        self.sigProcessFinished.emit(procuuid, retCode)

    def __onProcessStarted(self, procuuid):
        """Triggered when a process has started"""
        index = self.__getProcessIndex(procuuid)
        if index is not None:
            item = self.__processes[index]
            if item.widget:
                msg = item.widget.appendIDEMessage('Script started')
                item.procWrapper.startTime = msg.timestamp

    def __onUserInput(self, procuuid, userInput):
        """Triggered when the user input is collected"""
        index = self.__getProcessIndex(procuuid)
        if index is not None:
            item = self.__processes[index]
            if item.procWrapper.redirected:
                item.procWrapper.userInput(userInput)

    def __onWaitTimer(self):
        """Triggered when the timer fired"""
        needNewTimer = False
        index = len(self.__processes) - 1
        while index >= 0:
            item = self.__processes[index]
            if not item.procWrapper.redirected:
                if item.procWrapper.waitDetached():
                    item.procWrapper.finishTime = datetime.now()
                    if item.procWrapper.kind == PROFILE:
                        self.__sendProfileResultsSignal(item.procWrapper)
                    del self.__processes[index]
                else:
                    needNewTimer = True
            index -= 1
        if needNewTimer:
            self.__waitTimer.start(1000)

    def __sendProfileResultsSignal(self, procWrapper):
        """Sends a signal to tell that the results are available"""
        self.sigProfilingResults.emit(
            procWrapper.path,
            GlobalData().getProfileOutputPath(procWrapper.procuuid),
            printableTimestamp(procWrapper.startTime),
            printableTimestamp(procWrapper.finishTime), procWrapper.redirected)

    def __onIncomingMessage(self, procuuid, method, params):
        """Handles a debugger incoming message"""
        if IDE_DEBUG:
            print('Debugger message from ' + procuuid + ' Method: ' + method +
                  ' Prameters: ' + repr(params))

        self.sigIncomingMessage.emit(procuuid, method, params)

    def __onPrologueTimer(self):
        """Triggered when a prologue phase controlling timer fired"""
        needNewTimer = False
        index = len(self.__prologueProcesses) - 1
        while index >= 0:
            procuuid, startTime = self.__prologueProcesses[index]
            procIndex = self.__getProcessIndex(procuuid)
            if procIndex is None:
                # No such process anymore
                del self.__prologueProcesses[index]
            else:
                item = self.__processes[procIndex]
                if item.procWrapper.state != STATE_PROLOGUE:
                    # The state has been changed
                    del self.__prologueProcesses[index]
                else:
                    if time.time() - startTime > HANDSHAKE_TIMEOUT:
                        # Waited too long
                        item.widget.appendIDEMessage(
                            'Timeout: the process did not start; '
                            'killing the process.')
                        item.procWrapper.stop()
                    else:
                        needNewTimer = True
            index -= 1
        if needNewTimer:
            self.__prologueTimer.start(1000)

    def appendIDEMessage(self, procuuid, message):
        """Appends a message to the appropriate IO window"""
        index = self.__getProcessIndex(procuuid)
        if index is not None:
            item = self.__processes[index]
            if item.widget:
                item.widget.appendIDEMessage(message)
                return
        logging.error(message)
Пример #26
0
class VCSManager(QObject):
    """Manages the VCS plugins"""

    sigVCSFileStatus = pyqtSignal(str, object)
    sigVCSDirStatus = pyqtSignal(str, object)

    def __init__(self):
        QObject.__init__(self)

        self.dirCache = VCSStatusCache()  # Path -> VCSStatus
        self.fileCache = VCSStatusCache()  # Path -> VCSStatus
        self.activePlugins = {}  # Plugin ID -> VCSPluginDescriptor
        self.systemIndicators = {}  # ID -> VCSIndicator

        self.__firstFreeIndex = 0

        self.__readSettingsIndicators()
        self.__dirRequestLoopTimer = QTimer(self)
        self.__dirRequestLoopTimer.setSingleShot(True)
        self.__dirRequestLoopTimer.timeout.connect(
            self.__onDirRequestLoopTimer)

        GlobalData().project.sigProjectChanged.connect(self.__onProjectChanged)
        GlobalData().project.sigFSChanged.connect(self.__onFSChanged)
        GlobalData().pluginManager.sigPluginActivated.connect(
            self.__onPluginActivated)

        # Plugin deactivation must be done via dismissPlugin(...)
        return

    def __getNewPluginIndex(self):
        """Provides a new plugin index"""
        index = self.__firstFreeIndex
        self.__firstFreeIndex += 1
        return index

    def __readSettingsIndicators(self):
        """Reads the system indicators"""
        for indicLine in Settings()['vcsindicators']:
            indicator = VCSIndicator(indicLine)
            self.systemIndicators[indicator.identifier] = indicator

    def __onPluginActivated(self, plugin):
        """Triggered when a plugin is activated"""
        if plugin.categoryName != "VersionControlSystemInterface":
            return

        newPluginIndex = self.__getNewPluginIndex()
        self.activePlugins[newPluginIndex] = VCSPluginDescriptor(
            self, newPluginIndex, plugin)
        plugin.getObject().PathChanged.connect(self.__onPathChanged)

        # Need to send requests for the opened TAB files
        mainWindow = GlobalData().mainWindow
        editorsManager = mainWindow.editorsManagerWidget.editorsManager
        editorsManager.sendAllTabsVCSStatusRequest()

        if self.activePluginCount() == 1 and GlobalData().project.isLoaded():
            # This is the first plugin and a project is there
            self.__populateProjectDirectories()
        self.__sendDirectoryRequestsOnActivation(newPluginIndex)
        self.__sendFileRequestsOnActivation(newPluginIndex)

        if self.activePluginCount() == 1:
            self.__startDirRequestTimer()

    def __onPathChanged(self, path):
        """The way plugins signal that a path has been changed"""
        # What is essentially required is to update the status of the path.
        # setLocallyModified(...) will do - it sends urgent request
        self.setLocallyModified(path)

        # Changed paths may change the file contents
        mainWindow = GlobalData().mainWindow
        editorsManager = mainWindow.editorsManagerWidget.editorsManager
        editorsManager.checkOutsidePathChange(path)

    def __populateProjectDirectories(self):
        """Populates the project directories in the dirCache"""
        project = GlobalData().project
        for path in project.filesList:
            if path.endswith(os.path.sep):
                self.dirCache.updateStatus(path, None, None, None, None)

    def __onProjectChanged(self, what):
        """Triggered when a project has changed"""
        if what == CodimensionProject.CompleteProject:
            self.dirCache.clear()
            self.fileCache.clear()
            if len(self.activePlugins) == 0:
                return

            # There are some plugins
            for _, descriptor in self.activePlugins.items():
                descriptor.clearRequestQueue()

            if GlobalData().project.isLoaded():
                self.__populateProjectDirectories()

    def __onFSChanged(self, items):
        """Triggered when files/dirs in the project are changed"""
        for item in items:
            item = str(item)
            if item.startswith('-'):
                item = item[1:]
                if item.endswith(os.path.sep):
                    # Directory removed
                    if item in self.dirCache.cache:
                        del self.dirCache.cache[item]
                else:
                    # File deleted
                    if item in self.fileCache.cache:
                        del self.fileCache.cache[item]
                    for _, descriptor in self.activePlugins.items():
                        descriptor.requestStatus(
                            item,
                            VersionControlSystemInterface.REQUEST_ITEM_ONLY,
                            True)
            else:
                item = item[1:]
                if item.endswith(os.path.sep):
                    # Directory added
                    if item not in self.dirCache.cache:
                        self.dirCache.updateStatus(item, None, None, None,
                                                   None)
                    for _, descriptor in self.activePlugins.items():
                        descriptor.requestStatus(
                            item,
                            VersionControlSystemInterface.REQUEST_DIRECTORY)
                else:
                    # File added
                    for _, descriptor in self.activePlugins.items():
                        descriptor.requestStatus(
                            item,
                            VersionControlSystemInterface.REQUEST_ITEM_ONLY,
                            True)

    def __sendDirectoryRequestsOnActivation(self, pluginID):
        """Sends the directory requests to the given plugin"""
        descriptor = self.activePlugins[pluginID]
        statuses = [None, VersionControlSystemInterface.NOT_UNDER_VCS]
        for path, status in self.dirCache.cache.items():
            if status.indicatorID in statuses:
                descriptor.requestStatus(
                    path, VersionControlSystemInterface.REQUEST_DIRECTORY)

    def __sendFileRequestsOnActivation(self, pluginID):
        """Sends the file requests to the given plugin"""
        descriptor = self.activePlugins[pluginID]
        statuses = [None, VersionControlSystemInterface.NOT_UNDER_VCS]
        for path, status in self.fileCache.cache.items():
            if status.indicatorID in statuses:
                descriptor.requestStatus(
                    path, VersionControlSystemInterface.REQUEST_ITEM_ONLY)

    def __sendPeriodicDirectoryRequests(self):
        """Sends the directory requests periodically"""
        for path, status in self.dirCache.cache.items():
            if status.pluginID in self.activePlugins:
                # This directory is under certain VCS, send the request
                # only to this VCS
                descriptor = self.activePlugins[status.pluginID]
                descriptor.requestStatus(
                    path, VersionControlSystemInterface.REQUEST_DIRECTORY)
            else:
                # The directory is not under any known VCS. Send the request
                # to all the registered VCS plugins
                for _, descriptor in self.activePlugins.items():
                    descriptor.requestStatus(
                        path, VersionControlSystemInterface.REQUEST_DIRECTORY)

    def dismissAllPlugins(self):
        """Stops all the plugin threads"""
        self.__dirRequestLoopTimer.stop()

        for identifier, descriptor in self.activePlugins.items():
            descriptor.plugin.getObject().PathChanged.disconnect(
                self.__onPathChanged)
            descriptor.stopThread()

        self.dirCache.clear()
        self.fileCache.clear()
        self.activePlugins = {}

    def dismissPlugin(self, plugin):
        """Stops the plugin thread and cleans the plugin data"""
        pluginID = None
        for identifier, descriptor in self.activePlugins.items():
            if descriptor.getPluginName() == plugin.getName():
                descriptor.plugin.getObject().PathChanged.disconnect(
                    self.__onPathChanged)
                pluginID = identifier
                descriptor.stopThread()
                self.fileCache.dismissPlugin(pluginID,
                                             self.sendFileStatusNotification)
                self.dirCache.dismissPlugin(pluginID,
                                            self.sendDirStatusNotification)

        if pluginID is not None:
            del self.activePlugins[pluginID]

        if self.activePluginCount() == 0:
            self.__dirRequestLoopTimer.stop()

    def requestStatus(self,
                      path,
                      flag=VersionControlSystemInterface.REQUEST_ITEM_ONLY):
        """Provides the path status asynchronously via sending a signal"""
        delta = datetime.timedelta(0, Settings()['vcsstatusupdateinterval'])
        if path.endswith(os.path.sep):
            status = self.dirCache.getStatus(path)
            if status:
                self.sendDirStatusNotification(path, status)
            else:
                self.sendDirStatusNotification(path, VCSStatus())
        else:
            status = self.fileCache.getStatus(path)
            if status:
                self.sendFileStatusNotification(path, status)
            else:
                self.sendFileStatusNotification(path, VCSStatus())

        if status is None:
            for _, descriptor in self.activePlugins.items():
                descriptor.requestStatus(path, flag, True)
        else:
            if status.indicatorID is None or \
               status.lastUpdate is None or \
               datetime.datetime.now() - status.lastUpdate > delta:
                # Outdated or never been received
                if status.pluginID in self.activePlugins:
                    # Path is claimed by a plugin
                    descriptor = self.activePlugins[status.pluginID]
                    descriptor.requestStatus(path, flag, True)
                    return
                # Path is not claimed by any plugin - send to all
                for _, descriptor in self.activePlugins.items():
                    descriptor.requestStatus(path, flag, True)

    def setLocallyModified(self, path):
        """Sets the item status as locally modified"""
        for _, descriptor in self.activePlugins.items():
            descriptor.requestStatus(
                path, VersionControlSystemInterface.REQUEST_ITEM_ONLY, True)

    def sendDirStatusNotification(self, path, status):
        """Sends a signal that a status of the directory is changed"""
        self.sigVCSDirStatus.emit(path, status)

    def sendFileStatusNotification(self, path, status):
        """Sends a signal that a status of the file is changed"""
        self.sigVCSFileStatus.emit(path, status)

    def updateStatus(self, path, pluginID, indicatorID, message):
        """Called when a plugin thread reports a status"""
        if path.endswith(os.path.sep):
            self.dirCache.updateStatus(path, pluginID, indicatorID, message,
                                       self.sendDirStatusNotification)
        else:
            self.fileCache.updateStatus(path, pluginID, indicatorID, message,
                                        self.sendFileStatusNotification)
        # print("Status of " + path + " is " + str(indicatorID) +
        # " Message: " + str(message) + " Plugin ID: " + str(pluginID))

    def activePluginCount(self):
        """Returns the number of active VCS plugins"""
        return len(self.activePlugins)

    def drawStatus(self, vcsLabel, status):
        """Draw the VCS status"""
        if status.pluginID not in self.activePlugins:
            vcsLabel.setVisible(False)
            return

        descriptor = self.activePlugins[status.pluginID]
        if status.indicatorID not in descriptor.indicators:
            # Check the standard indicator
            if status.indicatorID in self.systemIndicators:
                indicator = self.systemIndicators[status.indicatorID]
                indicator.draw(vcsLabel)
                if status.message:
                    vcsLabel.setToolTip(status.message)
                else:
                    vcsLabel.setToolTip(indicator.defaultTooltip)
            else:
                # Neither plugin, no standard indicator
                try:
                    indicator = self.systemIndicators[IND_VCS_ERROR]
                    indicator.draw(vcsLabel)
                    vcsLabel.setToolTip("VCS plugin provided undefined "
                                        "indicator (id is " +
                                        str(status.indicatorID) + ")")
                except:
                    # No way to display indicator
                    vcsLabel.setVisible(False)
            return

        indicator = descriptor.indicators[status.indicatorID]
        indicator.draw(vcsLabel)
        if status.message:
            vcsLabel.setToolTip(status.message)
        else:
            vcsLabel.setToolTip(indicator.defaultTooltip)

    def getStatusIndicator(self, status):
        """Provides the VCS status indicator description for the given status.
           It is mostly used for the project browser"""
        if status.pluginID not in self.activePlugins:
            return None

        descriptor = self.activePlugins[status.pluginID]
        if status.indicatorID not in descriptor.indicators:
            # Check the standard indicator
            return self.getSystemIndicator(status.indicatorID)

        return descriptor.indicators[status.indicatorID]

    def getSystemIndicator(self, indicatorID):
        """Provides the IDE defined indicator if so"""
        if indicatorID in self.systemIndicators:
            return self.systemIndicators[indicatorID]
        return None

    def __canInitiateStatusRequestLoop(self):
        """Returns true if the is no jam in the request queue"""
        for _, descriptor in self.activePlugins.items():
            if descriptor.canInitiateStatusRequestLoop():
                return True
        return False

    def __onDirRequestLoopTimer(self):
        """Triggered when the dir request loop timer is fired"""
        self.__dirRequestLoopTimer.stop()
        if self.activePluginCount() == 0:
            return

        if self.__canInitiateStatusRequestLoop():
            self.__sendPeriodicDirectoryRequests()
        self.__startDirRequestTimer()

    def __startDirRequestTimer(self):
        """Starts the periodic request timer"""
        self.__dirRequestLoopTimer.start(
            Settings()['vcsstatusupdateinterval'] * 1000)
Пример #27
0
class NavigationBar(QFrame):

    """Navigation bar at the top of the editor (python only)"""

    STATE_OK_UTD = 0        # Parsed OK, context up to date
    STATE_OK_CHN = 1        # Parsed OK, context changed
    STATE_BROKEN_UTD = 2    # Parsed with errors, context up to date
    STATE_BROKEN_CHN = 3    # Parsed with errors, context changed
    STATE_UNKNOWN = 4

    def __init__(self, editor, parent):
        QFrame.__init__(self, parent)
        self.__editor = editor
        self.__parentWidget = parent

        # It is always not visible at the beginning because there is no
        # editor content at the start
        self.setVisible(False)

        # There is no parser info used to display values
        self.__currentInfo = None
        self.__currentIconState = self.STATE_UNKNOWN
        self.__connected = False

        # List of PathElement starting after the global scope
        self.__path = []

        self.__createLayout()

        # Create the update timer
        self.__updateTimer = QTimer(self)
        self.__updateTimer.setSingleShot(True)
        self.__updateTimer.timeout.connect(self.updateBar)

        # Connect to the change file type signal
        mainWindow = GlobalData().mainWindow
        editorsManager = mainWindow.editorsManagerWidget.editorsManager
        editorsManager.sigFileTypeChanged.connect(self.__onFileTypeChanged)

    def getEditor(self):
        """Provides the editor"""
        return self.__editor

    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 __createLayout(self):
        """Creates the layout"""
        self.setFixedHeight(24)
        self.__layout = QHBoxLayout(self)
        self.__layout.setContentsMargins(0, 0, 0, 0)

        # Set the background color

        # Create info icon
        self.__infoIcon = QLabel()
        self.__layout.addWidget(self.__infoIcon)

        self.__globalScopeCombo = NavBarComboBox(self)
        self.__globalScopeCombo.jumpToLine.connect(self.__onJumpToLine)
        self.__layout.addWidget(self.__globalScopeCombo)

        self.__spacer = QWidget()
        self.__spacer.setSizePolicy(QSizePolicy.Expanding,
                                    QSizePolicy.Expanding)
        self.__layout.addWidget(self.__spacer)

    def __updateInfoIcon(self, state):
        """Updates the information icon"""
        if state != self.__currentIconState:
            if state == self.STATE_OK_UTD:
                self.__infoIcon.setPixmap(getPixmap('nbokutd.png'))
                self.__infoIcon.setToolTip("Context is up to date")
                self.__currentIconState = self.STATE_OK_UTD
            elif state == self.STATE_OK_CHN:
                self.__infoIcon.setPixmap(getPixmap('nbokchn.png'))
                self.__infoIcon.setToolTip("Context 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('nbbrokenutd.png'))
                self.__infoIcon.setToolTip("Context might be invalid "
                                           "due to invalid python code")
                self.__currentIconState = self.STATE_BROKEN_UTD
            else:
                # STATE_BROKEN_CHN
                self.__infoIcon.setPixmap(getPixmap('nbbrokenchn.png'))
                self.__infoIcon.setToolTip("Context might be invalid; "
                                           "will be updated on idle")
                self.__currentIconState = self.STATE_BROKEN_CHN

    def resizeEvent(self, event):
        """Editor has resized"""
        QFrame.resizeEvent(self, event)

    # Arguments: fileName, uuid, newFileType
    def __onFileTypeChanged(self, _, uuid, newFileType):
        """Triggered when a buffer content type has changed"""
        if self.__parentWidget.getUUID() != uuid:
            return

        if isPythonMime(newFileType) and Settings()['showNavigationBar']:
            # Update the bar and show it
            self.setVisible(True)
            self.updateBar()
        else:
            self.__disconnectEditorSignals()
            self.__updateTimer.stop()
            self.__currentInfo = None
            self.setVisible(False)
            self.__currentIconState = self.STATE_UNKNOWN

    def updateSettings(self):
        """Called when navigation bar settings have been updated"""
        textMime = self.__parentWidget.getMime()
        if Settings()['showNavigationBar'] and isPythonMime(textMime):
            self.setVisible(True)
            self.updateBar()
        else:
            self.__disconnectEditorSignals()
            self.__updateTimer.stop()
            self.__currentInfo = None
            self.setVisible(False)

    def updateBar(self):
        """Triggered when the timer is fired"""
        self.__updateTimer.stop()  # just in case

        if not isPythonMime(self.__parentWidget.getMime()):
            return

        if not self.__connected:
            self.__connectEditorSignals()

        # Parse the buffer content
        self.__currentInfo = getBriefModuleInfoFromMemory(self.__editor.text)

        # Decide what icon to use
        if self.__currentInfo.isOK:
            self.__updateInfoIcon(self.STATE_OK_UTD)
        else:
            self.__updateInfoIcon(self.STATE_BROKEN_UTD)

        # Calc the cursor context
        context = getContext(self.__editor, self.__currentInfo, True, False)

        # Display the context
        self.__populateGlobalScope()
        if context.length == 0:
            self.__globalScopeCombo.setCurrentIndex(-1)
        else:
            index = self.__globalScopeCombo.findData(
                context.levels[0][0].line)
            self.__globalScopeCombo.setCurrentIndex(index)

        usedFromStore = 0
        index = 1
        while index < context.length:
            if len(self.__path) < index:
                newPathItem = PathElement(self)
                self.__path.append(newPathItem)
                self.__layout.addWidget(newPathItem.icon)
                self.__layout.addWidget(newPathItem.combo)
                combo = newPathItem.combo
                combo.pathIndex = len(self.__path) - 1
                combo.jumpToLine.connect(self.__onJumpToLine)
            else:
                self.__path[index - 1].icon.setVisible(True)
                self.__path[index - 1].combo.setVisible(True)
                combo = self.__path[index - 1].combo
                combo.clear()

            # Populate the combo box
            self.__populateClassesAndFunctions(context.levels[index - 1][0],
                                               combo)
            combo.setCurrentIndex(combo.findData(context.levels[index][0].line))
            index += 1
            usedFromStore += 1

        # it might need to have one more level with nothing selected
        if context.length > 0:
            if len(context.levels[context.length - 1][0].functions) > 0 or \
               len(context.levels[context.length - 1][0].classes) > 0:
                # Need to add a combo
                if len(self.__path) <= usedFromStore:
                    newPathItem = PathElement(self)
                    self.__path.append(newPathItem)
                    self.__layout.addWidget(newPathItem.icon)
                    self.__layout.addWidget(newPathItem.combo)
                    combo = newPathItem.combo
                    combo.pathIndex = len(self.__path) - 1
                    combo.jumpToLine.connect(self.__onJumpToLine)
                else:
                    self.__path[index - 1].icon.setVisible(True)
                    self.__path[index - 1].combo.setVisible(True)
                    combo = self.__path[index - 1].combo
                    combo.clear()

                self.__populateClassesAndFunctions(
                    context.levels[context.length - 1][0], combo)
                combo.setCurrentIndex(-1)
                usedFromStore += 1

        # Hide extra components if so
        index = usedFromStore
        while index < len(self.__path):
            self.__path[index].icon.setVisible(False)
            self.__path[index].combo.setVisible(False)
            index += 1

        # Make sure the spacer is the last item
        self.__layout.removeWidget(self.__spacer)
        self.__layout.addWidget(self.__spacer)

    def __populateGlobalScope(self):
        """Repopulates the global scope combo box"""
        self.__globalScopeCombo.clear()

        self.__populateClassesAndFunctions(self.__currentInfo,
                                           self.__globalScopeCombo)

        if not Settings()['navbarglobalsimports']:
            return

        if len(self.__currentInfo.globals) == 0 and \
           len(self.__currentInfo.imports) == 0:
            return

        if self.__globalScopeCombo.count() != 0:
            self.__globalScopeCombo.insertSeparator(
                self.__globalScopeCombo.count())

        for glob in self.__currentInfo.globals:
            self.__globalScopeCombo.addItem(getIcon('globalvar.png'),
                                            glob.name, glob.line)
        for imp in self.__currentInfo.imports:
            self.__globalScopeCombo.addItem(getIcon('imports.png'),
                                            imp.name, imp.line)

    @staticmethod
    def __populateClassesAndFunctions(infoObj, combo):
        """Populates the combo with classes and functions from the infoObj"""
        for klass in infoObj.classes:
            combo.addItem(getIcon('class.png'), klass.name, klass.line)
        for func in infoObj.functions:
            if func.isPrivate():
                icon = getIcon('method_private.png')
            elif func.isProtected():
                icon = getIcon('method_protected.png')
            else:
                icon = getIcon('method.png')
            combo.addItem(icon, func.name, func.line)

    def __cursorPositionChanged(self):
        """Cursor position changed"""
        self.__onNeedUpdate()

    def __onBufferChanged(self):
        """Buffer changed"""
        self.__onNeedUpdate()

    def __onNeedUpdate(self):
        """Triggered to update status icon and to restart the timer"""
        self.__updateTimer.stop()
        if self.__currentInfo.isOK:
            self.__updateInfoIcon(self.STATE_OK_CHN)
        else:
            self.__updateInfoIcon(self.STATE_BROKEN_CHN)
        self.__updateTimer.start(IDLE_TIMEOUT)

    def __onJumpToLine(self, line):
        """Triggered when it needs to jump to a line"""
        self.__editor.gotoLine(line, 0)
        self.__editor.setFocus()

    def setFocusToLastCombo(self):
        """Activates the last combo"""
        if self.__currentInfo is None:
            return
        for index in range(len(self.__path) - 1, -1, -1):
            if self.__path[index].combo.isVisible():
                self.__path[index].combo.setFocus()
                self.__path[index].combo.showPopup()
                return

        self.__globalScopeCombo.setFocus()
        self.__globalScopeCombo.showPopup()

    def activateCombo(self, currentCombo, newIndex):
        """Triggered when a neighbour combo should be activated"""
        if newIndex == -1:
            if len(self.__path) > 0:
                if self.__path[0].combo.isVisible():
                    currentCombo.hidePopup()
            self.__globalScopeCombo.setFocus()
            self.__globalScopeCombo.showPopup()
            return

        if newIndex >= len(self.__path):
            # This is the most right one
            return

        if self.__path[newIndex].combo.isVisible():
            currentCombo.hidePopup()
            self.__path[newIndex].combo.setFocus()
            self.__path[newIndex].combo.showPopup()
Пример #28
0
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']
Пример #29
0
class TextEditor(QutepartWrapper, EditorContextMenuMixin):

    """Text editor implementation"""

    sigEscapePressed = pyqtSignal()
    sigCFlowSyncRequested = pyqtSignal(int, int, int)

    def __init__(self, parent, debugger):
        self._parent = parent
        QutepartWrapper.__init__(self, parent)
        EditorContextMenuMixin.__init__(self)

        self.setAttribute(Qt.WA_KeyCompression)
        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)

        skin = GlobalData().skin
        self.setPaper(skin['nolexerPaper'])
        self.setColor(skin['nolexerColor'])
        self.currentLineColor = skin['currentLinePaper']

        self.onTextZoomChanged()
        self.__initMargins(debugger)

        self.cursorPositionChanged.connect(self._onCursorPositionChanged)

        self.__skipChangeCursor = False

        self.__openedLine = None

        self.setFocusPolicy(Qt.StrongFocus)
        self.indentWidth = 4

        self.updateSettings()

        # Completion support
        self.__completionPrefix = ''
        self.__completionLine = -1
        self.__completionPos = -1
        self.__completer = CodeCompleter(self)
        self.__inCompletion = False
        self.__completer.activated.connect(self.insertCompletion)
        self.__lastTabPosition = None

        # Calltip support
        self.__calltip = None
        self.__callPosition = None
        self.__calltipTimer = QTimer(self)
        self.__calltipTimer.setSingleShot(True)
        self.__calltipTimer.timeout.connect(self.__onCalltipTimer)

        self.__initHotKeys()
        self.installEventFilter(self)

    def dedentLine(self):
        """Dedent the current line or selection"""
        self.decreaseIndentAction.activate(QAction.Trigger)

    def __initHotKeys(self):
        """Initializes a map for the hot keys event filter"""
        self.autoIndentLineAction.setShortcut('Ctrl+Shift+I')
        self.invokeCompletionAction.setEnabled(False)
        self.__hotKeys = {
            CTRL_SHIFT: {Qt.Key_T: self.onJumpToTop,
                         Qt.Key_M: self.onJumpToMiddle,
                         Qt.Key_B: self.onJumpToBottom},
            SHIFT: {Qt.Key_Delete: self.onShiftDel,
                    Qt.Key_Backtab: self.dedentLine,
                    Qt.Key_End: self.onShiftEnd,
                    Qt.Key_Home: self.onShiftHome},
            CTRL: {Qt.Key_X: self.onShiftDel,
                   Qt.Key_C: self.onCtrlC,
                   Qt.Key_Insert: self.onCtrlC,
                   Qt.Key_Apostrophe: self.onHighlight,
                   Qt.Key_Period: self.onNextHighlight,
                   Qt.Key_Comma: self.onPrevHighlight,
                   Qt.Key_M: self.onCommentUncomment,
                   Qt.Key_Space: self.onAutoComplete,
                   Qt.Key_F1: self.onTagHelp,
                   Qt.Key_Backslash: self.onGotoDefinition,
                   Qt.Key_BracketRight: self.onOccurences,
                   Qt.Key_Slash: self.onShowCalltip,
                   Qt.Key_Minus: Settings().onTextZoomOut,
                   Qt.Key_Equal: Settings().onTextZoomIn,
                   Qt.Key_0: Settings().onTextZoomReset,
                   Qt.Key_Home: self.onFirstChar,
                   Qt.Key_End: self.onLastChar,
                   Qt.Key_B: self.highlightInOutline,
                   Qt.Key_QuoteLeft: self.highlightInCFlow},
            ALT: {Qt.Key_U: self.onScopeBegin},
            CTRL_KEYPAD: {Qt.Key_Minus: Settings().onTextZoomOut,
                          Qt.Key_Plus: Settings().onTextZoomIn,
                          Qt.Key_0: Settings().onTextZoomReset},
            NO_MODIFIER: {Qt.Key_Home: self.onHome,
                          Qt.Key_End: self.moveToLineEnd,
                          Qt.Key_F12: self.makeLineFirst}}

        # Not all the derived classes need certain tool functionality
        if hasattr(self._parent, "getType"):
            widgetType = self._parent.getType()
            if widgetType in [MainWindowTabWidgetBase.PlainTextEditor]:
                if hasattr(self._parent, "onOpenImport"):
                    self.__hotKeys[CTRL][Qt.Key_I] = self._parent.onOpenImport
        if hasattr(self._parent, "onNavigationBar"):
            self.__hotKeys[NO_MODIFIER][Qt.Key_F2] = \
                self._parent.onNavigationBar

    # Arguments: obj, event
    def eventFilter(self, _, event):
        """Event filter to catch shortcuts on UBUNTU"""
        if event.type() == QEvent.KeyPress:
            key = event.key()
            if self.isReadOnly():
                if key in [Qt.Key_Delete, Qt.Key_Backspace, Qt.Key_Backtab,
                           Qt.Key_X, Qt.Key_Tab, Qt.Key_Space, Qt.Key_Slash,
                           Qt.Key_Z, Qt.Key_Y]:
                    return True

            modifiers = int(event.modifiers())
            try:
                self.__hotKeys[modifiers][key]()
                return True
            except KeyError:
                return False
            except Exception as exc:
                logging.warning(str(exc))
        return False

    def wheelEvent(self, event):
        """Mouse wheel event"""
        if QApplication.keyboardModifiers() == Qt.ControlModifier:
            angleDelta = event.angleDelta()
            if not angleDelta.isNull():
                if angleDelta.y() > 0:
                    Settings().onTextZoomIn()
                else:
                    Settings().onTextZoomOut()
            event.accept()
        else:
            QutepartWrapper.wheelEvent(self, event)

    def focusInEvent(self, event):
        """Enable Shift+Tab when the focus is received"""
        if self._parent.shouldAcceptFocus():
            QutepartWrapper.focusInEvent(self, event)
        else:
            self._parent.setFocus()

    def focusOutEvent(self, event):
        """Disable Shift+Tab when the focus is lost"""
        self.__completer.hide()
        if not self.__inCompletion:
            self.__resetCalltip()
        QutepartWrapper.focusOutEvent(self, event)

    def updateSettings(self):
        """Updates the editor settings"""
        settings = Settings()

        if settings['verticalEdge']:
            self.lineLengthEdge = settings['editorEdge']
            self.lineLengthEdgeColor = GlobalData().skin['edgeColor']
            self.drawSolidEdge = True
        else:
            self.lineLengthEdge = None

        self.drawAnyWhitespace = settings['showSpaces']
        self.drawIncorrectIndentation = settings['showSpaces']

        if settings['lineWrap']:
            self.setWordWrapMode(QTextOption.WrapAnywhere)
        else:
            self.setWordWrapMode(QTextOption.NoWrap)

        if hasattr(self._parent, "getNavigationBar"):
            navBar = self._parent.getNavigationBar()
            if navBar:
                navBar.updateSettings()

    def __initMargins(self, debugger):
        """Initializes the editor margins"""
        self.addMargin(CDMLineNumberMargin(self))
        self.addMargin(CDMFlakesMargin(self))
        self.getMargin('cdm_flakes_margin').setVisible(False)

        if debugger:
            self.addMargin(CDMBreakpointMargin(self, debugger))
            self.getMargin('cdm_bpoint_margin').setVisible(False)

    def highlightCurrentDebuggerLine(self, line, asException):
        """Highlights the current debugger line"""
        margin = self.getMargin('cdm_flakes_margin')
        if margin:
            if asException:
                margin.setExceptionLine(line)
            else:
                margin.setCurrentDebugLine(line)

    def clearCurrentDebuggerLine(self):
        """Removes the current debugger line marker"""
        margin = self.getMargin('cdm_flakes_margin')
        if margin:
            margin.clearDebugMarks()

    def readFile(self, fileName):
        """Reads the text from a file"""
        QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
        try:
            content, self.encoding = readEncodedFile(fileName)
            self.eol = detectEolString(content)

            # Copied from enki (enki/core/document.py: _readFile()):
            # Strip last EOL. Qutepart adds it when saving file
            if content.endswith('\r\n'):
                content = content[:-2]
            elif content.endswith('\n') or content.endswith('\r'):
                content = content[:-1]

            self.text = content

            self.mime, _, xmlSyntaxFile = getFileProperties(fileName)
            if xmlSyntaxFile:
                self.detectSyntax(xmlSyntaxFile)

            self.document().setModified(False)
        except:
            QApplication.restoreOverrideCursor()
            raise

        QApplication.restoreOverrideCursor()

    def writeFile(self, fileName):
        """Writes the text to a file"""
        QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))

        if Settings()['removeTrailingOnSave']:
            self.removeTrailingWhitespaces()

        try:
            encoding = detectWriteEncoding(self, fileName)
            if encoding is None:
                QApplication.restoreOverrideCursor()
                logging.error('Could not detect write encoding for ' +
                              fileName)
                return False

            writeEncodedFile(fileName, self.textForSaving(), encoding)
        except Exception as exc:
            logging.error(str(exc))
            QApplication.restoreOverrideCursor()
            return False

        self.encoding = encoding
        if self.explicitUserEncoding:
            userEncoding = getFileEncoding(fileName)
            if userEncoding != self.explicitUserEncoding:
                setFileEncoding(fileName, self.explicitUserEncoding)
            self.explicitUserEncoding = None

        self._parent.updateModificationTime(fileName)
        self._parent.setReloadDialogShown(False)
        QApplication.restoreOverrideCursor()
        return True

    def setReadOnly(self, mode):
        """Overridden version"""
        QPlainTextEdit.setReadOnly(self, mode)
        if mode:
            # Otherwise the cursor is suppressed in the RO mode
            self.setTextInteractionFlags(self.textInteractionFlags() |
                                         Qt.TextSelectableByKeyboard)
        self.increaseIndentAction.setEnabled(not mode)
        self.decreaseIndentAction.setEnabled(not mode)
        self.autoIndentLineAction.setEnabled(not mode)
        self.indentWithSpaceAction.setEnabled(not mode)
        self.unIndentWithSpaceAction.setEnabled(not mode)
        self.undoAction.setEnabled(not mode)
        self.redoAction.setEnabled(not mode)
        self.moveLineUpAction.setEnabled(not mode)
        self.moveLineDownAction.setEnabled(not mode)
        self.deleteLineAction.setEnabled(not mode)
        self.pasteLineAction.setEnabled(not mode)
        self.cutLineAction.setEnabled(not mode)
        self.duplicateLineAction.setEnabled(not mode)

    def keyPressEvent(self, event):
        """Handles the key press events"""
        key = event.key()
        if self.isReadOnly():
            # Space scrolls
            # Ctrl+X/Shift+Del/Alt+D/Alt+X deletes something
            if key in [Qt.Key_Delete, Qt.Key_Backspace,
                       Qt.Key_Space, Qt.Key_X, Qt.Key_Tab,
                       Qt.Key_Z, Qt.Key_Y]:
                return

            # Qutepart has its own handler and lets to insert new lines when
            # ENTER is clicked, so use the QPlainTextEdit
            return QPlainTextEdit.keyPressEvent(self, event)

        self.__skipChangeCursor = True
        if self.__completer.isVisible():
            self.__skipChangeCursor = False
            if key == Qt.Key_Escape:
                self.__completer.hide()
                self.setFocus()
                return
            # There could be backspace or printed characters only
            QutepartWrapper.keyPressEvent(self, event)
            QApplication.processEvents()
            if key == Qt.Key_Backspace:
                if self.__completionPrefix == '':
                    self.__completer.hide()
                    self.setFocus()
                else:
                    self.__completionPrefix = self.__completionPrefix[:-1]
                    self.__completer.setPrefix(self.__completionPrefix)
            else:
                self.__completionPrefix += event.text()
                self.__completer.setPrefix(self.__completionPrefix)
                if self.__completer.completionCount() == 0:
                    self.__completer.hide()
                    self.setFocus()

        elif key in [Qt.Key_Enter, Qt.Key_Return]:
            QApplication.processEvents()
            line, _ = self.cursorPosition

            QutepartWrapper.keyPressEvent(self, event)
            QApplication.processEvents()

            if line == self.__openedLine:
                self.lines[line] = ''

            # If the new line has one or more spaces then it is a candidate for
            # automatic trimming
            line, pos = self.cursorPosition
            text = self.lines[line]
            self.__openedLine = None
            if pos > 0 and len(text.strip()) == 0:
                self.__openedLine = line

        elif key in [Qt.Key_Up, Qt.Key_PageUp,
                     Qt.Key_Down, Qt.Key_PageDown]:
            line, _ = self.cursorPosition
            lineToTrim = line if line == self.__openedLine else None

            QutepartWrapper.keyPressEvent(self, event)
            QApplication.processEvents()

            if lineToTrim is not None:
                line, _ = self.cursorPosition
                if line != lineToTrim:
                    # The cursor was really moved to another line
                    self.lines[lineToTrim] = ''
            self.__openedLine = None

        elif key == Qt.Key_Escape:
            self.__resetCalltip()
            self.sigEscapePressed.emit()
            event.accept()

        elif key == Qt.Key_Tab:
            if self.selectedText:
                QutepartWrapper.keyPressEvent(self, event)
                self.__lastTabPosition = None
            else:
                line, pos = self.cursorPosition
                currentPosition = self.absCursorPosition
                if pos != 0:
                    char = self.lines[line][pos - 1]
                    if char not in [' ', ':', '{', '}', '[', ']', ',',
                                    '<', '>', '+', '!', ')'] and \
                       currentPosition != self.__lastTabPosition:
                        self.__lastTabPosition = currentPosition
                        self.onAutoComplete()
                        event.accept()
                    else:
                        QutepartWrapper.keyPressEvent(self, event)
                        self.__lastTabPosition = currentPosition
                else:
                    QutepartWrapper.keyPressEvent(self, event)
                    self.__lastTabPosition = currentPosition

        elif key == Qt.Key_Z and \
            int(event.modifiers()) == (Qt.ControlModifier + Qt.ShiftModifier):
            event.accept()

        elif key == Qt.Key_ParenLeft:
            if Settings()['editorCalltips']:
                QutepartWrapper.keyPressEvent(self, event)
                self.onShowCalltip(False)
            else:
                QutepartWrapper.keyPressEvent(self, event)
        else:
            # Special keyboard keys are delivered as 0 values
            if key != 0:
                self.__openedLine = None
                QutepartWrapper.keyPressEvent(self, event)

        self.__skipChangeCursor = False

    def _onCursorPositionChanged(self):
        """Triggered when the cursor changed the position"""
        self.__lastTabPosition = None
        line, _ = self.cursorPosition

        if self.__calltip:
            if self.__calltipTimer.isActive():
                self.__calltipTimer.stop()
            if self.absCursorPosition < self.__callPosition:
                self.__resetCalltip()
            else:
                self.__calltipTimer.start(500)

        if not self.__skipChangeCursor:
            if line == self.__openedLine:
                self.__openedLine = None
                return

            if self.__openedLine is not None:
                self.__skipChangeCursor = True
                self.lines[self.__openedLine] = ''
                self.__skipChangeCursor = False
                self.__openedLine = None

    def getCurrentPosFont(self):
        """Provides the font of the current character"""
        if self.lexer_ is not None:
            font = self.lexer_.font(self.styleAt(self.currentPosition()))
        else:
            font = self.font()
        font.setPointSize(font.pointSize() + self.getZoom())
        return font

    def onCommentUncomment(self):
        """Triggered when Ctrl+M is received"""
        if self.isReadOnly() or not self.isPythonBuffer():
            return

        with self:
            # Detect what we need - comment or uncomment
            line, _ = self.cursorPosition
            txt = self.lines[line]
            nonSpaceIndex = self.firstNonSpaceIndex(txt)
            if self.isCommentLine(line):
                # need to uncomment
                if nonSpaceIndex == len(txt) - 1:
                    # Strip the only '#' character
                    stripCount = 1
                else:
                    # Strip up to two characters if the next char is a ' '
                    if txt[nonSpaceIndex + 1] == ' ':
                        stripCount = 2
                    else:
                        stripCount = 1
                newTxt = txt[:nonSpaceIndex] + txt[nonSpaceIndex +
                                                   stripCount:]
                if not newTxt.strip():
                    newTxt = ''
                self.lines[line] = newTxt
            else:
                # need to comment
                if nonSpaceIndex is None:
                    self.lines[line] = '# '
                else:
                    newTxt = '# '.join((txt[:nonSpaceIndex],
                                        txt[nonSpaceIndex:]))
                    self.lines[line] = newTxt

            # Jump to the beginning of the next line
            if line + 1 < len(self.lines):
                line += 1
            self.cursorPosition = line, 0
            self.ensureLineOnScreen(line)

    def onAutoComplete(self):
        """Triggered when ctrl+space or TAB is clicked"""
        if self.isReadOnly():
            return

        QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
        self.__inCompletion = True
        self.__completionPrefix = self.getWordBeforeCursor()
        words = getCompletionList(self, self._parent.getFileName())

        QApplication.restoreOverrideCursor()

        if len(words) == 0:
            self.setFocus()
            self.__inCompletion = False
            return

        self.__completer.setWordsList(words, self.font())
        self.__completer.setPrefix(self.__completionPrefix)

        count = self.__completer.completionCount()
        if count == 0:
            self.setFocus()
            self.__inCompletion = False
            return

        # Make sure the line is visible
        line, _ = self.cursorPosition
        self.ensureLineOnScreen(line + 1)

        # Remove the selection as it could be interpreted not as expected
        if self.selectedText:
            self.clearSelection()

        if count == 1:
            self.insertCompletion(self.__completer.currentCompletion())
        else:
            cRectangle = self.cursorRect()
            cRectangle.setLeft(cRectangle.left() + self.viewport().x())
            cRectangle.setTop(cRectangle.top() + self.viewport().y() + 2)
            self.__completer.complete(cRectangle)
            # If something was selected then the next tab does not need to
            # bring the completion again. This is covered in the
            # insertCompletion() method. Here we reset the last tab position
            # preliminary because it is unknown if something will be inserted.
            self.__lastTabPosition = None
        self.__inCompletion = False

    def onTagHelp(self):
        """Provides help for an item if available"""
        if not self.isPythonBuffer():
            return

        QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
        definitions = getDefinitions(self, self._parent.getFileName())
        QApplication.restoreOverrideCursor()

        parts = []
        for definition in definitions:
            header = 'Type: ' + definition[3]
            if definition[5]:
                header += '\nModule: ' + definition[5]
            parts.append(header + '\n\n' + definition[4])

        if parts:
            QToolTip.showText(self.mapToGlobal(self.cursorRect().bottomLeft()),
                              '<pre>' + '\n\n'.join(parts) + '</pre>')
        else:
            GlobalData().mainWindow.showStatusBarMessage(
                "Definition is not found")

    def makeLineFirst(self):
        """Make the cursor line the first on the screen"""
        currentLine, _ = self.cursorPosition
        self.setFirstVisibleLine(currentLine)

    def onJumpToTop(self):
        """Jumps to the first position of the first visible line"""
        self.cursorPosition = self.firstVisibleLine(), 0

    def onJumpToMiddle(self):
        """Jumps to the first line pos in a middle of the editing area"""
        # Count the number of the visible line
        count = 0
        firstVisible = self.firstVisibleLine()
        lastVisible = self.lastVisibleLine()
        candidate = firstVisible
        while candidate <= lastVisible:
            if self.isLineVisible(candidate):
                count += 1
            candidate += 1

        shift = int(count / 2)
        jumpTo = firstVisible
        while shift > 0:
            if self.isLineVisible(jumpTo):
                shift -= 1
            jumpTo += 1
        self.cursorPosition = jumpTo, 0

    def onJumpToBottom(self):
        """Jumps to the first position of the last line"""
        currentFirstVisible = self.firstVisibleLine()
        self.cursorPosition = self.lastVisibleLine(), 0
        safeLastVisible = self.lastVisibleLine()

        while self.firstVisibleLine() != currentFirstVisible:
            # Here: a partially visible last line caused scrolling. So the
            # cursor needs to be set to the previous visible line
            self.cursorPosition = currentFirstVisible, 0
            safeLastVisible -= 1
            while not self.isLineVisible(safeLastVisible):
                safeLastVisible -= 1
            self.cursorPosition = safeLastVisible, 0

    def onGotoDefinition(self):
        """The user requested a jump to definition"""
        if not self.isPythonBuffer():
            return

        QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
        definitions = getDefinitions(self, self._parent.getFileName())
        QApplication.restoreOverrideCursor()

        if definitions:
            if len(definitions) == 1:
                GlobalData().mainWindow.openFile(
                    definitions[0][0], definitions[0][1],
                    definitions[0][2] + 1)
            else:
                if hasattr(self._parent, "importsBar"):
                    self._parent.importsBar.showDefinitions(definitions)
        else:
            GlobalData().mainWindow.showStatusBarMessage(
                "Definition is not found")

    def onScopeBegin(self):
        """The user requested jumping to the current scope begin"""
        if self.isPythonBuffer():
            info = getBriefModuleInfoFromMemory(self.text)
            context = getContext(self, info, True)
            if context.getScope() != context.GlobalScope:
                GlobalData().mainWindow.jumpToLine(context.getLastScopeLine())
        return

    def onShowCalltip(self, showMessage=True):
        """The user requested show calltip"""
        if self.__calltip is not None:
            self.__resetCalltip()
            return
        if not self.isPythonBuffer():
            return

        QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
        signatures = getCallSignatures(self, self._parent.getFileName())
        QApplication.restoreOverrideCursor()

        if not signatures:
            if showMessage:
                GlobalData().mainWindow.showStatusBarMessage(
                    "No calltip found")
            return

        # For the time being let's take only the first signature...
        calltipParams = []
        for param in signatures[0].params:
            calltipParams.append(param.description[len(param.type) + 1:])
        calltip = signatures[0].name + '(' + ', '.join(calltipParams) + ')'

        self.__calltip = Calltip(self)
        self.__calltip.showCalltip(calltip, signatures[0].index)

        line = signatures[0].bracket_start[0]
        column = signatures[0].bracket_start[1]
        self.__callPosition = self.mapToAbsPosition(line - 1, column)

    def __resetCalltip(self):
        """Hides the calltip and resets how it was shown"""
        self.__calltipTimer.stop()
        if self.__calltip is not None:
            self.__calltip.hide()
            self.__calltip = None
        self.__callPosition = None

    def resizeCalltip(self):
        """Resizes the calltip if so"""
        if self.__calltip:
            self.__calltip.resize()

    def __onCalltipTimer(self):
        """Handles the calltip update timer"""
        if self.__calltip:
            if self.absCursorPosition < self.__callPosition:
                self.__resetCalltip()
                return

            QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
            signatures = getCallSignatures(self, self._parent.getFileName())
            QApplication.restoreOverrideCursor()

            if not signatures:
                self.__resetCalltip()
                return

            line = signatures[0].bracket_start[0]
            column = signatures[0].bracket_start[1]
            callPosition = self.mapToAbsPosition(line - 1, column)

            if callPosition != self.__callPosition:
                self.__resetCalltip()
            else:
                # It is still the same call, check the commas
                self.__calltip.highlightParameter(signatures[0].index)

    def onOccurences(self):
        """The user requested a list of occurences"""
        if not self.isPythonBuffer():
            return
        if self._parent.getType() == MainWindowTabWidgetBase.VCSAnnotateViewer:
            return
        if not os.path.isabs(self._parent.getFileName()):
            GlobalData().mainWindow.showStatusBarMessage(
                "Please save the buffer and try again")
            return

        fileName = self._parent.getFileName()
        QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
        definitions = getOccurrences(self, fileName)
        QApplication.restoreOverrideCursor()

        if len(definitions) == 0:
            GlobalData().mainWindow.showStatusBarMessage('No occurences found')
            return

        # There are found items
        GlobalData().mainWindow.showStatusBarMessage('')
        result = []
        for definition in definitions:
            fName = definition.module_path
            if not fName:
                fName = fileName
            lineno = definition.line
            index = getSearchItemIndex(result, fName)
            if index < 0:
                widget = GlobalData().mainWindow.getWidgetForFileName(fName)
                if widget is None:
                    uuid = ""
                else:
                    uuid = widget.getUUID()
                newItem = ItemToSearchIn(fName, uuid)
                result.append(newItem)
                index = len(result) - 1
            result[index].addMatch(definition.name, lineno)

        GlobalData().mainWindow.displayFindInFiles('', result)

    def insertCompletion(self, text):
        """Triggered when a completion is selected"""
        if text:
            currentWord = self.getCurrentWord()
            line, pos = self.cursorPosition
            prefixLength = len(self.__completionPrefix)

            if text != currentWord and text != self.__completionPrefix:
                with self:
                    lineContent = self.lines[line]
                    leftPart = lineContent[0:pos - prefixLength]
                    rightPart = lineContent[pos:]
                    self.lines[line] = leftPart + text + rightPart

            newPos = pos + len(text) - prefixLength
            self.cursorPosition = line, newPos

            self.__completionPrefix = ''
            self.__completer.hide()

            # The next time there is nothing to insert for sure
            self.__lastTabPosition = self.absCursorPosition

    def insertLines(self, text, line):
        """Inserts the given text into new lines starting from 1-based line"""
        toInsert = text.splitlines()
        with self:
            if line > 0:
                line -= 1
            for item in toInsert:
                self.lines.insert(line, item)
                line += 1

    def hideCompleter(self):
        """Hides the completer if visible"""
        self.__completer.hide()

    def clearPyflakesMessages(self):
        """Clears all the pyflakes markers"""
        self.getMargin('cdm_flakes_margin').clearPyflakesMessages()

    def setPyflakesMessages(self, messages):
        """Shows up a pyflakes messages"""
        self.getMargin('cdm_flakes_margin').setPyflakesMessages(messages)

    def highlightInCFlow(self):
        """Triggered when highlight in the control flow is requested"""
        if self.isPythonBuffer():
            line, pos = self.cursorPosition
            absPos = self.absCursorPosition
            self.sigCFlowSyncRequested.emit(absPos, line + 1, pos + 1)

    def setDebugMode(self, debugOn, disableEditing):
        """Called to switch between debug/development"""
        skin = GlobalData().skin
        if debugOn:
            if disableEditing:
                self.setLinenoMarginBackgroundColor(skin['marginPaperDebug'])
                self.setLinenoMarginForegroundColor(skin['marginColorDebug'])
                self.setReadOnly(True)
        else:
            self.setLinenoMarginBackgroundColor(skin['marginPaper'])
            self.setLinenoMarginForegroundColor(skin['marginColor'])
            self.setReadOnly(False)

        bpointMargin = self.getMargin('cdm_bpoint_margin')
        if bpointMargin:
            bpointMargin.setDebugMode(debugOn, disableEditing)

    def restoreBreakpoints(self):
        """Restores the breakpoints"""
        bpointMargin = self.getMargin('cdm_bpoint_margin')
        if bpointMargin:
            bpointMargin.restoreBreakpoints()

    def isLineBreakable(self):
        """True if a line is breakable"""
        bpointMargin = self.getMargin('cdm_bpoint_margin')
        if bpointMargin:
            return bpointMargin.isLineBreakable()
        return False

    def validateBreakpoints(self):
        """Checks breakpoints and deletes those which are invalid"""
        bpointMargin = self.getMargin('cdm_bpoint_margin')
        if bpointMargin:
            bpointMargin.validateBreakpoints()

    def isPythonBuffer(self):
        """True if it is a python buffer"""
        return isPythonMime(self.mime)

    def setLinenoMarginBackgroundColor(self, color):
        """Sets the margins background"""
        linenoMargin = self.getMargin('cdm_line_number_margin')
        if linenoMargin:
            linenoMargin.setBackgroundColor(color)

    def setLinenoMarginForegroundColor(self, color):
        """Sets the lineno margin foreground color"""
        linenoMargin = self.getMargin('cdm_line_number_margin')
        if linenoMargin:
            linenoMargin.setForegroundColor(color)

    def terminate(self):
        """Overloaded version to pass the request to margins too"""
        for margin in self.getMargins():
            if hasattr(margin, 'onClose'):
                margin.onClose()
        QutepartWrapper.terminate(self)

    def resizeEvent(self, event):
        """Resize the parent panels if required"""
        QutepartWrapper.resizeEvent(self, event)
        self.hideCompleter()
        if hasattr(self._parent, 'resizeBars'):
            self._parent.resizeBars()
Пример #30
0
    def main(self):
        """The codimension driver"""
        usageMessage = 'Usage: %prog [options] [project file | python files]'
        parser = OptionParser(usage=usageMessage, version='%prog ' + VER)

        parser.add_option(
            '--debug', action='store_true', dest='debug',
            default=False,
            help='switch on debug and info messages (default: Off)')
        parser.add_option(
            '--clean-start', action='store_true', dest='cleanStart',
            default=False,
            help='do not restore previous IDE state (default: Off)')

        self.__options, self.__args = parser.parse_args()
        self.setupLogging()

        # The default exception handler can be replaced
        sys.excepthook = exceptionHook

        # Create global data singleton.
        # It's unlikely to throw any exceptions.
        globalData = GlobalData()
        globalData.version = VER

        # Loading settings - they have to be loaded before the application is
        # created. This is because the skin name is saved there.
        settings = Settings()
        populateSampleSkin()

        # Load the skin
        globalData.skin = Skin()
        globalData.skin.loadByName(settings['skin'])

        self.__delayedWarnings += settings.validateZoom(
            globalData.skin.minTextZoom, globalData.skin.minCFlowZoom)

        # QT on UBUNTU has a bug - the main menu bar does not generate the
        # 'aboutToHide' signal (though 'aboutToShow' is generated properly. This
        # prevents codimension working properly so the hack below disables the
        # global menu bar for codimension and makes it working properly.
        os.environ['QT_X11_NO_NATIVE_MENUBAR'] = '1'

        # Create QT application
        codimensionApp = CodimensionApplication(sys.argv, settings['style'])
        globalData.application = codimensionApp

        logging.debug('Starting codimension v.%s', VER)

        try:
            # Process command line arguments
            self.__projectFile = self.processCommandLineArgs()
        except Exception as exc:
            logging.error(str(exc))
            parser.print_help()
            return 1

        # Show splash screen
        self.__splash = SplashScreen()

        screenSize = codimensionApp.desktop().screenGeometry()
        globalData.screenWidth = screenSize.width()
        globalData.screenHeight = screenSize.height()

        self.__splash.showMessage('Importing packages...')
        from ui.mainwindow import CodimensionMainWindow

        self.__splash.showMessage('Generating main window...')
        mainWindow = CodimensionMainWindow(self.__splash, settings)
        codimensionApp.setMainWindow(mainWindow)
        globalData.mainWindow = mainWindow
        codimensionApp.lastWindowClosed.connect(codimensionApp.quit)

        mainWindow.show()
        mainWindow.restoreWindowPosition()
        mainWindow.restoreSplitterSizes()

        # Launch the user interface
        QTimer.singleShot(1, self.launchUserInterface)

        # Run the application main cycle
        retVal = codimensionApp.exec_()
        return retVal