class SQLiteCompleterText(QTextEdit): def __init__(self, parent=None): super(SQLiteCompleterText, self).__init__(parent) self.cp = QCompleter(words) self.cp.setWrapAround(False) self.cp.setWidget(self) self.cp.setCompletionMode(QCompleter.PopupCompletion) self.cp.activated.connect(self.insertCompletion) def insertCompletion(self, completion): tc = self.textCursor() extra = len(completion) - len(self.cp.completionPrefix()) tc.movePosition(QTextCursor.Left) tc.movePosition(QTextCursor.EndOfWord) tc.insertText(completion[-extra:]) self.setTextCursor(tc) def textUnderCursor(self): tc = self.textCursor() tc.select(QTextCursor.WordUnderCursor) return tc.selectedText() def focusInEvent(self, e): self.cp.setWidget(self) super(SQLiteCompleterText, self).focusInEvent(e) def keyPressEvent(self, e): if self.cp.popup().isVisible(): if e.key() in (Qt.Key_Enter, Qt.Key_Return, Qt.Key_Escape, Qt.Key_Tab, Qt.Key_Backtab): e.ignore() return is_shortcut = ((e.modifiers() & Qt.ControlModifier) != 0 and e.key() == Qt.Key_E & Qt.ControlModifier) if not is_shortcut: super(SQLiteCompleterText, self).keyPressEvent(e) ctrl_or_shift = e.modifiers() & (Qt.ControlModifier | Qt.ShiftModifier) if ctrl_or_shift and len(e.text()) == 0: return eo = "~!@#$%^&*()_+{}|:\"<>?,./;'[]\\-=" has_modifier = (e.modifiers() != Qt.NoModifier) and not ctrl_or_shift cp = self.textUnderCursor() if not is_shortcut and (cp in words or has_modifier or len(e.text()) == 0 or len(cp) < 1 or e.text()[-1] in eo): self.cp.popup().hide() return if cp != self.cp.completionPrefix(): self.cp.setCompletionPrefix(cp) self.cp.popup().setCurrentIndex(self.cp.completionModel().index(0, 0)) cr = self.cursorRect() cr.setWidth(self.cp.popup().sizeHintForColumn(0) + self.cp.popup().verticalScrollBar().sizeHint().width()) self.cp.complete(cr)
class HelpWebSearchWidget(E5ClearableLineEdit): """ Class implementing a web search widget for the web browser. @signal search(QUrl) emitted when the search should be done """ search = pyqtSignal(QUrl) def __init__(self, parent=None): """ Constructor @param parent reference to the parent widget (QWidget) """ super(HelpWebSearchWidget, self).__init__(parent) from E5Gui.E5LineEdit import E5LineEdit from E5Gui.E5LineEditButton import E5LineEditButton from .OpenSearch.OpenSearchManager import OpenSearchManager self.__mw = parent self.__openSearchManager = OpenSearchManager(self) self.__openSearchManager.currentEngineChanged.connect( self.__currentEngineChanged) self.__currentEngine = "" self.__enginesMenu = QMenu(self) self.__engineButton = E5LineEditButton(self) self.__engineButton.setMenu(self.__enginesMenu) self.addWidget(self.__engineButton, E5LineEdit.LeftSide) self.__searchButton = E5LineEditButton(self) self.__searchButton.setIcon(UI.PixmapCache.getIcon("webSearch.png")) self.addWidget(self.__searchButton, E5LineEdit.LeftSide) self.__model = QStandardItemModel(self) self.__completer = QCompleter() self.__completer.setModel(self.__model) self.__completer.setCompletionMode( QCompleter.UnfilteredPopupCompletion) self.__completer.setWidget(self) self.__searchButton.clicked.connect(self.__searchButtonClicked) self.textEdited.connect(self.__textEdited) self.returnPressed.connect(self.__searchNow) self.__completer.activated[QModelIndex].connect( self.__completerActivated) self.__completer.highlighted[QModelIndex].connect( self.__completerHighlighted) self.__enginesMenu.aboutToShow.connect(self.__showEnginesMenu) self.__suggestionsItem = None self.__suggestions = [] self.__suggestTimer = None self.__suggestionsEnabled = Preferences.getHelp("WebSearchSuggestions") self.__recentSearchesItem = None self.__recentSearches = [] self.__maxSavedSearches = 10 self.__engine = None self.__loadSearches() self.__setupCompleterMenu() self.__currentEngineChanged() def __searchNow(self): """ Private slot to perform the web search. """ searchText = self.text() if not searchText: return globalSettings = QWebSettings.globalSettings() if not globalSettings.testAttribute( QWebSettings.PrivateBrowsingEnabled): if searchText in self.__recentSearches: self.__recentSearches.remove(searchText) self.__recentSearches.insert(0, searchText) if len(self.__recentSearches) > self.__maxSavedSearches: self.__recentSearches = \ self.__recentSearches[:self.__maxSavedSearches] self.__setupCompleterMenu() url = self.__openSearchManager.currentEngine().searchUrl(searchText) self.search.emit(url) def __setupCompleterMenu(self): """ Private method to create the completer menu. """ if not self.__suggestions or \ (self.__model.rowCount() > 0 and self.__model.item(0) != self.__suggestionsItem): self.__model.clear() self.__suggestionsItem = None else: self.__model.removeRows(1, self.__model.rowCount() - 1) boldFont = QFont() boldFont.setBold(True) if self.__suggestions: if self.__model.rowCount() == 0: if not self.__suggestionsItem: self.__suggestionsItem = QStandardItem( self.tr("Suggestions")) self.__suggestionsItem.setFont(boldFont) self.__model.appendRow(self.__suggestionsItem) for suggestion in self.__suggestions: self.__model.appendRow(QStandardItem(suggestion)) if not self.__recentSearches: self.__recentSearchesItem = QStandardItem( self.tr("No Recent Searches")) self.__recentSearchesItem.setFont(boldFont) self.__model.appendRow(self.__recentSearchesItem) else: self.__recentSearchesItem = QStandardItem( self.tr("Recent Searches")) self.__recentSearchesItem.setFont(boldFont) self.__model.appendRow(self.__recentSearchesItem) for recentSearch in self.__recentSearches: self.__model.appendRow(QStandardItem(recentSearch)) view = self.__completer.popup() view.setFixedHeight(view.sizeHintForRow(0) * self.__model.rowCount() + view.frameWidth() * 2) self.__searchButton.setEnabled( bool(self.__recentSearches or self.__suggestions)) def __completerActivated(self, index): """ Private slot handling the selection of an entry from the completer. @param index index of the item (QModelIndex) """ if self.__suggestionsItem and \ self.__suggestionsItem.index().row() == index.row(): return if self.__recentSearchesItem and \ self.__recentSearchesItem.index().row() == index.row(): return self.__searchNow() def __completerHighlighted(self, index): """ Private slot handling the highlighting of an entry of the completer. @param index index of the item (QModelIndex) @return flah indicating a successful highlighting (boolean) """ if self.__suggestionsItem and \ self.__suggestionsItem.index().row() == index.row(): return False if self.__recentSearchesItem and \ self.__recentSearchesItem.index().row() == index.row(): return False self.setText(index.data()) return True def __textEdited(self, txt): """ Private slot to handle changes of the search text. @param txt search text (string) """ if self.__suggestionsEnabled: if self.__suggestTimer is None: self.__suggestTimer = QTimer(self) self.__suggestTimer.setSingleShot(True) self.__suggestTimer.setInterval(200) self.__suggestTimer.timeout.connect(self.__getSuggestions) self.__suggestTimer.start() else: self.__completer.setCompletionPrefix(txt) self.__completer.complete() def __getSuggestions(self): """ Private slot to get search suggestions from the configured search engine. """ searchText = self.text() if searchText: self.__openSearchManager.currentEngine()\ .requestSuggestions(searchText) def __newSuggestions(self, suggestions): """ Private slot to receive a new list of suggestions. @param suggestions list of suggestions (list of strings) """ self.__suggestions = suggestions self.__setupCompleterMenu() self.__completer.complete() def __showEnginesMenu(self): """ Private slot to handle the display of the engines menu. """ self.__enginesMenu.clear() from .OpenSearch.OpenSearchEngineAction import OpenSearchEngineAction engineNames = self.__openSearchManager.allEnginesNames() for engineName in engineNames: engine = self.__openSearchManager.engine(engineName) action = OpenSearchEngineAction(engine, self.__enginesMenu) action.setData(engineName) action.triggered.connect(self.__changeCurrentEngine) self.__enginesMenu.addAction(action) if self.__openSearchManager.currentEngineName() == engineName: action.setCheckable(True) action.setChecked(True) ct = self.__mw.currentBrowser() linkedResources = ct.linkedResources("search") if len(linkedResources) > 0: self.__enginesMenu.addSeparator() for linkedResource in linkedResources: url = QUrl(linkedResource.href) title = linkedResource.title mimetype = linkedResource.type_ if mimetype != "application/opensearchdescription+xml": continue if url.isEmpty(): continue if url.isRelative(): url = ct.url().resolved(url) if not title: if not ct.title(): title = url.host() else: title = ct.title() action = self.__enginesMenu.addAction( self.tr("Add '{0}'").format(title), self.__addEngineFromUrl) action.setData(url) action.setIcon(ct.icon()) self.__enginesMenu.addSeparator() self.__enginesMenu.addAction(self.__mw.searchEnginesAction()) if self.__recentSearches: self.__enginesMenu.addAction(self.tr("Clear Recent Searches"), self.clear) def __changeCurrentEngine(self): """ Private slot to handle the selection of a search engine. """ action = self.sender() if action is not None: name = action.data() self.__openSearchManager.setCurrentEngineName(name) def __addEngineFromUrl(self): """ Private slot to add a search engine given its URL. """ action = self.sender() if action is not None: url = action.data() if not isinstance(url, QUrl): return self.__openSearchManager.addEngine(url) def __searchButtonClicked(self): """ Private slot to show the search menu via the search button. """ self.__setupCompleterMenu() self.__completer.complete() def clear(self): """ Public method to clear all private data. """ self.__recentSearches = [] self.__setupCompleterMenu() super(HelpWebSearchWidget, self).clear() self.clearFocus() def preferencesChanged(self): """ Public method to handle the change of preferences. """ self.__suggestionsEnabled = Preferences.getHelp("WebSearchSuggestions") if not self.__suggestionsEnabled: self.__suggestions = [] self.__setupCompleterMenu() def saveSearches(self): """ Public method to save the recently performed web searches. """ Preferences.Prefs.settings.setValue( 'Help/WebSearches', self.__recentSearches) def __loadSearches(self): """ Private method to load the recently performed web searches. """ searches = Preferences.Prefs.settings.value('Help/WebSearches') if searches is not None: self.__recentSearches = searches def openSearchManager(self): """ Public method to get a reference to the opensearch manager object. @return reference to the opensearch manager object (OpenSearchManager) """ return self.__openSearchManager def __currentEngineChanged(self): """ Private slot to track a change of the current search engine. """ if self.__openSearchManager.engineExists(self.__currentEngine): oldEngine = self.__openSearchManager.engine(self.__currentEngine) oldEngine.imageChanged.disconnect(self.__engineImageChanged) if self.__suggestionsEnabled: oldEngine.suggestions.disconnect(self.__newSuggestions) newEngine = self.__openSearchManager.currentEngine() if newEngine.networkAccessManager() is None: newEngine.setNetworkAccessManager(self.__mw.networkAccessManager()) newEngine.imageChanged.connect(self.__engineImageChanged) if self.__suggestionsEnabled: newEngine.suggestions.connect(self.__newSuggestions) self.setInactiveText(self.__openSearchManager.currentEngineName()) self.__currentEngine = self.__openSearchManager.currentEngineName() self.__engineButton.setIcon(QIcon(QPixmap.fromImage( self.__openSearchManager.currentEngine().image()))) self.__suggestions = [] self.__setupCompleterMenu() def __engineImageChanged(self): """ Private slot to handle a change of the current search engine icon. """ self.__engineButton.setIcon(QIcon(QPixmap.fromImage( self.__openSearchManager.currentEngine().image()))) def mousePressEvent(self, evt): """ Protected method called by a mouse press event. @param evt reference to the mouse event (QMouseEvent) """ if evt.button() == Qt.XButton1: self.__mw.currentBrowser().pageAction(QWebPage.Back).trigger() elif evt.button() == Qt.XButton2: self.__mw.currentBrowser().pageAction(QWebPage.Forward).trigger() else: super(HelpWebSearchWidget, self).mousePressEvent(evt)
class HelpWebSearchWidget(E5ClearableLineEdit): """ Class implementing a web search widget for the web browser. @signal search(QUrl) emitted when the search should be done """ search = pyqtSignal(QUrl) def __init__(self, parent=None): """ Constructor @param parent reference to the parent widget (QWidget) """ super(HelpWebSearchWidget, self).__init__(parent) from E5Gui.E5LineEdit import E5LineEdit from E5Gui.E5LineEditButton import E5LineEditButton from .OpenSearch.OpenSearchManager import OpenSearchManager self.__mw = parent self.__openSearchManager = OpenSearchManager(self) self.__openSearchManager.currentEngineChanged.connect( self.__currentEngineChanged) self.__currentEngine = "" self.__enginesMenu = QMenu(self) self.__engineButton = E5LineEditButton(self) self.__engineButton.setMenu(self.__enginesMenu) self.addWidget(self.__engineButton, E5LineEdit.LeftSide) self.__searchButton = E5LineEditButton(self) self.__searchButton.setIcon(UI.PixmapCache.getIcon("webSearch.png")) self.addWidget(self.__searchButton, E5LineEdit.LeftSide) self.__model = QStandardItemModel(self) self.__completer = QCompleter() self.__completer.setModel(self.__model) self.__completer.setCompletionMode( QCompleter.UnfilteredPopupCompletion) self.__completer.setWidget(self) self.__searchButton.clicked.connect(self.__searchButtonClicked) self.textEdited.connect(self.__textEdited) self.returnPressed.connect(self.__searchNow) self.__completer.activated[QModelIndex].connect( self.__completerActivated) self.__completer.highlighted[QModelIndex].connect( self.__completerHighlighted) self.__enginesMenu.aboutToShow.connect(self.__showEnginesMenu) self.__suggestionsItem = None self.__suggestions = [] self.__suggestTimer = None self.__suggestionsEnabled = Preferences.getHelp("WebSearchSuggestions") self.__recentSearchesItem = None self.__recentSearches = [] self.__maxSavedSearches = 10 self.__engine = None self.__loadSearches() self.__setupCompleterMenu() self.__currentEngineChanged() def __searchNow(self): """ Private slot to perform the web search. """ searchText = self.text() if not searchText: return globalSettings = QWebSettings.globalSettings() if not globalSettings.testAttribute( QWebSettings.PrivateBrowsingEnabled): if searchText in self.__recentSearches: self.__recentSearches.remove(searchText) self.__recentSearches.insert(0, searchText) if len(self.__recentSearches) > self.__maxSavedSearches: self.__recentSearches = \ self.__recentSearches[:self.__maxSavedSearches] self.__setupCompleterMenu() url = self.__openSearchManager.currentEngine().searchUrl(searchText) self.search.emit(url) def __setupCompleterMenu(self): """ Private method to create the completer menu. """ if not self.__suggestions or \ (self.__model.rowCount() > 0 and self.__model.item(0) != self.__suggestionsItem): self.__model.clear() self.__suggestionsItem = None else: self.__model.removeRows(1, self.__model.rowCount() - 1) boldFont = QFont() boldFont.setBold(True) if self.__suggestions: if self.__model.rowCount() == 0: if not self.__suggestionsItem: self.__suggestionsItem = QStandardItem( self.tr("Suggestions")) self.__suggestionsItem.setFont(boldFont) self.__model.appendRow(self.__suggestionsItem) for suggestion in self.__suggestions: self.__model.appendRow(QStandardItem(suggestion)) if not self.__recentSearches: self.__recentSearchesItem = QStandardItem( self.tr("No Recent Searches")) self.__recentSearchesItem.setFont(boldFont) self.__model.appendRow(self.__recentSearchesItem) else: self.__recentSearchesItem = QStandardItem( self.tr("Recent Searches")) self.__recentSearchesItem.setFont(boldFont) self.__model.appendRow(self.__recentSearchesItem) for recentSearch in self.__recentSearches: self.__model.appendRow(QStandardItem(recentSearch)) view = self.__completer.popup() view.setFixedHeight( view.sizeHintForRow(0) * self.__model.rowCount() + view.frameWidth() * 2) self.__searchButton.setEnabled( bool(self.__recentSearches or self.__suggestions)) def __completerActivated(self, index): """ Private slot handling the selection of an entry from the completer. @param index index of the item (QModelIndex) """ if self.__suggestionsItem and \ self.__suggestionsItem.index().row() == index.row(): return if self.__recentSearchesItem and \ self.__recentSearchesItem.index().row() == index.row(): return self.__searchNow() def __completerHighlighted(self, index): """ Private slot handling the highlighting of an entry of the completer. @param index index of the item (QModelIndex) @return flah indicating a successful highlighting (boolean) """ if self.__suggestionsItem and \ self.__suggestionsItem.index().row() == index.row(): return False if self.__recentSearchesItem and \ self.__recentSearchesItem.index().row() == index.row(): return False self.setText(index.data()) return True def __textEdited(self, txt): """ Private slot to handle changes of the search text. @param txt search text (string) """ if self.__suggestionsEnabled: if self.__suggestTimer is None: self.__suggestTimer = QTimer(self) self.__suggestTimer.setSingleShot(True) self.__suggestTimer.setInterval(200) self.__suggestTimer.timeout.connect(self.__getSuggestions) self.__suggestTimer.start() else: self.__completer.setCompletionPrefix(txt) self.__completer.complete() def __getSuggestions(self): """ Private slot to get search suggestions from the configured search engine. """ searchText = self.text() if searchText: self.__openSearchManager.currentEngine()\ .requestSuggestions(searchText) def __newSuggestions(self, suggestions): """ Private slot to receive a new list of suggestions. @param suggestions list of suggestions (list of strings) """ self.__suggestions = suggestions self.__setupCompleterMenu() self.__completer.complete() def __showEnginesMenu(self): """ Private slot to handle the display of the engines menu. """ self.__enginesMenu.clear() from .OpenSearch.OpenSearchEngineAction import OpenSearchEngineAction engineNames = self.__openSearchManager.allEnginesNames() for engineName in engineNames: engine = self.__openSearchManager.engine(engineName) action = OpenSearchEngineAction(engine, self.__enginesMenu) action.setData(engineName) action.triggered.connect(self.__changeCurrentEngine) self.__enginesMenu.addAction(action) if self.__openSearchManager.currentEngineName() == engineName: action.setCheckable(True) action.setChecked(True) ct = self.__mw.currentBrowser() linkedResources = ct.linkedResources("search") if len(linkedResources) > 0: self.__enginesMenu.addSeparator() for linkedResource in linkedResources: url = QUrl(linkedResource.href) title = linkedResource.title mimetype = linkedResource.type_ if mimetype != "application/opensearchdescription+xml": continue if url.isEmpty(): continue if url.isRelative(): url = ct.url().resolved(url) if not title: if not ct.title(): title = url.host() else: title = ct.title() action = self.__enginesMenu.addAction( self.tr("Add '{0}'").format(title), self.__addEngineFromUrl) action.setData(url) action.setIcon(ct.icon()) self.__enginesMenu.addSeparator() self.__enginesMenu.addAction(self.__mw.searchEnginesAction()) if self.__recentSearches: self.__enginesMenu.addAction(self.tr("Clear Recent Searches"), self.clear) def __changeCurrentEngine(self): """ Private slot to handle the selection of a search engine. """ action = self.sender() if action is not None: name = action.data() self.__openSearchManager.setCurrentEngineName(name) def __addEngineFromUrl(self): """ Private slot to add a search engine given its URL. """ action = self.sender() if action is not None: url = action.data() if not isinstance(url, QUrl): return self.__openSearchManager.addEngine(url) def __searchButtonClicked(self): """ Private slot to show the search menu via the search button. """ self.__setupCompleterMenu() self.__completer.complete() def clear(self): """ Public method to clear all private data. """ self.__recentSearches = [] self.__setupCompleterMenu() super(HelpWebSearchWidget, self).clear() self.clearFocus() def preferencesChanged(self): """ Public method to handle the change of preferences. """ self.__suggestionsEnabled = Preferences.getHelp("WebSearchSuggestions") if not self.__suggestionsEnabled: self.__suggestions = [] self.__setupCompleterMenu() def saveSearches(self): """ Public method to save the recently performed web searches. """ Preferences.Prefs.settings.setValue('Help/WebSearches', self.__recentSearches) def __loadSearches(self): """ Private method to load the recently performed web searches. """ searches = Preferences.Prefs.settings.value('Help/WebSearches') if searches is not None: self.__recentSearches = searches def openSearchManager(self): """ Public method to get a reference to the opensearch manager object. @return reference to the opensearch manager object (OpenSearchManager) """ return self.__openSearchManager def __currentEngineChanged(self): """ Private slot to track a change of the current search engine. """ if self.__openSearchManager.engineExists(self.__currentEngine): oldEngine = self.__openSearchManager.engine(self.__currentEngine) oldEngine.imageChanged.disconnect(self.__engineImageChanged) if self.__suggestionsEnabled: oldEngine.suggestions.disconnect(self.__newSuggestions) newEngine = self.__openSearchManager.currentEngine() if newEngine.networkAccessManager() is None: newEngine.setNetworkAccessManager(self.__mw.networkAccessManager()) newEngine.imageChanged.connect(self.__engineImageChanged) if self.__suggestionsEnabled: newEngine.suggestions.connect(self.__newSuggestions) self.setInactiveText(self.__openSearchManager.currentEngineName()) self.__currentEngine = self.__openSearchManager.currentEngineName() self.__engineButton.setIcon( QIcon( QPixmap.fromImage( self.__openSearchManager.currentEngine().image()))) self.__suggestions = [] self.__setupCompleterMenu() def __engineImageChanged(self): """ Private slot to handle a change of the current search engine icon. """ self.__engineButton.setIcon( QIcon( QPixmap.fromImage( self.__openSearchManager.currentEngine().image()))) def mousePressEvent(self, evt): """ Protected method called by a mouse press event. @param evt reference to the mouse event (QMouseEvent) """ if evt.button() == Qt.XButton1: self.__mw.currentBrowser().pageAction(QWebPage.Back).trigger() elif evt.button() == Qt.XButton2: self.__mw.currentBrowser().pageAction(QWebPage.Forward).trigger() else: super(HelpWebSearchWidget, self).mousePressEvent(evt)
class CompletingTextEdit(QTextEdit): def __init__(self, parent=None): super().__init__(parent) self.completerSequence = QKeySequence("Ctrl+Space") self._completer = QCompleter() self._completer.setWidget(self) self._completer.activated.connect(self.insertCompletion) def insertCompletion(self, completion): if self._completer.widget() is not self: return tc = self.textCursor() extra = len(completion) - len(self._completer.completionPrefix()) tc.movePosition(QTextCursor.Left) tc.movePosition(QTextCursor.EndOfWord) tc.insertText(completion[-extra:]) self.setTextCursor(tc) def textUnderCursor(self): tc = self.textCursor() tc.select(QTextCursor.WordUnderCursor) return tc.selectedText() def loadFromFile(self, fileName): f = QFile(fileName) if not f.open(QFile.ReadOnly): model = QStringListModel() self._completer.setModel(model) QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) words = [] while not f.atEnd(): line = f.readLine().trimmed() if line.length() != 0: try: line = str(line, encoding='utf-8') except TypeError: line = str(line) words.append(line) QApplication.restoreOverrideCursor() model = QStringListModel(words) self._completer.setModel(model) def loadFromList(self, words): model = QStringListModel(words) self._completer.setModel(model) def keyPressEvent(self, e): if self._completer.popup().isVisible(): # The following keys are forwarded by the completer to the widget. if e.key() in (Qt.Key_Enter, Qt.Key_Return, Qt.Key_Escape, Qt.Key_Tab, Qt.Key_Backtab): e.ignore() # Let the completer do default behavior. return newSeq = QKeySequence(e.modifiers() | e.key()) isShortcut = newSeq == self.completerSequence if not isShortcut: # Do not process the shortcut when we have a completer. super().keyPressEvent(e) return ctrlOrShift = e.modifiers() & (Qt.ControlModifier | Qt.ShiftModifier) if ctrlOrShift and not e.text(): return eow = "~!@#$%^&*()_+{}|:\"<>?,./;'[]\\-=" hasModifier = (e.modifiers() != Qt.NoModifier) and not ctrlOrShift completionPrefix = self.textUnderCursor() if not isShortcut and (hasModifier or not e.text() or len(completionPrefix) < 3 or e.text()[-1] in eow): self._completer.popup().hide() return if completionPrefix != self._completer.completionPrefix(): self._completer.setCompletionPrefix(completionPrefix) self._completer.popup().setCurrentIndex( self._completer.completionModel().index(0, 0)) cr = self.cursorRect() cr.setWidth( self._completer.popup().sizeHintForColumn(0) + self._completer.popup().verticalScrollBar().sizeHint().width()) self._completer.complete(cr)
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 TextEditTechnique(QTextEdit): def __init__(self, text_file, input_trigger=None, active=True): super(TextEditTechnique, self).__init__() self._completer = None self.isActive = active word_list = self.get_suggestion_words(text_file) self.input_trigger = input_trigger # connects the text textChanged event self.textChanged.connect(self.handle_input) self.char_length = 1 self.set_completer(word_list) self.technique_used = False self.no_suggestion_input = "~!@#$%^&*()_+{}|:\"<>?,./;'[]\\-= " # gets words for suggestion def get_suggestion_words(self, text_file): words = [] file = open(text_file) for line in file: for word in line.split(' '): word = word.strip().lower().replace('.', '').replace( ',', '').replace(':', '').replace('?', '') if word and word not in words: words.append(word) file.close() return words # initializes the completer def set_completer(self, words): if self._completer is not None: self._completer.activated.disconnect() self._completer = QCompleter(words, self) self._completer.setCaseSensitivity(Qt.CaseInsensitive) self._completer.setWrapAround(False) self._completer.setWidget(self) self._completer.setCompletionMode(QCompleter.PopupCompletion) self._completer.activated.connect(self.insert_sel_suggestion) # insert the selected suggestion and moves the text cursor def insert_sel_suggestion(self, suggestion): if self._completer.widget() is not self: return text_cursor = self.textCursor() extra = len(suggestion) - len(self._completer.completionPrefix()) text_cursor.movePosition(QTextCursor.Left) text_cursor.movePosition(QTextCursor.EndOfWord) text_cursor.insertText(suggestion[-extra:]) self.setTextCursor(text_cursor) # returns the current typed word def text_under_cursor(self): text_cursor = self.textCursor() text_cursor.select(QTextCursor.WordUnderCursor) return text_cursor.selectedText() # sets the widget def focusInEvent(self, e): if self._completer is not None and self.isActive: self._completer.setWidget(self) super(TextEditTechnique, self).focusInEvent(e) def keyPressEvent(self, e): # behaves like a normal input field! if not self.isActive or self._completer is None: super(TextEditTechnique, self).keyPressEvent(e) return # ignore events funktion keys on Textedit if popup is visible --> popup behaves default if self._completer.popup().isVisible(): if e.key() in (Qt.Key_Enter, Qt.Key_Return, Qt.Key_Escape, Qt.Key_Tab, Qt.Key_Backtab): e.ignore() self.technique_used = True return # writes text in the editfield super(TextEditTechnique, self).keyPressEvent(e) ctrl_or_shift = e.modifiers() & (Qt.ControlModifier | Qt.ShiftModifier) if self._completer is None or (ctrl_or_shift and len(e.text()) == 0): return has_modifier = (e.modifiers() != Qt.NoModifier) and not ctrl_or_shift completion_prefix = self.text_under_cursor() # hide the popup for no chars, modifiers, shift and no text if (has_modifier or len(e.text()) == 0 or len(completion_prefix) < self.char_length or e.text()[-1] in self.no_suggestion_input): self._completer.popup().hide() return # shows the popup with the suggested words if completion_prefix != self._completer.completionPrefix(): self._completer.setCompletionPrefix(completion_prefix) self._completer.popup().setCurrentIndex( self._completer.completionModel().index(0, 0)) cursor = self.cursorRect() cursor.setWidth( self._completer.popup().sizeHintForColumn(0) + self._completer.popup().verticalScrollBar().sizeHint().width()) self._completer.complete(cursor) # event called if textedit input has changed def handle_input(self): timestamp = time.time() self.input_trigger.emit(self.toPlainText(), timestamp) # clears the text input def clear_input(self): self.setHtml('') # deactivates the completer def deactivate_completer(self): self.isActive = False # activates the completer def activate_completer(self): self.isActive = True
class WebBrowserWebSearchWidget(E5ClearableLineEdit): """ Class implementing a web search widget for the web browser. @signal search(QUrl) emitted when the search should be done """ search = pyqtSignal(QUrl) def __init__(self, mainWindow, parent=None): """ Constructor @param mainWindow reference to the browser main window @type WebBrowserWindow @param parent reference to the parent widget @type QWidget """ super(WebBrowserWebSearchWidget, self).__init__(parent) from E5Gui.E5LineEdit import E5LineEdit from E5Gui.E5LineEditButton import E5LineEditButton from .OpenSearch.OpenSearchManager import OpenSearchManager self.__mw = mainWindow self.__openSearchManager = OpenSearchManager(self) self.__openSearchManager.currentEngineChanged.connect( self.__currentEngineChanged) self.__currentEngine = "" self.__enginesMenu = QMenu(self) self.__enginesMenu.triggered.connect( self.__handleEnginesMenuActionTriggered) self.__engineButton = E5LineEditButton(self) self.__engineButton.setMenu(self.__enginesMenu) self.addWidget(self.__engineButton, E5LineEdit.LeftSide) self.__searchButton = E5LineEditButton(self) self.__searchButton.setIcon(UI.PixmapCache.getIcon("webSearch.png")) self.addWidget(self.__searchButton, E5LineEdit.LeftSide) self.__model = QStandardItemModel(self) self.__completer = QCompleter() self.__completer.setModel(self.__model) self.__completer.setCompletionMode( QCompleter.UnfilteredPopupCompletion) self.__completer.setWidget(self) self.__searchButton.clicked.connect(self.__searchButtonClicked) self.textEdited.connect(self.__textEdited) self.returnPressed.connect(self.__searchNow) self.__completer.activated[QModelIndex].connect( self.__completerActivated) self.__completer.highlighted[QModelIndex].connect( self.__completerHighlighted) self.__enginesMenu.aboutToShow.connect(self.__showEnginesMenu) self.__suggestionsItem = None self.__suggestions = [] self.__suggestTimer = None self.__suggestionsEnabled = Preferences.getWebBrowser( "WebSearchSuggestions") self.__recentSearchesItem = None self.__recentSearches = [] self.__maxSavedSearches = 10 self.__engine = None self.__loadSearches() self.__setupCompleterMenu() self.__currentEngineChanged() def __searchNow(self): """ Private slot to perform the web search. """ searchText = self.text() if not searchText: return import WebBrowser.WebBrowserWindow if WebBrowser.WebBrowserWindow.WebBrowserWindow.isPrivate(): return if searchText in self.__recentSearches: self.__recentSearches.remove(searchText) self.__recentSearches.insert(0, searchText) if len(self.__recentSearches) > self.__maxSavedSearches: self.__recentSearches = self.__recentSearches[:self. __maxSavedSearches] self.__setupCompleterMenu() self.__mw.currentBrowser().setFocus() self.__mw.currentBrowser().load( self.__openSearchManager.currentEngine().searchUrl(searchText)) def __setupCompleterMenu(self): """ Private method to create the completer menu. """ if (not self.__suggestions or (self.__model.rowCount() > 0 and self.__model.item(0) != self.__suggestionsItem)): self.__model.clear() self.__suggestionsItem = None else: self.__model.removeRows(1, self.__model.rowCount() - 1) boldFont = QFont() boldFont.setBold(True) if self.__suggestions: if self.__model.rowCount() == 0: if not self.__suggestionsItem: self.__suggestionsItem = QStandardItem( self.tr("Suggestions")) self.__suggestionsItem.setFont(boldFont) self.__model.appendRow(self.__suggestionsItem) for suggestion in self.__suggestions: self.__model.appendRow(QStandardItem(suggestion)) if not self.__recentSearches: self.__recentSearchesItem = QStandardItem( self.tr("No Recent Searches")) self.__recentSearchesItem.setFont(boldFont) self.__model.appendRow(self.__recentSearchesItem) else: self.__recentSearchesItem = QStandardItem( self.tr("Recent Searches")) self.__recentSearchesItem.setFont(boldFont) self.__model.appendRow(self.__recentSearchesItem) for recentSearch in self.__recentSearches: self.__model.appendRow(QStandardItem(recentSearch)) view = self.__completer.popup() view.setFixedHeight( view.sizeHintForRow(0) * self.__model.rowCount() + view.frameWidth() * 2) self.__searchButton.setEnabled( bool(self.__recentSearches or self.__suggestions)) def __completerActivated(self, index): """ Private slot handling the selection of an entry from the completer. @param index index of the item (QModelIndex) """ if (self.__suggestionsItem and self.__suggestionsItem.index().row() == index.row()): return if (self.__recentSearchesItem and self.__recentSearchesItem.index().row() == index.row()): return self.__searchNow() def __completerHighlighted(self, index): """ Private slot handling the highlighting of an entry of the completer. @param index index of the item (QModelIndex) @return flah indicating a successful highlighting (boolean) """ if (self.__suggestionsItem and self.__suggestionsItem.index().row() == index.row()): return False if (self.__recentSearchesItem and self.__recentSearchesItem.index().row() == index.row()): return False self.setText(index.data()) return True def __textEdited(self, txt): """ Private slot to handle changes of the search text. @param txt search text (string) """ if self.__suggestionsEnabled: if self.__suggestTimer is None: self.__suggestTimer = QTimer(self) self.__suggestTimer.setSingleShot(True) self.__suggestTimer.setInterval(200) self.__suggestTimer.timeout.connect(self.__getSuggestions) self.__suggestTimer.start() else: self.__completer.setCompletionPrefix(txt) self.__completer.complete() def __getSuggestions(self): """ Private slot to get search suggestions from the configured search engine. """ searchText = self.text() if searchText: self.__openSearchManager.currentEngine().requestSuggestions( searchText) def __newSuggestions(self, suggestions): """ Private slot to receive a new list of suggestions. @param suggestions list of suggestions (list of strings) """ self.__suggestions = suggestions self.__setupCompleterMenu() self.__completer.complete() def __showEnginesMenu(self): """ Private slot to handle the display of the engines menu. """ self.__enginesMenu.clear() from .OpenSearch.OpenSearchEngineAction import OpenSearchEngineAction engineNames = self.__openSearchManager.allEnginesNames() for engineName in engineNames: engine = self.__openSearchManager.engine(engineName) action = OpenSearchEngineAction(engine, self.__enginesMenu) action.setData(engineName) self.__enginesMenu.addAction(action) if self.__openSearchManager.currentEngineName() == engineName: action.setCheckable(True) action.setChecked(True) cb = self.__mw.currentBrowser() from .Tools import Scripts script = Scripts.getOpenSearchLinks() cb.page().runJavaScript(script, WebBrowserPage.SafeJsWorld, self.__showEnginesMenuCallback) def __showEnginesMenuCallback(self, res): """ Private method handling the open search links callback. @param res result of the JavaScript @type list of dict """ cb = self.__mw.currentBrowser() if res: self.__enginesMenu.addSeparator() for entry in res: url = cb.url().resolved(QUrl(entry["url"])) title = entry["title"] if url.isEmpty(): continue if not title: title = cb.title() action = self.__enginesMenu.addAction( self.tr("Add '{0}'").format(title)) action.setData(url) action.setIcon(cb.icon()) self.__enginesMenu.addSeparator() self.__enginesMenu.addAction(self.__mw.searchEnginesAction()) if self.__recentSearches: act = self.__enginesMenu.addAction( self.tr("Clear Recent Searches")) act.setData("@@CLEAR@@") def __handleEnginesMenuActionTriggered(self, action): """ Private slot to handle an action of the menu being triggered. @param action reference to the action that triggered @type QAction """ actData = action.data() if isinstance(actData, QUrl): # add search engine self.__openSearchManager.addEngine(actData) elif isinstance(actData, str): # engine name or special action if actData == "@@CLEAR@@": self.clear() else: self.__openSearchManager.setCurrentEngineName(actData) def __searchButtonClicked(self): """ Private slot to show the search menu via the search button. """ self.__setupCompleterMenu() self.__completer.complete() def clear(self): """ Public method to clear all private data. """ self.__recentSearches = [] self.__setupCompleterMenu() super(WebBrowserWebSearchWidget, self).clear() self.clearFocus() def preferencesChanged(self): """ Public method to handle the change of preferences. """ self.__suggestionsEnabled = Preferences.getWebBrowser( "WebSearchSuggestions") if not self.__suggestionsEnabled: self.__suggestions = [] self.__setupCompleterMenu() def saveSearches(self): """ Public method to save the recently performed web searches. """ Preferences.Prefs.settings.setValue('WebBrowser/WebSearches', self.__recentSearches) def __loadSearches(self): """ Private method to load the recently performed web searches. """ searches = Preferences.Prefs.settings.value('WebBrowser/WebSearches') if searches is not None: self.__recentSearches = searches def openSearchManager(self): """ Public method to get a reference to the opensearch manager object. @return reference to the opensearch manager object (OpenSearchManager) """ return self.__openSearchManager def __currentEngineChanged(self): """ Private slot to track a change of the current search engine. """ if self.__openSearchManager.engineExists(self.__currentEngine): oldEngine = self.__openSearchManager.engine(self.__currentEngine) oldEngine.imageChanged.disconnect(self.__engineImageChanged) if self.__suggestionsEnabled: oldEngine.suggestions.disconnect(self.__newSuggestions) newEngine = self.__openSearchManager.currentEngine() if newEngine.networkAccessManager() is None: newEngine.setNetworkAccessManager(self.__mw.networkManager()) newEngine.imageChanged.connect(self.__engineImageChanged) if self.__suggestionsEnabled: newEngine.suggestions.connect(self.__newSuggestions) self.setInactiveText(self.__openSearchManager.currentEngineName()) self.__currentEngine = self.__openSearchManager.currentEngineName() self.__engineButton.setIcon( QIcon( QPixmap.fromImage( self.__openSearchManager.currentEngine().image()))) self.__suggestions = [] self.__setupCompleterMenu() def __engineImageChanged(self): """ Private slot to handle a change of the current search engine icon. """ self.__engineButton.setIcon( QIcon( QPixmap.fromImage( self.__openSearchManager.currentEngine().image()))) def mousePressEvent(self, evt): """ Protected method called by a mouse press event. @param evt reference to the mouse event (QMouseEvent) """ if evt.button() == Qt.XButton1: self.__mw.currentBrowser().triggerPageAction(QWebEnginePage.Back) elif evt.button() == Qt.XButton2: self.__mw.currentBrowser().triggerPageAction( QWebEnginePage.Forward) else: super(WebBrowserWebSearchWidget, self).mousePressEvent(evt)