class Completer(QGraphicsProxyWidget, object): ''' Class for handling text autocompletion in the SDL scene ''' def __init__(self, parent): ''' Create an autocompletion list popup ''' widget = QListWidget() super(Completer, self).__init__(parent) self.setWidget(widget) self.string_list = QStringListModel() self._completer = QCompleter() self.parent = parent self._completer.setCaseSensitivity(Qt.CaseInsensitive) # For some reason the default minimum size is (61,61) # Set it to 0 so that the size of the box is not taken # into account when it is hidden. self.setMinimumSize(0, 0) self.prepareGeometryChange() self.resize(0, 0) self.hide() def set_completer_list(self): ''' Set list of items for the autocompleter popup ''' compl = [item.replace('-', '_') for item in self.parent.parentItem().completion_list] self.string_list.setStringList(compl) self._completer.setModel(self.string_list) def set_completion_prefix(self, completion_prefix): ''' Set the current completion prefix (user-entered text) and set the corresponding list of words in the popup widget ''' self._completer.setCompletionPrefix(completion_prefix) self.widget().clear() count = self._completer.completionCount() for i in xrange(count): self._completer.setCurrentRow(i) self.widget().addItem(self._completer.currentCompletion()) self.prepareGeometryChange() if count: self.resize(self.widget().sizeHintForColumn(0) + 40, 70) else: self.resize(0, 0) return count # pylint: disable=C0103 def keyPressEvent(self, e): super(Completer, self).keyPressEvent(e) if e.key() == Qt.Key_Escape: self.parentItem().setFocus() # Consume the event so that it is not repeated at EditableText level e.accept() # pylint: disable=C0103 def focusOutEvent(self, event): ''' When the user leaves the popup, return focus to parent ''' super(Completer, self).focusOutEvent(event) self.hide() self.resize(0, 0) self.parentItem().setFocus()
class LineEditWithComplete(QLineEdit): def __init__(self, parent, inputList, configPath='./config/LineEditWithHistory.ini', qssFilePath="./Resources/stylesheet/style.qss"): super(LineEditWithComplete, self).__init__(parent) self.qssFilePath = qssFilePath self.configPath = configPath # 用于存放历史记录的List self.inputList = inputList self.__widgetInit() def Exit(self): pass def __widgetInit(self): # LineEdit设置QCompleter,用于显示历史记录 self.completer = QCompleter(self) self.listModel = QStringListModel(self.inputList, self) self.completer.setCaseSensitivity(Qt.CaseInsensitive) self.completer.setModel(self.listModel) self.completer.activated.connect(self.Slot_completer_activated, type=Qt.QueuedConnection) # try: # with open(self.qssFilePath, "r") as fh: # self.completer.popup().setStyleSheet(fh.read()) # except Exception as e: # logger.info('读取QSS文件失败' + str(e)) self.setCompleter(self.completer) # 按下回车或单击后恢复显示模式 https://doc.qt.io/qt-5/qcompleter.html#activated def Slot_completer_activated(self, text): self.completer.setCompletionMode(QCompleter.PopupCompletion) def event(self, event): # 按下Tab键时弹出所有记录 if event.type() == QEvent.KeyPress: if event.key() == Qt.Key_Tab: # 设置不过滤显示 https://doc.qt.io/qt-5/qcompleter.html#completionMode-prop if self.completer.completionCount() > 0: self.completer.setCompletionMode( QCompleter.UnfilteredPopupCompletion) self.listModel.setStringList(self.inputList) self.completer.complete() self.completer.popup().show() return True elif event.key() == Qt.Key_Delete: self.deleteEle(self.text()) return super().event(event)
class TextStatusEditComplete(TextStatusEdit): """ Adds Completion functions to the base class This class extends 'TextStatusEdit' by: 1. providing a QCompleter to validate lines for the 'fixupText' and 'lineChanged' signals 2. providing a popup for suggested completions as the user is typing 3. auto-completing the line when the user selects a suggestion. The task of auto completion and providing suggestions is provided directly by this class. The task validating and cleaning up text is provided by the PluginFinder. """ def __init__(self, parent: QWidget = None): super().__init__(parent) self._dataModel = None self._monitorDbChanges = False self._enableAutoCompletion = False self._completedAndSelected = False self._completer = QCompleter(self) self._completer.setWidget(self) self._completer.setWrapAround(False) self._completer.setCompletionMode(QCompleter.PopupCompletion) self._completer.setCaseSensitivity(Qt.CaseInsensitive) self._completer.setFilterMode(Qt.MatchStartsWith) self._completer.setModelSorting( QCompleter.CaseInsensitivelySortedModel) self._completer.activated.connect(self.replaceLine) self._pluginFinder = PluginFinder(self._completer, self) self.fixupText.connect(self._pluginFinder.fixupText) self.lineChanged.connect(self._pluginFinder.setRowForLine) QShortcut(Qt.CTRL + Qt.Key_E, self, self.toggleAutoCompletion) QShortcut(Qt.CTRL + Qt.Key_T, self, self.suggestCompletions) # --- Methods related to the completer's underlying data model def setModel(self, model: QAbstractItemModel): self._completer.setModel(model) def _updateModelSignals(self): """ We do not need to check for column changes due to the way our PluginModel is structured. """ if self._dataModel is not None: self._dataModel.rowsMoved.disconnect(self.resetData) self._dataModel.rowsInserted.disconnect(self.resetData) self._dataModel.rowsRemoved.disconnect(self.resetData) self._dataModel.modelReset.disconnect(self.resetData) self._dataModel.dataChanged.disconnect(self.resetData) self._dataModel.layoutChanged.disconnect(self.resetData) if self._monitorDbChanges: self._dataModel = self._completer.model() if self._dataModel is not None: self._dataModel.rowsMoved.connect(self.resetData) self._dataModel.rowsInserted.connect(self.resetData) self._dataModel.rowsRemoved.connect(self.resetData) self._dataModel.modelReset.connect(self.resetData) self._dataModel.dataChanged.connect(self.resetData) self._dataModel.layoutChanged.connect(self.resetData) else: self._dataModel = None def monitorDbChanges(self, enable: bool): """ Enable invalidating line status when the data model changes. Depending on the underlying data model, it may be unnecessary to monitor these changes, or, a higher level class can monitor specific signals more efficiently. So, this is not enabled by default. """ if self._monitorDbChanges == enable: return self._monitorDbChanges = enable if enable: self._dataModel = self._completer.model() self._completer.completionModel().sourceModelChanged.connect( self._updateModelSignals) else: self._completer.completionModel().sourceModelChanged.disconnect( self._updateModelSignals) self._updateModelSignals() # ---- Methods related to line completion def completer(self): return self._completer def enableAutoCompletion(self, enable: bool): self._enableAutoCompletion = enable def toggleAutoCompletion(self): self.enableAutoCompletion(not self._enableAutoCompletion) def _textUnderCursor(self): tc = self.textCursor() if tc.positionInBlock() == 0 and len(tc.block().text()) > 1: tc.movePosition(QTextCursor.NextCharacter) tc.movePosition(QTextCursor.StartOfLine, QTextCursor.KeepAnchor) return tc.selectedText().lstrip() def suggestCompletions(self): if self.isLineInvalid(self.textCursor().blockNumber()): self._suggestCompletionsForText(self._textUnderCursor()) def _suggestCompletionsForText(self, prefix: str): if not prefix: return if prefix != self._completer.completionPrefix(): self._completer.setCompletionPrefix(prefix) self._completer.popup().setCurrentIndex( self._completer.completionModel().index(0, 0)) if self._completer.completionCount() == 1: self._insertSuggestion(self._completer.currentCompletion()) else: rect = self.cursorRect() rect.moveRight(self.statusAreaWidth()) rect.setWidth( self._completer.popup().sizeHintForColumn( self._completer.completionColumn()) + self._completer.popup().verticalScrollBar().sizeHint().width()) self._completer.complete(rect) def _insertSuggestion(self, text: str): """ Only one suggestion matched, prefill line """ cursor = self.textCursor() # handle when cursor is in middle of line if not cursor.atBlockEnd(): cursor.beginEditBlock() cursor.select(QTextCursor.LineUnderCursor) cursor.removeSelectedText() cursor.insertText(text) cursor.movePosition(QTextCursor.StartOfLine) cursor.movePosition(QTextCursor.EndOfLine, QTextCursor.KeepAnchor) self._completedAndSelected = True self.setTextCursor(cursor) cursor.endEditBlock() return # handle when cursor at end of line cursor.beginEditBlock() numCharsToComplete = len(text) - len( self._completer.completionPrefix()) insertionPosition = cursor.position() cursor.movePosition(QTextCursor.EndOfLine, QTextCursor.KeepAnchor) cursor.removeSelectedText() cursor.insertText(text[-numCharsToComplete:]) cursor.setPosition(insertionPosition) cursor.movePosition(QTextCursor.EndOfLine, QTextCursor.KeepAnchor) self._completedAndSelected = True self.setTextCursor(cursor) cursor.endEditBlock() def keyPressEvent(self, event: QKeyEvent): if self._completedAndSelected and self.handledCompletedAndSelected( event): return self._completedAndSelected = False if self._completer.popup().isVisible(): ignoredKeys = [ Qt.Key_Up, Qt.Key_Down, Qt.Key_Enter, Qt.Key_Return, Qt.Key_Tab, Qt.Key_Escape, ] if event.key() in ignoredKeys: event.ignore() return self._completer.popup().hide() super().keyPressEvent(event) if not self._enableAutoCompletion: return ctrlOrShift = (event.modifiers() & Qt.ShiftModifier == Qt.ShiftModifier or event.modifiers() & Qt.ControlModifier == Qt.ControlModifier) if ctrlOrShift and not event.text(): return if self.textCursor().atBlockEnd(): self.suggestCompletions() def mousePressEvent(self, event: QMouseEvent): if self._completedAndSelected: self._completedAndSelected = False self.document().undo() super().mousePressEvent(event) def handledCompletedAndSelected(self, event: QKeyEvent): """ The line is prefilled when only one completion matches. The user can accept the suggestion by pressing 'Enter'. The user can reject the suggestion by pressing 'Esc' or by continuing to type. """ self._completedAndSelected = False cursor = self.textCursor() acceptKeys = [Qt.Key_Enter, Qt.Key_Return, Qt.Key_Tab] if event.key() in acceptKeys: self.replaceLine(self._completer.currentCompletion()) elif event.key() == Qt.Key_Escape: self.document().undo() else: self.document().undo() return False self.setTextCursor(cursor) event.accept() return True def replaceLine(self, text: str): cursor = self.textCursor() cursor.beginEditBlock() cursor.select(QTextCursor.LineUnderCursor) cursor.removeSelectedText() cursor.insertText(text) cursor.movePosition(QTextCursor.EndOfLine) self.setTextCursor(cursor) cursor.endEditBlock() # ---- Methods related to Context Menu def createStandardContextMenu(self, pos: QPoint): menu = super().createStandardContextMenu(pos) menu.addSeparator() autoCompletionAction = menu.addAction( QIcon(), self.tr("Enable Auto Complete"), self.toggleAutoCompletion, QKeySequence(Qt.CTRL + Qt.Key_E), ) autoCompletionAction.setCheckable(True) autoCompletionAction.setChecked(self._enableAutoCompletion) completionAction = menu.addAction( QIcon(), self.tr("Suggest Completions"), self.suggestCompletions, QKeySequence(Qt.CTRL + Qt.Key_T), ) completionAction.setEnabled( self.isLineInvalid(self.textCursor().blockNumber())) return menu
class LineEditWithHistory(QLineEdit): elementSelected = pyqtSignal(str) nowTime = 0 oldTime = 0 def __init__(self, parent, title='历史记录', configPath='', qssFilePath="./Resources/stylesheet/style.qss"): super(LineEditWithHistory, self).__init__(parent) self.qssFilePath = qssFilePath if "" == configPath: self.configPath = "./config/{parentName}/LineEditWithHistory.ini".format( parentName=type(parent).__name__) else: if 0 <= os.path.basename(configPath).find("."): self.configPath = configPath else: self.configPath = "{basePath}/LineEditWithHistory.ini".format( basePath=configPath) self.title = title self.sectionName = '{title}'.format(title=self.title) self.HistoryListChanged = False self.commandHasSent = False # 用于存放历史记录的List self.inputList = [] self.__loadHistory() self.__widgetInit() def Exit(self): self.__saveHistory() def __widgetInit(self): # LineEdit设置QCompleter,用于显示历史记录 self.completer = QCompleter(self) self.listModel = QStringListModel(self.inputList, self) self.completer.setCaseSensitivity(Qt.CaseInsensitive) self.completer.setModel(self.listModel) self.completer.activated.connect(self.Slot_completer_activated, type=Qt.QueuedConnection) try: with open(self.qssFilePath, "r") as fh: self.completer.popup().setStyleSheet(fh.read()) except Exception as e: logger.info('读取QSS文件失败' + str(e)) self.setCompleter(self.completer) # 输入完成按下回车后去重添加到历史记录中 self.returnPressed.connect(self.Slot_updateHistoryModule) def __loadHistory(self): historyList = self.inputList historyOp = Public.Public_ConfigOp(self.configPath) rt = historyOp.ReadAllBySection(self.sectionName) if True is rt[0]: for item in rt[1]: if (item[1] not in historyList) and ("" != item[1]): historyList.append(item[1]) else: logger.info(rt[1]) def __saveHistory(self): ipOp = Public.Public_ConfigOp(self.configPath) if True is self.HistoryListChanged: ipOp.RemoveSection(self.sectionName) ipOp.SaveAll() for index, item in enumerate(self.inputList): ipOp.SaveConfig(self.sectionName, str(index), item) def updateHistory(self): content = self.text() if content != "": if content not in self.inputList: self.inputList.append(content) self.listModel.setStringList(self.inputList) self.completer.setCompletionMode(QCompleter.PopupCompletion) self.HistoryListChanged = True self.__sendElement() def Slot_updateHistoryModule(self): self.updateHistory() # 按下回车或单击后恢复显示模式 https://doc.qt.io/qt-5/qcompleter.html#activated def Slot_completer_activated(self, text): self.completer.setCompletionMode(QCompleter.PopupCompletion) self.__sendElement() def __sendElement(self): self.elementSelected.emit(self.text()) def deleteEle(self, ele): if ele in self.inputList: self.inputList.remove(ele) self.listModel.setStringList(self.inputList) self.completer.setCompletionMode(QCompleter.PopupCompletion) self.HistoryListChanged = True def event(self, event): # 按下Tab键时弹出所有记录 if event.type() == QEvent.KeyPress: if event.key() == Qt.Key_Tab: # 设置不过滤显示 https://doc.qt.io/qt-5/qcompleter.html#completionMode-prop if self.completer.completionCount() > 0: self.completer.setCompletionMode( QCompleter.UnfilteredPopupCompletion) self.listModel.setStringList(self.inputList) self.completer.complete() self.completer.popup().show() return True elif event.key() == Qt.Key_Delete: self.deleteEle(self.text()) return super().event(event)