class CommandBox(QtGui.QPlainTextEdit, object): newCommand = QtCore.Signal(str) def reset_history(self): self.history_index = len(self.history) def __init__(self, history, commands, prefix): self.prefix = prefix self.history_index = 0 self.history = history self.reset_history() super(CommandBox, self).__init__() # Autocompleter self.completer = QCompleter([prefix + name for name in commands], self) self.completer.setCaseSensitivity(Qt.CaseInsensitive) self.completer.setWidget(self) self.completer.activated.connect(self.onAutoComplete) self.autocompleteStart = None def onAutoComplete(self, text): # Select the text from autocompleteStart until the current cursor cursor = self.textCursor() cursor.setPosition(0, cursor.KeepAnchor) # Replace it with the selected text cursor.insertText(text) self.autocompleteStart = None # noinspection PyStringFormat def keyPressEvent(self, *args, **kwargs): event = args[0] key = event.key() ctrl = event.modifiers() == QtCore.Qt.ControlModifier # don't disturb the completer behavior if self.completer.popup().isVisible() and key in ( Qt.Key_Enter, Qt.Key_Return, Qt.Key_Tab, Qt.Key_Backtab): event.ignore() return if self.autocompleteStart is not None and not event.text().isalnum() and \ not (key == Qt.Key_Backspace and self.textCursor().position() > self.autocompleteStart): self.completer.popup().hide() self.autocompleteStart = None if key == Qt.Key_Space and ctrl: # Pop-up the autocompleteList rect = self.cursorRect(self.textCursor()) rect.setSize(QtCore.QSize(100, 150)) self.autocompleteStart = self.textCursor().position() self.completer.complete( rect) # The popup is positioned in the next if block if self.autocompleteStart: prefix = self.toPlainText() cur = self.textCursor() cur.setPosition(self.autocompleteStart) self.completer.setCompletionPrefix(prefix) # Select the first one of the matches self.completer.popup().setCurrentIndex( self.completer.completionModel().index(0, 0)) if key == Qt.Key_Up and ctrl: if self.history_index > 0: self.history_index -= 1 self.setPlainText(BOT_PREFIX + '%s %s' % self.history[self.history_index]) key.ignore() return elif key == Qt.Key_Down and ctrl: if self.history_index < len(self.history) - 1: self.history_index += 1 self.setPlainText(BOT_PREFIX + '%s %s' % self.history[self.history_index]) key.ignore() return elif key == QtCore.Qt.Key_Return and ctrl: self.newCommand.emit(self.toPlainText()) self.reset_history() super(CommandBox, self).keyPressEvent(*args, **kwargs)
class TextEditor(RWWidget, Ui_Form): """ Contains many features of popular text editors adapted for use with Ontologies such as syntax highlighting, and autocompletion. One column on the left of the text editor contains line numbers and another contains other contextual information such as whether a block of code has been hidden/collapsed and can be displayed/expanded later. It also contains an incremental search and an interface to pySUMO's settings so font size and family can be changed at will. Variables: - syntax_highlighter: The syntax highlighter object for the text editor. Methods: - __init__: Initalizes the Object and the QPlainTextEdit - commit: Notifies other Widgets of changes. - show_autocomplete: Returns autocompletion choices. - getWidget: returns the QPlainTextEdit - numberbarPaint: Paints the numberbar - searchCompletion: Asks QCompleter if a whole word exists starting with user input - hideFrom: Starts hides all lines from the ()-block started by line - insertCompletion: Puts the selected Completion into the TextEditor """ def __init__(self, mainwindow, settings=None): """ Initializes the text editor widget. """ super(TextEditor, self).__init__(mainwindow) self.setupUi(self.mw) self.plainTextEdit.clear() self.plainTextEdit.setEnabled(False) self.plainTextEdit.show() self.highlighter = SyntaxHighlighter(self.plainTextEdit.document(), settings) self.initAutocomplete() self._initNumberBar() self.hidden = {} self.printer = QPrinter(QPrinterInfo.defaultPrinter()) self.plainTextEdit.setTextCursor( self.plainTextEdit.cursorForPosition(QPoint(0, 0))) self.canUndo = False self.canRedo = False self.ontologySelector.setCurrentIndex(-1) self.timer = QTimer(self) self.timer.setSingleShot(True) self.timer.timeout.connect(self.commit) #Connects self.getWidget().textChanged.connect(self.searchCompletion) self.plainTextEdit.undoAvailable.connect(self.setCanUndo) self.plainTextEdit.redoAvailable.connect(self.setCanRedo) self.ontologySelector.currentIndexChanged[int].connect( self.showOtherOntology) self.plainTextEdit.textChanged.connect(self.expandIfBracketRemoved) self.plainTextEdit.textChanged.connect(self.setTextChanged) self._updateOntologySelector() #must be after connects @Slot() def setTextChanged(self): """Is called if the text changed signal is thrown and sets a timer of 3 seconds to reparse the ontology. """ self.timer.stop() self.timer.start(3000) def setCanUndo(self, b): self.canUndo = b def setCanRedo(self, b): self.canRedo = b def _print_(self): """ Creates a print dialog with the latest text""" dialog = QPrintDialog() if dialog.exec_() == QDialog.Accepted: doc = self.plainTextEdit.document() doc.print_(dialog.printer()) def _quickPrint_(self): """ No dialog, just print""" if self.printer is None: return doc = self.plainTextEdit.document() doc.print_(self.printer) def _printPreview_(self): """ Create a print preview""" dialog = QPrintPreviewDialog() dialog.paintRequested.connect(self.plainTextEdit.print_) dialog.exec_() def saveOntology(self): """ Save the ontology to disk""" idx = self.ontologySelector.currentIndex() ontology = self.ontologySelector.itemData(idx) if ontology is None: return if type(ontology) is Ontology: ontology.save() def getActiveOntology(self): idx = self.ontologySelector.currentIndex() return self.ontologySelector.itemData(idx) def undo(self): if self.canUndo: self.plainTextEdit.undo() try: self.SyntaxController.add_ontology( self.getActiveOntology(), self.plainTextEdit.toPlainText()) except ParseError: return self.commit() else: super(TextEditor, self).undo() def redo(self): if self.canRedo: self.plainTextEdit.redo() try: self.SyntaxController.add_ontology( self.getActiveOntology(), self.plainTextEdit.toPlainText()) except ParseError: return self.commit() else: super(TextEditor, self).redo() def _initNumberBar(self): """ Init the number bar""" self.number_bar = NumberBar(self) self.number_bar.setMinimumSize(QSize(30, 0)) self.number_bar.setObjectName("number_bar") self.gridLayout.addWidget(self.number_bar, 1, 0, 1, 1) self.plainTextEdit.blockCountChanged.connect( self.number_bar.adjustWidth) self.plainTextEdit.updateRequest.connect( self.number_bar.updateContents) @Slot(int) def jumpToLocation(self, location, ontology): if ontology == str(self.getActiveOntology()): textBlock = self.plainTextEdit.document().findBlockByNumber( location) pos = textBlock.position() textCursor = self.plainTextEdit.textCursor() textCursor.setPosition(pos) self.plainTextEdit.setTextCursor(textCursor) self.plainTextEdit.centerCursor() def _updateOntologySelector(self): """ Update the ontology selector where you can select which Ontology to show in the editor""" current = self.ontologySelector.currentText() self.ontologySelector.currentIndexChanged[int].disconnect( self.showOtherOntology) self.ontologySelector.clear() index = -1 count = 0 for i in self.getIndexAbstractor().ontologies: if current == i.name: index = count self.ontologySelector.addItem(i.name, i) count = count + 1 self.ontologySelector.setCurrentIndex(index) # if index == -1 : # the ontology was removed. # self.showOtherOntology(index) if index == -1: self.plainTextEdit.setEnabled(False) self.plainTextEdit.clear() self.ontologySelector.currentIndexChanged[int].connect( self.showOtherOntology) def setActiveOntology(self, ontology): index = -1 count = 0 for i in self.getIndexAbstractor().ontologies: if ontology.name == i.name: index = count break count = count + 1 self.ontologySelector.setCurrentIndex(index) @Slot(int) def showOtherOntology(self, idx): """ Show other ontology in the plaintextedit Arguments: - idx: The id of the current Ontologyselector """ dced = False try: self.plainTextEdit.textChanged.disconnect(self.setTextChanged) except RuntimeError: dced = True idx = self.ontologySelector.currentIndex() if idx == -1: self.plainTextEdit.setEnabled(False) self.plainTextEdit.clear() return ontologyname = self.ontologySelector.currentText() for i in self.getIndexAbstractor().ontologies: if i.name == ontologyname: self.plainTextEdit.setEnabled(True) self.getWidget().setPlainText( self.getIndexAbstractor().get_ontology_file(i).getvalue()) if not dced: self.plainTextEdit.textChanged.connect(self.setTextChanged) return self.plainTextEdit.textChanged.connect(self.commit) assert False @Slot() def expandIfBracketRemoved(self): """ Check if a line with ( or ) was changed and expand the possible hidden lines """ current_line = self.getWidget().document().findBlock( self.getWidget().textCursor().position()).blockNumber() + 1 if current_line in self.hidden: self.toggleVisibility(current_line) @Slot() def zoomIn(self): """ Increase the size of the font in the TextEditor """ doc = self.getWidget().document() font = doc.defaultFont() font.setPointSize(font.pointSize() + 1) font = QFont(font) doc.setDefaultFont(font) @Slot() def zoomOut(self): """ Decrease the size of the font in the TextEditor""" doc = self.getWidget().document() font = doc.defaultFont() font.setPointSize(font.pointSize() - 1) font = QFont(font) doc.setDefaultFont(font) @Slot() def expandAll(self): """ Expands all hidden code blocks""" for see in list(self.hidden.keys()): self.toggleVisibility(see) @Slot() def collapseAll(self): """ Collapse all code blocks (where possible)""" block = self.getWidget().document().firstBlock() while block.isValid(): if block.isVisible(): if block.text().count("(") > block.text().count(")"): self.toggleVisibility(block.blockNumber() + 1) block = block.next() def _hideLines(self, lines): for line in lines: if line == 0: continue block = self.getWidget().document().findBlockByNumber(line - 1) assert block.isVisible() block.setVisible(False) assert not block.isVisible(), "Problem with line %r" % (line) def _showLines(self, lines): """ Show the lines not visible starting by lines Arguments: - lines: The first line followed by an unvisible block """ for line in lines: block = self.getWidget().document().findBlockByNumber(line - 1) block.setVisible(True) def getLayoutWidget(self): """ Returns the layout widget""" return self.widget def numberbarPaint(self, number_bar, event): """Paints the line numbers of the code file""" self.number_bar.link = [] font_metrics = self.getWidget().fontMetrics() current_line = self.getWidget().document().findBlock( self.getWidget().textCursor().position()).blockNumber() + 1 block = self.getWidget().firstVisibleBlock() line_count = block.blockNumber() painter = QPainter(self.number_bar) # TODO: second argument is color -> to settings painter.fillRect(self.number_bar.rect(), self.getWidget().palette().base()) # Iterate over all visible text blocks in the document. while block.isValid(): line_count += 1 text = str(line_count) block_top = self.getWidget().blockBoundingGeometry( block).translated(self.getWidget().contentOffset()).top() if not block.isVisible(): block = block.next() while not block.isVisible(): line_count += 1 block = block.next() continue self.number_bar.link.append((block_top, line_count)) # Check if the position of the block is out side of the visible # area. if block_top >= event.rect().bottom(): break # We want the line number for the selected line to be bold. if line_count == current_line: font = painter.font() font.setBold(True) else: font = painter.font() font.setBold(False) # line opens a block if line_count in self.hidden: text += "+" font.setUnderline(True) elif block.text().count("(") > block.text().count(")"): text += "-" font.setUnderline(True) else: font.setUnderline(False) painter.setFont(font) # Draw the line number right justified at the position of the # line. paint_rect = QRect(0, block_top, self.number_bar.width(), font_metrics.height()) painter.drawText(paint_rect, Qt.AlignLeft, text) block = block.next() painter.end() def initAutocomplete(self): """Inits the QCompleter and gives him a list of words""" self.completer = QCompleter( list( OrderedDict.fromkeys( re.split("\\W", self.plainTextEdit.toPlainText())))) self.completer.setCaseSensitivity(Qt.CaseInsensitive) self.completer.setWidget(self.getWidget()) self.completer.activated.connect(self.insertCompletion) def searchCompletion(self): """Searches for possible completion from QCompleter to the current text position""" tc = self.getWidget().textCursor() tc.movePosition(QTextCursor.PreviousCharacter, QTextCursor.KeepAnchor) if tc.selectedText() in string.whitespace: self.completer.popup().hide() return tc.movePosition(QTextCursor.StartOfWord, QTextCursor.KeepAnchor) beginning = tc.selectedText() if len(beginning) >= 3: self.completer.setCompletionPrefix(beginning) self.completer.complete() shortcut = QShortcut(QKeySequence("Ctrl+Enter"), self.getWidget(), self.insertCompletion) def toggleVisibility(self, line): """ Shows or hides a line """ if line in self.hidden: self._showLines(self.hidden[line]) del self.hidden[line] else: self.hideFrom(line) # update views self.getWidget().hide() self.getWidget().show() self.number_bar.update() def hideFrom(self, line): """ Hides a block starting by line. Do nothing if not hidable""" block = self.getWidget().document().findBlockByNumber(line - 1) openB = block.text().count("(") closeB = block.text().count(")") startline = line # go to line >= line: block starts counting by 0 block = self.getWidget().document().findBlockByNumber(line - 1) hidden = [] assert block.isValid() while openB > closeB and block.isValid(): assert block.isValid() block = block.next() line = block.blockNumber() + 1 if block.isVisible(): hidden.append(line) openB += block.text().count("(") closeB += block.text().count(")") if hidden == []: return self._hideLines(hidden) self.hidden[startline] = hidden # set current line in viewable area current_line = self.getWidget().document().findBlock( self.getWidget().textCursor().position()).blockNumber() + 1 if (startline < current_line and current_line <= line): block = block.next() cursor = QTextCursor(block) self.getWidget().setTextCursor(cursor) @Slot(str) def insertCompletion(self, completion): """ Adds the completion to current text""" tc = self.getWidget().textCursor() tc.movePosition(QTextCursor.StartOfWord, QTextCursor.KeepAnchor) tc.removeSelectedText() tc.insertText(completion) def getWidget(self): """ Return the QPlainTextEdit Widget""" return self.plainTextEdit @Slot() def commit(self): """ Overrides commit from RWWidget. """ idx = self.ontologySelector.currentIndex() if idx == -1: return ontology = self.ontologySelector.itemData(idx) if ontology is None: return try: QApplication.setOverrideCursor(Qt.BusyCursor) self.SyntaxController.add_ontology( ontology, self.plainTextEdit.toPlainText()) QApplication.setOverrideCursor(Qt.ArrowCursor) except ParseError: return super(TextEditor, self).commit() @Slot() def refresh(self): """ Refreshes the content of the TextEditor (syncing with other widgets)""" textCursorPos = self.plainTextEdit.textCursor().position() super(TextEditor, self).refresh() dced = False try: self.plainTextEdit.textChanged.disconnect(self.setTextChanged) except RuntimeError: dced = True idx = self.ontologySelector.currentIndex() ontology = self.ontologySelector.itemData(idx) if ontology in self.IA.ontologies: f = self.IA.get_ontology_file(ontology) self.plainTextEdit.setPlainText(f.getvalue()) if not dced: self.plainTextEdit.textChanged.connect(self.setTextChanged) cursor = self.plainTextEdit.textCursor() cursor.setPosition(textCursorPos) self.plainTextEdit.setTextCursor(cursor) self.plainTextEdit.centerCursor()
class CommandBox(QtGui.QPlainTextEdit, object): newCommand = QtCore.Signal(str) def reset_history(self): self.history_index = len(self.history) def __init__(self, history, commands): self.history_index = 0 self.history = history self.reset_history() super(CommandBox, self).__init__() #Autocompleter self.completer = QCompleter([BOT_PREFIX + name for name in commands], self) self.completer.setCaseSensitivity(Qt.CaseInsensitive) self.completer.setWidget(self) self.completer.activated.connect(self.onAutoComplete) self.autocompleteStart = None def onAutoComplete(self, text): #Select the text from autocompleteStart until the current cursor cursor = self.textCursor() cursor.setPosition(0, cursor.KeepAnchor) #Replace it with the selected text cursor.insertText(text) self.autocompleteStart = None #noinspection PyStringFormat def keyPressEvent(self, *args, **kwargs): event = args[0] key = event.key() ctrl = event.modifiers() == QtCore.Qt.ControlModifier # don't disturb the completer behavior if self.completer.popup().isVisible() and key in (Qt.Key_Enter, Qt.Key_Return, Qt.Key_Tab, Qt.Key_Backtab): event.ignore() return if self.autocompleteStart is not None and not event.text().isalnum() and \ not (key == Qt.Key_Backspace and self.textCursor().position() > self.autocompleteStart): self.completer.popup().hide() self.autocompleteStart = None if key == Qt.Key_Space and ctrl: #Pop-up the autocompleteList rect = self.cursorRect(self.textCursor()) rect.setSize(QtCore.QSize(100, 150)) self.autocompleteStart = self.textCursor().position() self.completer.complete(rect) # The popup is positioned in the next if block if self.autocompleteStart: prefix = self.toPlainText() cur = self.textCursor() cur.setPosition(self.autocompleteStart) self.completer.setCompletionPrefix(prefix) #Select the first one of the matches self.completer.popup().setCurrentIndex(self.completer.completionModel().index(0, 0)) if key == Qt.Key_Up and ctrl: if self.history_index > 0: self.history_index -= 1 self.setPlainText(BOT_PREFIX + '%s %s' % self.history[self.history_index]) key.ignore() return elif key == Qt.Key_Down and ctrl: if self.history_index < len(self.history) - 1: self.history_index += 1 self.setPlainText(BOT_PREFIX + '%s %s' % self.history[self.history_index]) key.ignore() return elif key == QtCore.Qt.Key_Return and ctrl: self.newCommand.emit(self.toPlainText()) self.reset_history() super(CommandBox, self).keyPressEvent(*args, **kwargs)
class EditorCodeCompletion(QTextEdit): def __init__(self, path_dict): super(EditorCodeCompletion, self).__init__() self.m_completer = QCompleter(self) self.m_completer.setWidget(self) words = [] self.flag_open_angle_bracket = False self.tag_name = "" try: f = open(path_dict,"r") for word in f: words.append(word.strip()) f.close() except IOError: print ("dictionary not in anticipated location") model = QStringListModel(words, self.m_completer) self.m_completer.setModel(model) self.m_completer.setCompletionMode(QCompleter.PopupCompletion) self.m_completer.activated.connect(self.insertCompletion) def insertCompletion (self, completion): cursor = self.textCursor() cursor.beginEditBlock() cursor.movePosition(QTextCursor.Left) cursor.movePosition(QTextCursor.EndOfWord) extra = len(self.m_completer.completionPrefix()) cursor.insertText(completion[extra:]) self.setTextCursor(cursor) cursor.endEditBlock() def __insertTag(self): ''' inserts the corresponding closing tag to an opening xml tag ''' self.find('<', QTextDocument.FindBackward) tc = self.textCursor() tc.select(QTextCursor.WordUnderCursor) txt = '' if self.__stringHasBracket(tc.selectedText().replace(' ', '')) else tc.selectedText() txt = '</' + txt + '>' self.find('>') tc = self.textCursor() tc.clearSelection() tc.insertText(txt) tc.movePosition(QTextCursor.Left, QTextCursor.MoveAnchor, len(txt)) self.setTextCursor(tc) def __stringHasBracket(self, s): return '<' in s or '>' in s def __insertClosingTag(self, event): operation_sys = sys.platform flag = "linux" in operation_sys if flag : self.__insertClosingTag_Unix(event) else: self.__insertClosingTag_Win(event) def __insertClosingTag_Unix(self, event): ''' inserts a closing tag after the closing bracket of open tag @param key: keyboard input value as int ''' if self.flag_open_angle_bracket : if event.key() == 47 : # / print ("/") self.flag_open_angle_bracket = False elif event.key() == 62 : # > print (">") self.__insertTag() self.flag_open_angle_bracket = False elif event.key() == 60 : # < print ("<") self.flag_open_angle_bracket = True def __insertClosingTag_Win(self, event) : if self.flag_open_angle_bracket : if event.modifiers() & Qt.ShiftModifier : if event.key() == 55 : # / print ("/") self.flag_open_angle_bracket = False elif event.key() == 60 : # > print (">") self.__insertTag() self.flag_open_angle_bracket = False elif event.key() == 60 : # < print ("<") self.flag_open_angle_bracket = True def keyPressEvent(self, event): ''' checks keyboard input to set closing tag or start code completion ''' if self.m_completer.popup().isVisible() : if event.key() == Qt.Key_Enter or event.key() == Qt.Key_Return or event.key() == Qt.Key_Tab or event.key() == Qt.Key_Escape : event.ignore() return # open popup isShortcut = (event.modifiers() == Qt.ControlModifier and event.key() == Qt.Key_Space) if not isShortcut: super(EditorCodeCompletion, self).keyPressEvent(event) # ============================================================================================= # begin tag inline insertion self.__insertClosingTag(event) # end tag inline insertion # ============================================================================================= cursor = self.textCursor() cursor.select(QTextCursor.WordUnderCursor) completionPrefix = cursor.selectedText() if completionPrefix != self.m_completer.completionPrefix() : self.m_completer.setCompletionPrefix(completionPrefix) self.m_completer.popup().setCurrentIndex(self.m_completer.completionModel().index(0, 0)) # if not event.text() != "" and len(completionPrefix) > 2 : if len(completionPrefix) > 2 and isShortcut : print ("hier") cr = self.cursorRect() cr.setWidth(2 * (self.m_completer.popup().sizeHintForColumn(0) + self.m_completer.popup().verticalScrollBar().sizeHint().width())) self.m_completer.complete(cr) # # popup it up!