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()
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()
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()