class AbstractReferenceSelector(ABC, QWidget, metaclass=SelectorMeta): changed = Signal() def __init__(self, parent=None): QWidget.__init__(self, parent) self.completer = None self.p_selected_id = 0 self.layout = QHBoxLayout() self.layout.setMargin(0) self.name = QLineEdit() self.name.setText("") self.layout.addWidget(self.name) self.details = QLabel() self.details.setText("") self.details.setVisible(False) self.layout.addWidget(self.details) self.button = QPushButton("...") self.button.setFixedWidth(self.button.fontMetrics().width("XXXX")) self.layout.addWidget(self.button) self.setLayout(self.layout) self.setFocusProxy(self.name) self.button.clicked.connect(self.on_button_clicked) if self.details_field: self.name.setFixedWidth(self.name.fontMetrics().width("X") * 15) self.details.setVisible(True) self.completer = QCompleter(self.dialog.model.completion_model) self.completer.setCompletionColumn( self.dialog.model.completion_model.fieldIndex(self.selector_field)) self.completer.setCaseSensitivity(Qt.CaseInsensitive) self.name.setCompleter(self.completer) self.completer.activated[QModelIndex].connect(self.on_completion) def getId(self): return self.p_selected_id def setId(self, selected_id): if self.p_selected_id == selected_id: return self.p_selected_id = selected_id self.name.setText( self.dialog.model.getFieldValue(selected_id, self.selector_field)) if self.details_field: self.details.setText( self.dialog.model.getFieldValue(selected_id, self.details_field)) selected_id = Property(int, getId, setId, notify=changed, user=True) def on_button_clicked(self): ref_point = self.mapToGlobal(self.name.geometry().bottomLeft()) self.dialog.setGeometry(ref_point.x(), ref_point.y(), self.dialog.width(), self.dialog.height()) res = self.dialog.exec_(enable_selection=True, selected=self.selected_id) if res: self.selected_id = self.dialog.selected_id self.changed.emit() @Slot(QModelIndex) def on_completion(self, index): model = index.model() self.selected_id = model.data(model.index(index.row(), 0), Qt.DisplayRole) self.changed.emit() def isCustom(self): return True
class Editor(QPlainTextEdit): keyPressed = Signal(QEvent) class _NumberArea(QWidget): def __init__(self, editor): super().__init__(editor) self.codeEditor = editor def sizeHint(self): return QSize(self.editor.lineNumberAreaWidth(), 0) def paintEvent(self, event): self.codeEditor.lineNumberAreaPaintEvent(event) def __init__(self, **kwargs): parent = kwargs["parent"] if "parent" in kwargs else None super().__init__(parent) self._addSaveAction = kwargs[ "saveAction"] if "saveAction" in kwargs else False self._addSaveAction = kwargs[ "saveAction"] if "saveAction" in kwargs else False if self._addSaveAction: self.saveACT = QAction("Save") self.saveACT.setShortcut(QKeySequence("Ctrl+S")) self.saveACT.triggered.connect( lambda: self._save_file(self.toPlainText())) self.addAction(self.saveACT) self._saveCB = kwargs[ "saveFunction"] if "saveFunction" in kwargs else None self.setFont(QFont("Courier New", 11)) self.lineNumberArea = Editor._NumberArea(self) self.blockCountChanged.connect(self.updateLineNumberAreaWidth) self.updateRequest.connect(self.updateLineNumberArea) self.cursorPositionChanged.connect(self.highlightCurrentLine) self.updateLineNumberAreaWidth(0) self.findHighlightFormat = QTextCharFormat() self.findHighlightFormat.setBackground(QBrush(QColor("red"))) self.searchTxtBx = None def lineNumberAreaWidth(self): digits = 5 max_value = max(1, self.blockCount()) while max_value >= 10: max_value /= 10 digits += 1 space = 3 + self.fontMetrics().width('9') * digits return space def updateLineNumberAreaWidth(self, _): self.setViewportMargins(self.lineNumberAreaWidth(), 0, 0, 0) def updateLineNumberArea(self, rect, dy): if dy: self.lineNumberArea.scroll(0, dy) else: self.lineNumberArea.update(0, rect.y(), self.lineNumberArea.width(), rect.height()) if rect.contains(self.viewport().rect()): self.updateLineNumberAreaWidth(0) def resizeEvent(self, event): super().resizeEvent(event) cr = self.contentsRect() self.lineNumberArea.setGeometry( QRect(cr.left(), cr.top(), self.lineNumberAreaWidth(), cr.height())) def highlightCurrentLine(self): extraSelections = [] if not self.isReadOnly(): selection = QTextEdit.ExtraSelection() lineColor = QColor(229, 248, 255, 255) selection.format.setBackground(lineColor) selection.format.setProperty(QTextFormat.FullWidthSelection, True) selection.cursor = self.textCursor() selection.cursor.clearSelection() extraSelections.append(selection) self.setExtraSelections(extraSelections) def lineNumberAreaPaintEvent(self, event): painter = QPainter(self.lineNumberArea) painter.fillRect(event.rect(), QColor(233, 233, 233, 255)) block = self.firstVisibleBlock() blockNumber = block.blockNumber() top = self.blockBoundingGeometry(block).translated( self.contentOffset()).top() bottom = top + self.blockBoundingRect(block).height() # Just to make sure I use the right font height = self.fontMetrics().height() while block.isValid() and (top <= event.rect().bottom()): if block.isVisible() and (bottom >= event.rect().top()): number = str(blockNumber + 1) fFont = QFont("Courier New", 10) painter.setPen(QColor(130, 130, 130, 255)) painter.setFont(fFont) painter.drawText(0, top, self.lineNumberArea.width(), height, Qt.AlignCenter, number) block = block.next() top = bottom bottom = top + self.blockBoundingRect(block).height() blockNumber += 1 def contextMenuEvent(self, event): menu = self.createStandardContextMenu() if self._addSaveAction: index = 0 if len(menu.actions()) > 6: index = 5 act_beforeACT = menu.actions()[index] menu.insertAction(act_beforeACT, self.saveACT) action = menu.addAction("Find" + "\t" + "Ctrl+F") action.triggered.connect(self.find_key) menu.popup(event.globalPos()) def keyPressEvent(self, event: QKeyEvent): if event.key() == Qt.Key_F and (event.modifiers() & Qt.ControlModifier): self.find_key() if event.key() == Qt.Key_Escape: if self.searchTxtBx is not None: self.searchTxtBx.hide() self.searchTxtBx = None self.clear_format() super(Editor, self).keyPressEvent(event) def find_key(self): if self.searchTxtBx is None: self.searchTxtBx = QLineEdit(self) p = self.geometry().topRight() - self.searchTxtBx.geometry( ).topRight() - QPoint(50, 0) self.searchTxtBx.move(p) self.searchTxtBx.show() self.searchTxtBx.textChanged.connect(self.find_with_pattern) self.searchTxtBx.setFocus() def find_with_pattern(self, pattern): self.setUndoRedoEnabled(False) self.clear_format() if pattern == "": return cursor = self.textCursor() regex = QRegExp(pattern) pos = 0 index = regex.indexIn(self.toPlainText(), pos) while index != -1: cursor.setPosition(index) cursor.movePosition(QTextCursor.EndOfWord, QTextCursor.KeepAnchor, 1) cursor.mergeCharFormat(self.findHighlightFormat) pos = index + regex.matchedLength() index = regex.indexIn(self.toPlainText(), pos) self.setUndoRedoEnabled(True) def clear_format(self): cursor = self.textCursor() cursor.select(QTextCursor.Document) cursor.setCharFormat(QTextCharFormat()) cursor.clearSelection() self.setTextCursor(cursor) def setSaveCB(self, cb): self._saveCB = cb def _save_file(self, text): if self._saveCB is not None: self._saveCB(text)