class ScriptTab(QWidget,): autocomplete_lst = [] def __init__(self, parent, name, script="", filemodified=0): QWidget.__init__(self) self.parent = parent self.tabWidget = self.parent.ui.tabWidget self.gridlayout = QGridLayout(self) self._dirty = False self.initialize_editor() self.gridlayout.addWidget(self.editor) self.setAcceptDrops(True) self.filename = name self.filemodified = filemodified if self.is_file(): self.title = os.path.basename(name) # if self.equals_saved(): # self.filemodified = os.path.getmtime(self.filename) else: self.filename = "" self.title = name # Show this file in the self.editor self.editor.setText(script) self.clean_txt = self.saved() self.update_dirty() self.editor.keyPressEvent = self.key_press_event @property def scriptRunner(self): return self.parent.controller.scriptRunner def wheelEvent(self, event): if event.modifiers() == Qt.ControlModifier: if event.delta() > 0: self.editor.zoomIn() else: self.editor.zoomOut() return QWidget.wheelEvent(self, event) def initialize_editor(self): self.editor = QsciScintilla() # self.editor.cursorPositionChanged.connect(self.e) # self.editor.copyAvailable.connect(self.e) # self.editor.indicatorClicked.connect(self.e) # self.editor.indicatorReleased.connect(self.e) # self.editor.linesChanged.connect(self.e) # self.editor.marginClicked.connect(self.e) # self.editor.modificationAttempted.connect(self.e) # self.editor.modificationChanged.connect(self.e) # self.editor.selectionChanged.connect(self.e) # self.editor.textChanged.connect(self.e) # self.editor.userListActivated.connect(self.e) if self.editor.__class__.__name__ == "LineTextWidget": return # When using PySide without QSciScintilla # define the font to use font = QFont() font.setFamily("Consolas") font.setFixedPitch(True) font.setPointSize(10) # the font metrics here will help # building the margin width later fm = QFontMetrics(font) # set the default font of the self.editor # and take the same font for line numbers self.editor.setFont(font) self.editor.setMarginsFont(font) # Line numbers # conventionnaly, margin 0 is for line numbers self.editor.setMarginWidth(0, fm.width("00000") + 5) self.editor.setMarginLineNumbers(0, True) self.editor.setTabWidth(4) # Folding visual : we will use boxes self.editor.setFolding(QsciScintilla.BoxedTreeFoldStyle) self.editor.setAutoIndent(True) # Braces matching self.editor.setBraceMatching(QsciScintilla.SloppyBraceMatch) # Editing line color self.editor.setCaretLineVisible(True) self.editor.setCaretLineBackgroundColor(QColor("#CDA869")) # Margins colors # line numbers margin self.editor.setMarginsBackgroundColor(QColor("#333333")) self.editor.setMarginsForegroundColor(QColor("#CCCCCC")) # folding margin colors (foreground,background) self.editor.setFoldMarginColors(QColor("#99CC66"), QColor("#333300")) # Choose a lexer self.lexer = QsciLexerPython() self.lexer.setDefaultFont(font) # Set the length of the string before the editor tries to autocomplete # In practise this would be higher than 1 # But its set lower here to make the autocompletion more obvious self.editor.setAutoCompletionThreshold(1) # Tell the editor we are using a QsciAPI for the autocompletion self.editor.setAutoCompletionSource(QsciScintilla.AcsAPIs) self.editor.setLexer(self.lexer) self.editor.setCallTipsStyle(QsciScintilla.CallTipsContext) # self.editor.setCallTipsVisible(0) # Create an API for us to populate with our autocomplete terms self.api = QsciAPIs(self.lexer) # Compile the api for use in the lexer self.api.prepare() # def tefocusInEvent(self, event): # self.parent.focusInEvent(event) # return QTextEdit.focusInEvent(self.textEditScript, event) def is_file(self): return os.path.isfile(self.filename) def is_modified(self): if self.is_file(): if self.equals_saved(): self.filemodified = os.path.getmtime(self.filename) return str(os.path.getmtime(self.filename)) != str(self.filemodified) else: return False def saved(self): if self.is_file(): f = open(self.filename) saved = f.read() f.close() return saved.strip() else: return "" def equals_saved(self): curr_lines = self.get_script().strip().splitlines() saved_lines = self.saved().strip().splitlines() if len(curr_lines) != len(saved_lines): return False for cl, sl in zip(curr_lines, saved_lines): if cl.strip() != sl.strip(): return False return True @property def dirty(self): if not self._dirty: self.dirty = self.clean_txt != self.get_script() return self._dirty @dirty.setter def dirty(self, dirty): if dirty is False: self.clean_txt = self.get_script() if self._dirty != dirty: self._dirty = dirty self.filename_changed() def update_dirty(self): return self.dirty def index(self): return self.tabWidget.indexOf(self) def filename_changed(self): index = self.parent.ui.tabWidget.indexOf(self) if self.dirty: self.tabWidget.setTabText(index, "%s*" % self.title) else: self.tabWidget.setTabText(index, self.title) def reload(self): self.set_script(self.parent._load_script(self.filename)) self.filemodified = os.path.getmtime(self.filename) def close(self): while self.dirty is True: # While avoids data loss, in case save operation is aborted if self.filename == "": text = "Save unsaved changes?" else: text = "Save %s?" % self.filename ans = QMessageBox.question(self, 'Save', text, QMessageBox.Yes, QMessageBox.Cancel, QMessageBox.No) if ans == QMessageBox.Cancel: return elif ans == QMessageBox.Yes: self.parent.actionSave(False) elif ans == QMessageBox.No: break self.tabWidget.removeTab(self.index()) def _save(self,): f = open(self.filename, 'w') f.write(self.get_script()) f.close() self.title = os.path.basename(self.filename) self.dirty = False def key_press_event(self, event): self.update_dirty() if self.editor.__class__.__name__ == "LineTextWidget": return self.editor.edit.keyReleaseEvent(event) # When using PySide without QSciScintilla QsciScintilla.keyPressEvent(self.editor, event) linenr, pos_in_line = self.editor.getCursorPosition() line = str(self.editor.text(linenr)[:pos_in_line]) if event.key() == Qt.Key_F1: try: self.parent.show_documentation(getattr(self.scriptRunner, str(self.editor.selectedText()))) except AttributeError: pass if event.key() == Qt.Key_Enter or event.key() == Qt.Key_Return: for tip in self.autocomplete_lst: try: if line.endswith(tip[:tip.index('(') ]): self.editor.insert(tip[tip.index('('):]) parent, residual = self.scriptRunner.split_line(line) self.parent.show_documentation(self.scriptRunner.get_function_dict(parent, residual)[residual]) return except ValueError: pass if event.key() < 100 or event.key() in [Qt.Key_Backspace, Qt.Key_Shift]: linenr, pos_in_line = self.editor.getCursorPosition() line = str(self.editor.text(linenr)[:pos_in_line]) lst = self.scriptRunner.get_autocomplete_list(line) if lst is not None: self.api.clear() list(map(self.api.add, lst)) self.api.prepare() if len(lst) > 0: self.autocomplete_lst = lst self.editor.autoCompleteFromAll() shift_event = QKeyEvent(QEvent.KeyPress, Qt.Key_Shift, Qt.NoModifier) QsciScintilla.keyPressEvent(self.editor, shift_event) # show autocomplete list def set_script(self, script): self.editor.setText(script) def get_script(self): return str(self.editor.text()).replace("\r", "").replace(">>> ", "").replace("\t", " ") def dropHandler(self, dataItemList, ctrl, shift, event): pass
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 """ apiPreparationFinished = pyqtSignal() apiPreparationCancelled = pyqtSignal() apiPreparationStarted = pyqtSignal() def __init__(self, language, projectType="", forPreparation=False, parent=None): """ Constructor @param language language of the APIs object @type str @param projectType type of the project @type str @param forPreparation flag indicating this object is just needed for a preparation process @type bool @param parent reference to the parent object @type QObject """ super(APIs, self).__init__(parent) if projectType: self.setObjectName("APIs_{0}_{1}".format(language, projectType)) else: self.setObjectName("APIs_{0}".format(language)) self.__inPreparation = False self.__language = language self.__projectType = projectType self.__forPreparation = forPreparation self.__lexer = Lexers.getLexer(self.__language) self.__apifiles = Preferences.getEditorAPI(self.__language, self.__projectType) self.__apifiles.sort() if self.__lexer is None: self.__apis = None else: self.__apis = QsciAPIs(self.__lexer) self.__apis.apiPreparationFinished.connect( self.__apiPreparationFinished) self.__apis.apiPreparationCancelled.connect( self.__apiPreparationCancelled) self.__apis.apiPreparationStarted.connect( 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(self.__preparedName()) 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 isEmpty(self): """ Public method to check, if the object has API files configured. @return flag indicating no API files have been configured (boolean) """ return len(self.__apifiles) == 0 def __apiPreparationFinished(self): """ Private method called to save an API, after it has been prepared. """ self.__apis.savePrepared(self.__preparedName()) self.__inPreparation = False self.apiPreparationFinished.emit() def __apiPreparationCancelled(self): """ Private method called, after the API preparation process has been cancelled. """ self.__inPreparation = False self.apiPreparationCancelled.emit() def __apiPreparationStarted(self): """ Private method called, when the API preparation process started. """ self.__inPreparation = True self.apiPreparationStarted.emit() 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 (list of strings) """ 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.__preparedName() if preparedAPIs: preparedAPIsInfo = QFileInfo(preparedAPIs) if not preparedAPIsInfo.exists(): needsPreparation = True else: preparedAPIsTime = preparedAPIsInfo.lastModified() apifiles = sorted( Preferences.getEditorAPI(self.__language, self.__projectType)) 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, self.__projectType) 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 (list of strings) """ if self.__apis is not None: if Globals.isWindowsPlatform(): qsciPath = os.path.join(Globals.getPyQt5ModulesDirectory(), "qsci") if os.path.exists(qsciPath): # it's the installer if self.__lexer.lexerName() is not None: apidir = os.path.join(qsciPath, "api", self.__lexer.lexerName()) fnames = [] filist = QDir(apidir).entryInfoList(["*.api"], QDir.Files, QDir.IgnoreCase) for fi in filist: fnames.append(fi.absoluteFilePath()) return fnames else: return [] return self.__apis.installedAPIFiles() else: return [] def __preparedName(self): """ Private method returning the default name of a prepared API file. @return complete filename for the Prepared APIs file (string) """ apisDir = os.path.join(Globals.getConfigDir(), "APIs") if self.__apis is not None: if self.__projectType: filename = "{0}_{1}.pap".format(self.__language, self.__projectType) else: filename = "{0}.pap".format(self.__language) return os.path.join(apisDir, filename) else: return ""
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 """ apiPreparationFinished = pyqtSignal() apiPreparationCancelled = pyqtSignal() apiPreparationStarted = pyqtSignal() 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) """ super(APIs, self).__init__(parent) self.setObjectName("APIs_{0}".format(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.__apis.apiPreparationFinished.connect( self.__apiPreparationFinished) self.__apis.apiPreparationCancelled.connect( self.__apiPreparationCancelled) self.__apis.apiPreparationStarted.connect( 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 isEmpty(self): """ Public method to check, if the object has API files configured. @return flag indicating no API files have been configured (boolean) """ return len(self.__apifiles) == 0 def __apiPreparationFinished(self): """ Private method called to save an API, after it has been prepared. """ self.__apis.savePrepared() self.__inPreparation = False self.apiPreparationFinished.emit() def __apiPreparationCancelled(self): """ Private method called, after the API preparation process has been cancelled. """ self.__inPreparation = False self.apiPreparationCancelled.emit() def __apiPreparationStarted(self): """ Private method called, when the API preparation process started. """ self.__inPreparation = True self.apiPreparationStarted.emit() 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 (list of strings) """ 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 preparedAPIs: preparedAPIsInfo = QFileInfo(preparedAPIs) if not preparedAPIsInfo.exists(): needsPreparation = True else: preparedAPIsTime = preparedAPIsInfo.lastModified() apifiles = sorted( Preferences.getEditorAPI(self.__language)) 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 (list of strings) """ if self.__apis is not None: if Globals.isWindowsPlatform(): qsciPath = os.path.join( Globals.getPyQt5ModulesDirectory(), "qsci") if os.path.exists(qsciPath): # it's the installer if self.__lexer.lexerName() is not None: apidir = os.path.join(qsciPath, "api", self.__lexer.lexerName()) fnames = [] filist = QDir(apidir).entryInfoList( ["*.api"], QDir.Files, QDir.IgnoreCase) for fi in filist: fnames.append(fi.absoluteFilePath()) return fnames else: return [] return self.__apis.installedAPIFiles() else: return [] def __defaultPreparedName(self): """ Private method returning the default name of a prepared API file. @return complete filename for the Prepared APIs file (string) """ if self.__apis is not None: return self.__apis.defaultPreparedName() else: return ""
class CodeEditor(QsciScintilla): def __init__(self, parent=None): super().__init__(parent) self.filename = None self.fileBrowser = None self.mainWindow = parent self.debugging = False c = Configuration() self.pointSize = int(c.getFont()) self.tabWidth = int(c.getTab()) # Scrollbars self.verticalScrollBar().setStyleSheet("""border: 20px solid black; background-color: darkgreen; alternate-background-color: #FFFFFF;""") self.horizontalScrollBar().setStyleSheet("""border: 20px solid black; background-color: darkgreen; alternate-background-color: #FFFFFF;""") # matched / unmatched brace color ... self.setMatchedBraceBackgroundColor(QColor('#000000')) self.setMatchedBraceForegroundColor(QColor('cyan')) self.setUnmatchedBraceBackgroundColor(QColor('#000000')) self.setUnmatchedBraceForegroundColor(QColor('red')) self.setBraceMatching(QsciScintilla.SloppyBraceMatch) # edge mode ... line at 79 characters self.setEdgeColumn(79) self.setEdgeMode(1) self.setEdgeColor(QColor('dark green')) # Set the default font self.font = QFont() self.font.setFamily(c.getFontFamily()) self.font.setFixedPitch(True) self.font.setPointSize(self.pointSize) self.setFont(self.font) self.setMarginsFont(self.font) # Margin 0 is used for line numbers fontmetrics = QFontMetrics(self.font) self.setMarginsFont(self.font) self.setMarginWidth(0, fontmetrics.width("00000") + 5) self.setMarginLineNumbers(0, True) self.setMarginsBackgroundColor(QColor("#000000")) self.setMarginsForegroundColor(QColor("#FFFFFF")) # Margin 1 for breakpoints self.setMarginType(1, QsciScintilla.SymbolMargin) self.setMarginWidth(1, "00") sym = QsciScintilla.Circle self.markerDefine(sym, 1) self.setMarginMarkerMask(0, 0b1111) handle_01 = self.markerAdd(1, 0) # FoldingBox self.setFoldMarginColors(QColor('dark green'), QColor('dark green')) # CallTipBox self.setCallTipsForegroundColor(QColor('#FFFFFF')) self.setCallTipsBackgroundColor(QColor('#282828')) self.setCallTipsHighlightColor(QColor('#3b5784')) self.setCallTipsStyle(QsciScintilla.CallTipsContext) self.setCallTipsPosition(QsciScintilla.CallTipsBelowText) self.setCallTipsVisible(-1) # change caret's color self.SendScintilla(QsciScintilla.SCI_SETCARETFORE, QColor('#98fb98')) self.setCaretWidth(4) # tab Width self.setIndentationsUseTabs(False) self.setTabWidth(self.tabWidth) # use Whitespaces instead tabs self.SendScintilla(QsciScintilla.SCI_SETUSETABS, False) self.setAutoIndent(True) self.setTabIndents(True) # BackTab self.setBackspaceUnindents(True) # Current line visible with special background color or not :) #self.setCaretLineVisible(False) #self.setCaretLineVisible(True) #self.setCaretLineBackgroundColor(QColor("#020202")) # not too small self.setMinimumSize(300, 300) # get style self.style = None # Call the Color-Function: ... self.setPythonStyle() #self.SendScintilla(QsciScintilla.SCI_SETHSCROLLBAR, 0) # Contextmenu self.setContextMenuPolicy(Qt.ActionsContextMenu) undoAction = QAction("Undo", self) undoAction.triggered.connect(self.undoContext) redoAction = QAction("Redo", self) redoAction.triggered.connect(self.redoContext) sepAction1 = QAction("", self) sepAction1.setSeparator(True) cutAction = QAction("Cut", self) cutAction.triggered.connect(self.cutContext) copyAction = QAction("Copy", self) copyAction.triggered.connect(self.copyContext) pasteAction = QAction("Paste", self) pasteAction.triggered.connect(self.pasteContext) sepAction2 = QAction("", self) sepAction2.setSeparator(True) sepAction3 = QAction("", self) sepAction3.setSeparator(True) selectAllAction = QAction("Select All", self) selectAllAction.triggered.connect(self.getContext) terminalAction = QAction("Open Terminal", self) terminalAction.triggered.connect(self.termContext) self.addAction(undoAction) self.addAction(redoAction) self.addAction(sepAction1) self.addAction(cutAction) self.addAction(copyAction) self.addAction(pasteAction) self.addAction(sepAction2) self.addAction(selectAllAction) self.addAction(sepAction3) self.addAction(terminalAction) # signals self.SCN_FOCUSIN.connect(self.onFocusIn) self.textChanged.connect(self.onTextChanged) self.marginClicked.connect(self.onMarginClicked) def onFocusIn(self): self.mainWindow.refresh(self) def onTextChanged(self): notebook = self.mainWindow.notebook textPad = notebook.currentWidget() index = notebook.currentIndex() if self.debugging is True: self.mainWindow.statusBar.showMessage( 'remember to update CodeView if you delete or change lines in CodeEditor !', 3000) if textPad == None: return if textPad.filename: if not '*' in notebook.tabText(index): fname = os.path.basename(textPad.filename) fname += '*' notebook.setTabText(index, fname) else: fname = notebook.tabText(index) fname += '*' if not '*' in notebook.tabText(index): notebook.setTabText(index, fname) def onMarginClicked(self): pass def checkPath(self, path): if '\\' in path: path = path.replace('\\', '/') return path def undoContext(self): self.undo() def redoContext(self): self.redo() def cutContext(self): self.cut() def copyContext(self): self.copy() def pasteContext(self): self.paste() def getContext(self): self.selectAll() def termContext(self): c = Configuration() system = c.getSystem() command = c.getTerminal(system) thread = RunThread(command) thread.start() def getLexer(self): return self.lexer def setPythonStyle(self): self.style = 'Python' # Set Python lexer self.setAutoIndent(True) #self.lexer = QsciLexerPython() self.lexer = PythonLexer() self.lexer.setFont(self.font) self.lexer.setFoldComments(True) # set Lexer self.setLexer(self.lexer) self.setCaretLineBackgroundColor(QColor("#344c4c")) self.lexer.setDefaultPaper(QColor("black")) self.lexer.setDefaultColor(QColor("white")) self.lexer.setColor(QColor('white'), 0) # default self.lexer.setPaper(QColor('black'), -1) # default -1 vor all styles self.lexer.setColor(QColor('gray'), PythonLexer.Comment) # = 1 self.lexer.setColor(QColor('orange'), 2) # Number = 2 self.lexer.setColor(QColor('lightblue'), 3) # DoubleQuotedString self.lexer.setColor(QColor('lightblue'), 4) # SingleQuotedString self.lexer.setColor(QColor('#33cccc'), 5) # Keyword self.lexer.setColor(QColor('lightblue'), 6) # TripleSingleQuotedString self.lexer.setColor(QColor('lightblue'), 7) # TripleDoubleQuotedString self.lexer.setColor(QColor('#ffff00'), 8) # ClassName self.lexer.setColor(QColor('#ffff66'), 9) # FunctionMethodName self.lexer.setColor(QColor('magenta'), 10) # Operator self.lexer.setColor(QColor('white'), 11) # Identifier self.lexer.setColor(QColor('gray'), 12) # CommentBlock self.lexer.setColor(QColor('#ff471a'), 13) # UnclosedString self.lexer.setColor(QColor('gray'), 14) # HighlightedIdentifier self.lexer.setColor(QColor('#5DD3AF'), 15) # Decorator self.setPythonAutocomplete() self.setFold() def setPythonAutocomplete(self): self.autocomplete = QsciAPIs(self.lexer) self.keywords = self.lexer.keywords(1) self.keywords = self.keywords.split(' ') for word in self.keywords: self.autocomplete.add(word) self.autocomplete.add('super') self.autocomplete.add('self') self.autocomplete.add('__name__') self.autocomplete.add('__main__') self.autocomplete.add('__init__') self.autocomplete.add('__str__') self.autocomplete.add('__repr__') self.autocomplete.prepare() ## Set the length of the string before the editor tries to autocomplete self.setAutoCompletionThreshold(3) ## Tell the editor we are using a QsciAPI for the autocompletion self.setAutoCompletionSource(QsciScintilla.AcsAPIs) self.updateAutoComplete() def setFold(self): # setup Fold Styles for classes and functions ... x = self.FoldStyle(self.FoldStyle(5)) #self.textPad.folding() if not x: self.foldAll(False) self.setFolding(x) #self.textPad.folding() def unsetFold(self): self.setFolding(0) def keyReleaseEvent(self, e): # feed the autocomplete with the words from editor # simple algorithm to do this ... everytime after Enter # refresh CodeView text = self.text() self.updateCodeView(text) # if ENTER was hit ... : if e.key() == Qt.Key_Return: self.updateAutoComplete() def updateCodeView(self, text=''): codeView = self.mainWindow.codeView codeViewDict = codeView.makeDictForCodeView(text) codeView.updateCodeView(codeViewDict) def updateAutoComplete(self, text=None): if not text: firstList = [] # list to edit secondList = [] # collect all items for autocomplete text = self.text() # parse complete text .... firstList = text.splitlines() for line in firstList: if 'def' in line: item = line.strip() item = item.strip('def') item = item.replace(':', '') if not item in secondList: secondList.append(item) elif 'class' in line: item = line.strip() item = item.strip('class') item = item.replace(':', '') if not item in secondList: secondList.append(item) text = text.replace('"', " ").replace("'", " ").replace("(", " ").replace\ (")", " ").replace("[", " ").replace("]", " ").replace\ (':', " ").replace(',', " ").replace("<", " ").replace\ (">", " ").replace("/", " ").replace("=", " ").replace\ (";", " ") firstList = text.split('\n') for row in firstList: if (row.strip().startswith('#')) or ( row.strip().startswith('//')): continue else: wordList = row.split() for word in wordList: if re.match("(^[0-9])", word): continue elif '#' in word or '//' in word: continue elif word in self.keywords: continue elif (word == '__init__') or (word == '__main__') or \ (word == '__name__') or (word == '__str__') or \ (word == '__repr__'): continue elif word in secondList: continue elif len(word) > 15: continue elif not len(word) < 3: w = re.sub("{}<>;,:]", '', word) #print(w) secondList.append(w) # delete doubled entries x = set(secondList) secondList = list(x) # debugging ... #print(secondList) for item in secondList: self.autocomplete.add(item) self.autocomplete.prepare() def setPythonPrintStyle(self): # Set None lexer self.font = QFont() self.font.setFamily(c.getFontFamily()) self.font.setFixedPitch(True) self.font.setPointSize(10) self.setFont(self.font) self.lexer = PythonLexer() self.lexer.setFont(self.font) # set Lexer self.setLexer(self.lexer) self.setCaretLineBackgroundColor(QColor("#344c4c")) self.lexer.setDefaultPaper(QColor("white")) self.lexer.setDefaultColor(QColor("black")) self.lexer.setColor(QColor('black'), -1) # default self.lexer.setPaper(QColor('white'), -1) # default self.lexer.setColor(QColor('gray'), PythonLexer.Comment) # entspricht 1 self.lexer.setColor(QColor('orange'), 2) # Number entspricht 2 self.lexer.setColor(QColor('darkgreen'), 3) # DoubleQuotedString entspricht 3 self.lexer.setColor(QColor('darkgreen'), 4) # SingleQuotedString entspricht 4 self.lexer.setColor(QColor('darkblue'), 5) # Keyword entspricht 5 self.lexer.setColor(QColor('darkgreen'), 6) # TripleSingleQuotedString entspricht 6 self.lexer.setColor(QColor('darkgreen'), 7) # TripleDoubleQuotedString entspricht 7 self.lexer.setColor(QColor('red'), 8) # ClassName entspricht 8 self.lexer.setColor(QColor('crimson'), 9) # FunctionMethodName entspricht 9 self.lexer.setColor(QColor('green'), 10) # Operator entspricht 10 self.lexer.setColor(QColor('black'), 11) # Identifier entspricht 11 ### alle Wörter self.lexer.setColor(QColor('gray'), 12) # CommentBlock entspricht 12 self.lexer.setColor(QColor('#ff471a'), 13) # UnclosedString entspricht 13 self.lexer.setColor(QColor('gray'), 14) # HighlightedIdentifier entspricht 14 self.lexer.setColor(QColor('#5DD3AF'), 15) # Decorator entspricht 15 self.setNoneAutocomplete() self.unsetFold() self.font = QFont() self.font.setFamily('Mono') self.font.setFixedPitch(True) self.font.setPointSize(self.pointSize) def setNoneAutocomplete(self): #AutoCompletion self.autocomplete = Qsci.QsciAPIs(self.lexer) self.autocomplete.clear() self.autocomplete.prepare() self.setAutoCompletionThreshold(3) self.setAutoCompletionSource(QsciScintilla.AcsAPIs) def resetPythonPrintStyle(self, lexer): self.font = QFont() self.font.setFamily(c.getFontFamily()) self.font.setFixedPitch(True) self.font.setPointSize(self.pointSize) self.setFont(self.font) lexer.setFont(self.font) # set Lexer self.setLexer(lexer) # margins reset # Margin 0 is used for line numbers fontmetrics = QFontMetrics(self.font) self.setMarginsFont(self.font) self.setMarginWidth(0, fontmetrics.width("00000") + 5) self.setMarginLineNumbers(0, True) self.setMarginsBackgroundColor(QColor("#000000")) self.setMarginsForegroundColor(QColor("#FFFFFF")) # FoldingBox self.setFoldMarginColors(QColor('dark green'), QColor('dark green'))
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 = QtWidgets.QShortcut(QtGui.QKeySequence("Ctrl+Space"), self) self.shortcut_ctrl_space.activated.connect(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.textChanged.connect(self.onTextChanged ) def onTextChanged(self): self.isModified = True self.parent.onChildContentChanged() def loadFile(self, fileName): try: self.clear() with open(fileName, 'r') as f: for line in f.readlines(): self.append(line) return True except: QtWidgets.QMessageBox.warning(self, PROJECT_ALIAS, "failed to read %s." % fileName ) return False def saveFile(self, fileName): if fileName.find(' ')>=0: QtWidgets.QMessageBox.warning(self, PROJECT_ALIAS, 'File path "%s" contains space(s). Please save to a valid location.'%fileName) return None try: with open(fileName, 'wt') as f: f.write(self.text()) except: QtWidgets.QMessageBox.warning(self, PROJECT_ALIAS, "Failed to save %s." % fileName ) return None self.curFile = fileName self.isUntitled = False self.isModified = False return fileName def saveAs(self): fileName, _ = QtWidgets.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 QtWidgets.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()