Example #1
0
class CppEditor(QsciScintilla):
    '''
    classdocs
    '''
    def __init__(self, parent=None, fileName=None, readOnlyFiles=[]):
        '''
        Constructor
        '''
        super(CppEditor, self).__init__(parent)
        self.parent = parent
        self.roFiles = readOnlyFiles

        self.setAcceptDrops(False)  # drag&drop is on its parent

        # Set the default font
        font = QtGui.QFont()
        font.setFamily('Courier')
        font.setFixedPitch(True)
        font.setPointSize(10)
        self.setFont(font)
        self.setMarginsFont(font)

        # C/C++ lexer
        self.lexer = QsciLexerCPP(self, True)
        self.lexer.setDefaultFont(font)
        self.libraryAPIs = QsciAPIs(QsciLexerCPP(self, True))
        self.setLexer(self.lexer)
        self.SendScintilla(QsciScintilla.SCI_STYLESETFONT, 1, 'Courier')

        # Auto-indent
        self.setTabWidth(4)
        #self.setIndentationsUseTabs(False)
        self.setAutoIndent(True)

        # Current line visible with special background color
        self.setCaretLineVisible(True)
        self.setCaretLineBackgroundColor(QtGui.QColor("#ffe4e4"))

        # Enable brace matching
        self.setBraceMatching(QsciScintilla.SloppyBraceMatch)

        # Enable folding visual- use boxes
        self.setFolding(QsciScintilla.BoxedTreeFoldStyle)

        # show line numbers
        fontmetrics = QtGui.QFontMetrics(font)
        self.setMarginsFont(font)
        self.setMarginWidth(0, fontmetrics.width("00000") + 4)
        self.setMarginLineNumbers(0, True)
        self.setMarginsBackgroundColor(QtGui.QColor("#ccccee"))

        # not too small
        self.setMinimumSize(400, 200)

        # set the length of the string before the editor tries to auto-complete
        self.setAutoCompletionThreshold(3)
        # tell the editor we are using a QsciAPI for the auto-completion
        self.setAutoCompletionSource(QsciScintilla.AcsAPIs)
        # removed remaining right side characters from the current cursor
        self.setAutoCompletionReplaceWord(True)

        # "CTRL+Space" autocomplete
        self.shortcut_ctrl_space = QtGui.QShortcut(
            QtGui.QKeySequence("Ctrl+Space"), self)
        self.connect(self.shortcut_ctrl_space, QtCore.SIGNAL('activated()'),
                     self.autoCompleteFromAll)

        if fileName:
            self.curFile = fileName
            self.loadFile(fileName)
            self.isUntitled = False
        else:
            self.curFile = PROJECT_NONAME + USER_CODE_EXT
            self.setText(__default_content__)
            self.isUntitled = True

        self.updateApiKeywords()
        self.isModified = False
        self.connect(self, QtCore.SIGNAL('textChanged()'), self.onTextChanged)

    def onTextChanged(self):
        self.isModified = True
        self.parent.onChildContentChanged()

    def loadFile(self, fileName):
        qfile = QtCore.QFile(fileName)
        if not qfile.open(QtCore.QFile.ReadOnly | QtCore.QFile.Text):
            QtGui.QMessageBox.warning(
                self, PROJECT_ALIAS,
                "Cannot read file %s:\n%s." % (fileName, qfile.errorString()))
            return False

        ret = True
        try:
            # workaround for OS X QFile.readAll()
            f = open(qfile.fileName(), 'r')
            self.clear()
            for line in f.readlines():
                self.append(line)
        except:
            QtGui.QMessageBox.warning(self, PROJECT_ALIAS,
                                      "failed to read %s." % fileName)
            ret = False
        finally:
            f.close()

        qfile.close()
        return ret

    def saveFile(self, fileName):
        if str(fileName).find(' ') >= 0:
            QtGui.QMessageBox.warning(
                self, PROJECT_ALIAS,
                'File path "%s" contains space(s). Please save to a valid location.'
                % fileName)
            return None
        qfile = QtCore.QFile(fileName)
        if not qfile.open(QtCore.QFile.WriteOnly | QtCore.QFile.Text):
            QtGui.QMessageBox.warning(
                self, PROJECT_ALIAS,
                "Cannot write file %s:\n%s." % (fileName, qfile.errorString()))
            return None

        try:
            qfile.writeData(self.text())
        except:
            QtGui.QMessageBox.warning(self, PROJECT_ALIAS,
                                      "Failed to save %s." % fileName)
            qfile.close()
            return None

        qfile.close()
        self.curFile = fileName
        self.isUntitled = False
        self.isModified = False
        return fileName

    def saveAs(self):
        fileName = QtGui.QFileDialog.getSaveFileName(
            self, "Save As", self.curFile,
            PROJECT_ALIAS + " (*" + USER_CODE_EXT + ");;" +
            "C source (*.c);;C++ source (*.cpp);;Text File (*.txt);;All files (*.*)"
        )
        if not fileName:
            return None
        return self.saveFile(fileName)

    def save(self):
        f1 = os.path.abspath(self.curFile)
        for fname in self.roFiles:
            if f1 == os.path.abspath(fname):  # same file
                if QtGui.QMessageBox.question(self.parent, "Project is read-only",
                        "This project is marked as \"read-only\".\n" + \
                        "Please click \"Cancel\" and save this to another location.\n\n" + \
                        "Continue saving the current project anyway?", "OK", "Cancel"):
                    return None
                #return self.saveAs()
                return self.saveFile(self.curFile)
        if self.isUntitled:
            return self.saveAs()
        else:
            return self.saveFile(self.curFile)

    def currentFile(self):
        return self.curFile

    def modified(self):
        return self.isModified

    def updateApiKeywords(self):
        self.libraryAPIs.clear()
        self.apiKeywords = self.parent.getDefaultKeywords()
        headerfiles = []
        for line in range(self.lines()):
            txt = str(self.text(line)).strip()
            if txt.find('int') == 0:  # e.g. reached "int main()"
                break
            elif txt.find('#include') == 0:
                txt = ''.join(txt.split())
                temp = txt[len('#includes') - 1:]
                header = temp[1:-1]  # get the header file
                hfile = os.path.join('libraries', header[:-2], header)
                if os.path.isfile(hfile):
                    if not (hfile in headerfiles): headerfiles.append(hfile)
        if len(headerfiles):
            #print 'parsing: ', headerfiles
            self.apiKeywords += getLibraryKeywords(headerfiles)
        #self.apiKeywords = list(set(self.apiKeywords)) # remove duplicates
        for keyword in self.apiKeywords:
            self.libraryAPIs.add(keyword)
        self.libraryAPIs.prepare()
        self.lexer.setAPIs(self.libraryAPIs)

    def insertIncludeDirective(self, library=''):
        directive = '#include <' + library + '.h>\r\n'
        insert_pos = 0
        found_inc = False
        for line in range(self.lines()):
            txt = str(self.text(line)).strip()
            if txt.find('int') == 0:  # e.g. reached "int main()"
                insert_pos = line - 1
                break
            elif txt.find('#include') == 0:
                found_inc = True
            elif found_inc:
                insert_pos = line
                break
        if insert_pos < 0 or insert_pos >= self.lines():
            insert_pos = 0
        self.insertAt(directive, insert_pos, 0)
        self.updateApiKeywords()
Example #2
0
class CppEditor(QsciScintilla):
    '''
    classdocs
    '''
    def __init__(self, parent=None, fileName=None, readOnlyFiles=[]):
        '''
        Constructor
        '''
        super(CppEditor, self).__init__(parent)
        self.parent = parent
        self.roFiles = readOnlyFiles

        self.setAcceptDrops(False) # drag&drop is on its parent

        # Set the default font
        font = QtGui.QFont()
        font.setFamily('Courier')
        font.setFixedPitch(True)
        font.setPointSize(10)
        self.setFont(font)
        self.setMarginsFont(font)

        # C/C++ lexer
        self.lexer = QsciLexerCPP(self,  True)
        self.lexer.setDefaultFont(font)
        self.libraryAPIs = QsciAPIs(QsciLexerCPP(self,True))
        self.setLexer(self.lexer)
        self.SendScintilla(QsciScintilla.SCI_STYLESETFONT, 1, 'Courier')

        # Auto-indent
        self.setTabWidth(4)
        #self.setIndentationsUseTabs(False)
        self.setAutoIndent(True)

        # Current line visible with special background color
        self.setCaretLineVisible(True)
        self.setCaretLineBackgroundColor(QtGui.QColor("#ffe4e4"))

        # Enable brace matching
        self.setBraceMatching(QsciScintilla.SloppyBraceMatch)

        # Enable folding visual- use boxes
        self.setFolding(QsciScintilla.BoxedTreeFoldStyle)

        # show line numbers
        fontmetrics = QtGui.QFontMetrics(font)
        self.setMarginsFont(font)
        self.setMarginWidth(0, fontmetrics.width("00000") + 4)
        self.setMarginLineNumbers(0, True)
        self.setMarginsBackgroundColor(QtGui.QColor("#ccccee"))

        # not too small
        self.setMinimumSize(400, 200)

        # set the length of the string before the editor tries to auto-complete
        self.setAutoCompletionThreshold(3)
        # tell the editor we are using a QsciAPI for the auto-completion
        self.setAutoCompletionSource(QsciScintilla.AcsAPIs)
        # removed remaining right side characters from the current cursor
        self.setAutoCompletionReplaceWord(True)

        # "CTRL+Space" autocomplete
        self.shortcut_ctrl_space = QtGui.QShortcut(QtGui.QKeySequence("Ctrl+Space"), self)
        self.connect(self.shortcut_ctrl_space,
            QtCore.SIGNAL('activated()'), self.autoCompleteFromAll)

        if fileName:
            self.curFile = fileName
            self.loadFile(fileName)
            self.isUntitled = False
        else:
            self.curFile = PROJECT_NONAME + USER_CODE_EXT
            self.setText( __default_content__ )
            self.isUntitled = True

        self.updateApiKeywords()
        self.isModified = False
        self.connect(self, QtCore.SIGNAL('textChanged()'), self.onTextChanged )

    def onTextChanged(self):
        self.isModified = True
        self.parent.onChildContentChanged()

    def loadFile(self, fileName):
        qfile = QtCore.QFile(fileName)
        if not qfile.open(QtCore.QFile.ReadOnly | QtCore.QFile.Text):
            QtGui.QMessageBox.warning(self, PROJECT_ALIAS,
                    "Cannot read file %s:\n%s." % (fileName, qfile.errorString()))
            return False

        ret = True
        try:
            # workaround for OS X QFile.readAll()
            f = open(qfile.fileName(), 'r')
            self.clear()
            for line in f.readlines():
                self.append(line)
        except:
            QtGui.QMessageBox.warning(self, PROJECT_ALIAS,
                    "failed to read %s." % fileName )
            ret = False
        finally:
            f.close()

        qfile.close()
        return ret

    def saveFile(self, fileName):
        if str(fileName).find(' ')>=0:
            QtGui.QMessageBox.warning(self, PROJECT_ALIAS,
                    'File path "%s" contains space(s). Please save to a valid location.'%fileName)
            return None
        qfile = QtCore.QFile(fileName)
        if not qfile.open(QtCore.QFile.WriteOnly | QtCore.QFile.Text):
            QtGui.QMessageBox.warning(self, PROJECT_ALIAS,
                    "Cannot write file %s:\n%s." % (fileName, qfile.errorString()))
            return None

        try:
            qfile.writeData(self.text())
        except:
            QtGui.QMessageBox.warning(self, PROJECT_ALIAS,
                    "Failed to save %s." % fileName )
            qfile.close()
            return None

        qfile.close()
        self.curFile = fileName
        self.isUntitled = False
        self.isModified = False
        return fileName

    def saveAs(self):
        fileName = QtGui.QFileDialog.getSaveFileName(self, "Save As",
                self.curFile, PROJECT_ALIAS + " (*" + USER_CODE_EXT + ");;" +
                    "C source (*.c);;C++ source (*.cpp);;Text File (*.txt);;All files (*.*)" )
        if not fileName:
            return None
        return self.saveFile(fileName)

    def save(self):
        f1 = os.path.abspath(self.curFile)
        for fname in self.roFiles:
            if f1 == os.path.abspath(fname): # same file
                if QtGui.QMessageBox.question(self.parent, "Project is read-only",
                        "This project is marked as \"read-only\".\n" + \
                        "Please click \"Cancel\" and save this to another location.\n\n" + \
                        "Continue saving the current project anyway?", "OK", "Cancel"):
                    return None
                #return self.saveAs()
                return self.saveFile(self.curFile)
        if self.isUntitled:
            return self.saveAs()
        else:
            return self.saveFile(self.curFile)

    def currentFile(self):
        return self.curFile

    def modified(self):
        return self.isModified

    def updateApiKeywords(self):
        self.libraryAPIs.clear()
        self.apiKeywords = self.parent.getDefaultKeywords()
        headerfiles = []
        for line in range(self.lines()):
            txt = str(self.text(line)).strip()
            if txt.find('int') == 0: # e.g. reached "int main()"
                break
            elif txt.find('#include') == 0:
                txt = ''.join(txt.split())
                temp = txt[len('#includes')-1 : ]
                header = temp[1:-1] # get the header file
                hfile = os.path.join('libraries', header[:-2], header)
                if os.path.isfile(hfile):
                    if not (hfile in headerfiles): headerfiles.append( hfile )
        if len( headerfiles ):
            #print 'parsing: ', headerfiles
            self.apiKeywords += getLibraryKeywords( headerfiles )
        #self.apiKeywords = list(set(self.apiKeywords)) # remove duplicates
        for keyword in self.apiKeywords:
            self.libraryAPIs.add( keyword )
        self.libraryAPIs.prepare()
        self.lexer.setAPIs(self.libraryAPIs)

    def insertIncludeDirective(self, library=''):
        directive =  '#include <' + library + '.h>\r\n'
        insert_pos = 0
        found_inc = False
        for line in range(self.lines()):
            txt = str(self.text(line)).strip()
            if txt.find('int') == 0: # e.g. reached "int main()"
                insert_pos = line - 1
                break
            elif txt.find('#include') == 0:
                found_inc = True
            elif found_inc:
                insert_pos = line
                break
        if insert_pos < 0 or insert_pos >= self.lines():
            insert_pos = 0
        self.insertAt(directive, insert_pos, 0)
        self.updateApiKeywords()
Example #3
0
class CommandShell(QsciScintilla):
    def __init__(self, parent=None):
        super(CommandShell, self).__init__(parent)
        self.setMinimumHeight(50)
        self.setMaximumHeight(50)
        self.settings = QSettings()
        self.lex = QsciLexerPython(self)
        self.apis = QsciAPIs(self.lex)
        self.lex.setAPIs(self.apis)
        self.setLexers()
        self.SendScintilla(QsciScintilla.SCI_SETHSCROLLBAR, 0)
        self.SendScintilla(QsciScintilla.SCI_SETVSCROLLBAR, 0)
        self.setFolding(0)
        self._start_prompt = _start_prompt
        self.prompt = self._start_prompt
        self.currentfunction = None
        self.setAutoCompletionSource(self.AcsAPIs)
        self.setAutoCompletionThreshold(1)
        self.setAutoCompletionReplaceWord(True)
        self.setCallTipsStyle(QsciScintilla.CallTipsNoContext)
        self.parent().installEventFilter(self)
        self.textChanged.connect(self.text_changed)
        self._lastcompletions = None

    def text_changed(self):
        if not self.get_data().strip():
            return

        try:
            completions = command.completions_for_line(self.get_data())
            if completions == self._lastcompletions:
                self.autoCompleteFromAPIs()
                return

            self._lastcompletions = completions

            self.apis.cancelPreparation()
            self.apis.clear()
            for value in completions:
                data = u"{}".format(value)
                self.apis.add(data)

            self.apis.prepare()
        except command.NoFunction:
            return

    def end(self):
        self.parent().removeEventFilter(self)
        self.close()

    def eventFilter(self, object, event):
        if event.type() == QEvent.Resize:
            self.adjust_size()
        return QWidget.eventFilter(self, object, event)

    def keyPressEvent(self, e):
        if e.key() in (Qt.Key_Return, Qt.Key_Enter):
            self.entered()
        elif e.key() == Qt.Key_Escape:
            self.close()
        elif e.key() in (Qt.Key_Backspace, Qt.Key_Delete):
            _, newindex = self.getCursorPosition()
            if newindex > len(self.prompt):
                QsciScintilla.keyPressEvent(self, e)
        else:
            QsciScintilla.keyPressEvent(self, e)

    def show_prompt(self, prompt=_start_prompt, data=None):
        self.clear()
        if not prompt == _start_prompt:
            prompt += ":"

        text = prompt

        if data:
            text = prompt + str(data)

        self.setText(text)
        self.prompt = prompt
        self.move_cursor_to_end()

    def get_end_pos(self):
        """Return (line, index) position of the last character"""
        line = self.lines() - 1
        return (line, len(self.text(line)))

    def move_cursor_to_end(self):
        """Move cursor to end of text"""
        line, index = self.get_end_pos()
        self.setCursorPosition(line, index)
        self.ensureCursorVisible()
        self.ensureLineVisible(line)

    def get_data(self):
        line = self.text()
        line = line[len(self.prompt):]
        return line

    def entered(self):
        line = self.get_data()
        if not self.currentfunction:
            try:
                gen = command.parse_line_data(line)
            except command.NoFunction:
                return

            if gen:
                self.currentfunction = gen
                line = None
            else:
                self.currentfunction = None
                self.show_prompt()
                return

        try:
            prompt, data = self.currentfunction.send(line)
            self.show_prompt(prompt, data)
        except StopIteration:
            self.currentfunction = None
            self.show_prompt()

    def setLexers(self):
        loadFont = self.settings.value("pythonConsole/fontfamilytext", "Monospace")
        fontSize = self.settings.value("pythonConsole/fontsize", 10, type=int)

        font = QFont(loadFont)
        font.setFixedPitch(True)
        font.setPointSize(fontSize)
        font.setStyleHint(QFont.TypeWriter)
        font.setStretch(QFont.SemiCondensed)
        font.setLetterSpacing(QFont.PercentageSpacing, 87.0)
        font.setBold(False)

        self.lex.setFont(font)
        self.lex.setDefaultFont(font)
        self.setLexer(self.lex)

    def adjust_size(self):
        fm = QFontMetrics(self.font())
        self.setMaximumHeight(20)
        self.resize(self.parent().width(), 20)
        self.move(0, self.parent().height() - self.height())

    def showEvent(self, event):
        self.adjust_size()
        self.show_prompt()
        self.setFocus()

    def activated(self):
        visible = self.isVisible()
        self.setVisible(not visible)
class APIs(QObject):
    """
    Class implementing an API storage entity.
    
    @signal apiPreparationFinished() emitted after the API preparation has finished
    @signal apiPreparationCancelled() emitted after the API preparation has been cancelled
    @signal apiPreparationStarted() emitted after the API preparation has started
    """
    def __init__(self, language, forPreparation = False, parent = None):
        """
        Constructor
        
        @param language language of the APIs object (string)
        @param forPreparation flag indicating this object is just needed
            for a preparation process (boolean)
        @param parent reference to the parent object (QObject)
        """
        QObject.__init__(self, parent)
        self.setObjectName("APIs_%s" % language)
        
        self.__inPreparation = False
        self.__language = language
        self.__forPreparation = forPreparation
        self.__lexer = Lexers.getLexer(self.__language)
        self.__apifiles = Preferences.getEditorAPI(self.__language)
        self.__apifiles.sort()
        if self.__lexer is None:
            self.__apis = None
        else:
            self.__apis = QsciAPIs(self.__lexer)
            self.connect(self.__apis, SIGNAL("apiPreparationFinished()"),
                         self.__apiPreparationFinished)
            self.connect(self.__apis, SIGNAL("apiPreparationCancelled()"),
                         self.__apiPreparationCancelled)
            self.connect(self.__apis, SIGNAL("apiPreparationStarted()"),
                         self.__apiPreparationStarted)
            self.__loadAPIs()
        
    def __loadAPIs(self):
        """
        Private method to load the APIs.
        """
        if self.__apis.isPrepared():
            # load a prepared API file
            if not self.__forPreparation and Preferences.getEditor("AutoPrepareAPIs"):
                self.prepareAPIs()
            self.__apis.loadPrepared()
        else:
            # load the raw files and prepare the API file
            if not self.__forPreparation and Preferences.getEditor("AutoPrepareAPIs"):
                self.prepareAPIs(ondemand = True)
    
    def reloadAPIs(self):
        """
        Public method to reload the API information.
        """
        if not self.__forPreparation and Preferences.getEditor("AutoPrepareAPIs"):
            self.prepareAPIs()
        self.__loadAPIs()
    
    def getQsciAPIs(self):
        """
        Public method to get a reference to QsciAPIs object.
        
        @return reference to the QsciAPIs object (QsciAPIs)
        """
        if not self.__forPreparation and Preferences.getEditor("AutoPrepareAPIs"):
            self.prepareAPIs()
        return self.__apis
    
    def __apiPreparationFinished(self):
        """
        Private method called to save an API, after it has been prepared.
        """
        res = self.__apis.savePrepared()
        self.__inPreparation = False
        self.emit(SIGNAL('apiPreparationFinished()'))
    
    def __apiPreparationCancelled(self):
        """
        Private method called, after the API preparation process has been cancelled.
        """
        self.__inPreparation = False
        self.emit(SIGNAL('apiPreparationCancelled()'))
    
    def __apiPreparationStarted(self):
        """
        Private method called, when the API preparation process started.
        """
        self.__inPreparation = True
        self.emit(SIGNAL('apiPreparationStarted()'))
    
    def prepareAPIs(self, ondemand = False, rawList = None):
        """
        Public method to prepare the APIs if necessary.
        
        @keyparam ondemand flag indicating a requested preparation (boolean)
        @keyparam rawList list of raw API files (QStringList)
        """
        if self.__apis is None or self.__inPreparation:
            return
        
        needsPreparation = False
        if ondemand:
            needsPreparation = True
        else:
            # check, if a new preparation is necessary
            preparedAPIs = self.__defaultPreparedName()
            if not preparedAPIs.isEmpty():
                preparedAPIsInfo = QFileInfo(preparedAPIs)
                if not preparedAPIsInfo.exists():
                    needsPreparation = True
                else:
                    preparedAPIsTime = preparedAPIsInfo.lastModified()
                    apifiles = Preferences.getEditorAPI(self.__language)
                    apifiles.sort()
                    if self.__apifiles != apifiles:
                        needsPreparation = True
                    for apifile in apifiles:
                        if QFileInfo(apifile).lastModified() > preparedAPIsTime:
                            needsPreparation = True
                            break
        
        if needsPreparation:
            # do the preparation
            self.__apis.clear()
            if rawList:
                apifiles = rawList
            else:
                apifiles = Preferences.getEditorAPI(self.__language)
            for apifile in apifiles:
                self.__apis.load(apifile)
            self.__apis.prepare()
            self.__apifiles = apifiles
    
    def cancelPreparation(self):
        """
        Public slot to cancel the APIs preparation.
        """
        self.__apis and self.__apis.cancelPreparation()
    
    def installedAPIFiles(self):
        """
        Public method to get a list of installed API files.
        
        @return list of installed API files (QStringList)
        """
        if self.__apis is not None:
            if Utilities.isWindowsPlatform():
                qsciPath = os.path.join(Utilities.getPyQt4ModulesDirectory(), "qsci")
                if os.path.exists(qsciPath):
                    # it's the installer
                    if self.__lexer.lexer() is not None:
                        apidir = os.path.join(qsciPath, "api", self.__lexer.lexer())
                        fnames = QStringList()
                        filist = QDir(apidir).entryInfoList(["*.api"], QDir.Files, 
                                                            QDir.IgnoreCase)
                        for fi in filist:
                            fnames.append(fi.absoluteFilePath())
                        return fnames
                    else:
                        return QStringList()
            
            return self.__apis.installedAPIFiles()
        else:
            return QStringList()
    
    def __defaultPreparedName(self):
        """
        Private method returning the default name of a prepared API file.
        
        @return complete filename for the Prepared APIs file (QString)
        """
        if self.__apis is not None:
            return self.__apis.defaultPreparedName()
        else:
            return QString()