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)
def complete(self, cr, results): proposals = [] proposals += results.get('modules', []) proposals += results.get('classes', []) proposals += results.get('attributes', []) proposals += results.get('functions', []) self.model().setStringList(proposals) self.popup().setCurrentIndex(self.model().index(0, 0)) cr.setWidth(self.popup().sizeHintForColumn(0) + self.popup().verticalScrollBar().sizeHint().width() + 10) QCompleter.complete(self, cr)
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 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 DiscoveryPlugin: def __init__(self, _iface): # Save reference to the QGIS interface self.iface = _iface # initialize plugin directory self.plugin_dir = os.path.dirname(__file__) # Variables to facilitate delayed queries and database connection management self.db_timer = QTimer() self.line_edit_timer = QTimer() self.line_edit_timer.setSingleShot(True) self.line_edit_timer.timeout.connect(self.reset_line_edit_after_move) self.next_query_time = None self.last_query_time = time.time() self.db_conn = None self.search_delay = 0.5 # s self.query_sql = '' self.query_text = '' self.query_dict = {} self.db_idle_time = 60.0 # s self.display_time = 5000 # ms self.bar_info_time = 30 # s self.search_results = [] self.tool_bar = None self.search_line_edit = None self.completer = None self.conn_info = {} self.marker = QgsVertexMarker(iface.mapCanvas()) self.marker.setIconSize(15) self.marker.setPenWidth(2) self.marker.setColor(QColor(226, 27, 28)) #51,160,44)) self.marker.setZValue(11) self.marker.setVisible(False) self.marker2 = QgsVertexMarker(iface.mapCanvas()) self.marker2.setIconSize(16) self.marker2.setPenWidth(4) self.marker2.setColor(QColor(255, 255, 255, 200)) self.marker2.setZValue(10) self.marker2.setVisible(False) self.is_displayed = False self.rubber_band = QgsRubberBand(iface.mapCanvas(), False) self.rubber_band.setVisible(False) self.rubber_band.setWidth(3) self.rubber_band.setStrokeColor(QColor(226, 27, 28)) self.rubber_band.setFillColor(QColor(226, 27, 28, 63)) def initGui(self): # Create a new toolbar self.tool_bar = self.iface.addToolBar('Discovery') self.tool_bar.setObjectName('Discovery_Plugin') # Create action that will start plugin configuration self.action_config = QAction( QIcon(os.path.join(self.plugin_dir, "discovery_logo.png")), u"Configure Discovery", self.tool_bar) self.action_config.triggered.connect(self.show_config_dialog) self.tool_bar.addAction(self.action_config) # Add combobox for configs self.config_combo = QComboBox() settings = QgsSettings() settings.beginGroup("/Discovery") config_list = settings.value("config_list") if config_list: for conf in config_list: self.config_combo.addItem(conf) elif settings.childGroups(): # support for prev version key = "Config1" config_list = [] config_list.append(key) settings.setValue("config_list", config_list) self.config_combo.addItem(key) settings.setValue(key + "data_type", settings.value("data_type")) settings.setValue(key + "file", settings.value("file")) settings.setValue(key + "connection", settings.value("connection")) settings.setValue(key + "schema", settings.value("schema")) settings.setValue(key + "table", settings.value("table")) settings.setValue(key + "search_column", settings.value("search_column")) settings.setValue(key + "echo_search_column", settings.value("echo_search_column")) settings.setValue(key + "display_columns", settings.value("display_columns")) settings.setValue(key + "geom_column", settings.value("geom_column")) settings.setValue(key + "scale_expr", settings.value("scale_expr")) settings.setValue(key + "bbox_expr", settings.value("bbox_expr")) delete_config_from_settings("", settings) self.tool_bar.addWidget(self.config_combo) # Add search edit box self.search_line_edit = QgsFilterLineEdit() self.search_line_edit.setPlaceholderText('Search for...') self.search_line_edit.setMaximumWidth(768) self.tool_bar.addWidget(self.search_line_edit) self.config_combo.currentIndexChanged.connect( self.change_configuration) # Set up the completer self.completer = QCompleter([]) # Initialise with en empty list self.completer.setCaseSensitivity(Qt.CaseInsensitive) self.completer.setMaxVisibleItems(1000) self.completer.setModelSorting( QCompleter.UnsortedModel) # Sorting done in PostGIS self.completer.setCompletionMode(QCompleter.UnfilteredPopupCompletion ) # Show all fetched possibilities self.completer.activated[QModelIndex].connect(self.on_result_selected) self.completer.highlighted[QModelIndex].connect( self.on_result_highlighted) self.search_line_edit.setCompleter(self.completer) # Connect any signals self.search_line_edit.textEdited.connect(self.on_search_text_changed) # Search results self.search_results = [] # Set up a timer to periodically perform db queries as required self.db_timer.timeout.connect(self.do_db_operations) self.db_timer.start(100) # Read config self.read_config(config_list[0] if config_list else "") self.locator_filter = locator_filter.DiscoveryLocatorFilter(self) self.iface.registerLocatorFilter(self.locator_filter) # Debug # import pydevd; pydevd.settrace('localhost', port=5678) def unload(self): # Stop timer self.db_timer.stop() # Disconnect any signals self.db_timer.timeout.disconnect(self.do_db_operations) self.completer.highlighted[QModelIndex].disconnect( self.on_result_highlighted) self.completer.activated[QModelIndex].disconnect( self.on_result_selected) self.search_line_edit.textEdited.disconnect( self.on_search_text_changed) # Remove the new toolbar self.tool_bar.clear() # Clear all actions self.iface.mainWindow().removeToolBar(self.tool_bar) self.iface.deregisterLocatorFilter(self.locator_filter) self.locator_filter = None def clear_suggestions(self): model = self.completer.model() model.setStringList([]) def on_search_text_changed(self, new_search_text): """ This function is called whenever the user modified the search text 1. Open a database connection 2. Make the query 3. Update the QStringListModel with these results 4. Store the other details in self.search_results """ self.query_text = new_search_text if len(new_search_text) < 3: # Clear any previous suggestions in case the user is 'backspacing' self.clear_suggestions() return if self.data_type == "postgres": query_text, query_dict = dbutils.get_search_sql( new_search_text, self.postgisgeomcolumn, self.postgissearchcolumn, self.echosearchcolumn, self.postgisdisplaycolumn, self.extra_expr_columns, self.postgisschema, self.postgistable) self.schedule_search(query_text, query_dict) elif self.data_type == "gpkg": query_text = (new_search_text, self.postgissearchcolumn, self.echosearchcolumn, self.postgisdisplaycolumn.split(","), self.extra_expr_columns, self.layer) self.schedule_search(query_text, None) elif self.data_type == "mssql": query_text = mssql_utils.get_search_sql( new_search_text, self.postgisgeomcolumn, self.postgissearchcolumn, self.echosearchcolumn, self.postgisdisplaycolumn, self.extra_expr_columns, self.postgisschema, self.postgistable) self.schedule_search(query_text, None) def do_db_operations(self): if self.next_query_time is not None and self.next_query_time < time.time( ): # It's time to run a query self.next_query_time = None # Prevent this query from being repeated self.last_query_time = time.time() self.perform_search() else: # We're not performing a query, close the db connection if it's been open for > 60s if time.time() > self.last_query_time + self.db_idle_time: self.db_conn = None def perform_search(self): db = self.get_db() self.search_results = [] suggestions = [] if self.data_type == "postgres": cur = db.cursor() try: cur.execute(self.query_sql, self.query_dict) except psycopg2.Error as e: err_info = "Failed to execute the search query. Please, check your settings. Error message:\n\n" err_info += f"{e.pgerror}" QMessageBox.critical(None, "Discovery", err_info) return result_set = cur.fetchall() elif self.data_type == "mssql": result_set = mssql_utils.execute(db, self.query_sql) elif self.data_type == "gpkg": result_set = gpkg_utils.search_gpkg(*self.query_sql) for row in result_set: geom, epsg, suggestion_text = row[0], row[1], row[2] extra_data = {} for idx, extra_col in enumerate(self.extra_expr_columns): extra_data[extra_col] = row[3 + idx] self.search_results.append( (geom, epsg, suggestion_text, extra_data)) suggestions.append(suggestion_text) model = self.completer.model() model.setStringList(suggestions) self.completer.complete() def schedule_search(self, query_text, query_dict): # Update the search text and the time after which the query should be executed self.query_sql = query_text self.query_dict = query_dict self.next_query_time = time.time() + self.search_delay def show_bar_info(self, info_text): """Optional show info bar message with selected result information""" self.iface.messageBar().clearWidgets() if self.bar_info_time: self.iface.messageBar().pushMessage("Discovery", info_text, level=Qgis.Info, duration=self.bar_info_time) def on_result_selected(self, result_index): # What to do when the user makes a selection self.select_result(self.search_results[result_index.row()]) def select_result(self, result_data): geometry_text, src_epsg, suggestion_text, extra_data = result_data location_geom = QgsGeometry.fromWkt(geometry_text) canvas = self.iface.mapCanvas() dst_srid = canvas.mapSettings().destinationCrs().authid() transform = QgsCoordinateTransform( QgsCoordinateReferenceSystem(src_epsg), QgsCoordinateReferenceSystem(dst_srid), canvas.mapSettings().transformContext()) # Ensure the geometry from the DB is reprojected to the same SRID as the map canvas location_geom.transform(transform) location_centroid = location_geom.centroid().asPoint() # show temporary marker if location_geom.type() == QgsWkbTypes.PointGeometry: self.show_marker(location_centroid) elif location_geom.type() == QgsWkbTypes.LineGeometry or \ location_geom.type() == QgsWkbTypes.PolygonGeometry: self.show_line_rubber_band(location_geom) else: #unsupported geometry type pass # Adjust map canvas extent zoom_method = 'Move and Zoom' if zoom_method == 'Move and Zoom': # with higher priority try to use exact bounding box to zoom to features (if provided) bbox_str = eval_expression(self.bbox_expr, extra_data) rect = bbox_str_to_rectangle(bbox_str) if rect is not None: # transform the rectangle in case of OTF projection rect = transform.transformBoundingBox(rect) else: # bbox is not available - so let's just use defined scale # compute target scale. If the result is 2000 this means the target scale is 1:2000 rect = location_geom.boundingBox() if rect.isEmpty(): scale_denom = eval_expression(self.scale_expr, extra_data, default=2000.) rect = canvas.mapSettings().extent() rect.scale(scale_denom / canvas.scale(), location_centroid) else: # enlarge geom bbox to have some margin rect.scale(1.2) canvas.setExtent(rect) elif zoom_method == 'Move': current_extent = QgsGeometry.fromRect( self.iface.mapCanvas().extent()) dx = location_centroid.x() - location_centroid.x() dy = location_centroid.y() - location_centroid.y() current_extent.translate(dx, dy) canvas.setExtent(current_extent.boundingBox()) canvas.refresh() self.line_edit_timer.start(0) if self.info_to_clipboard: QApplication.clipboard().setText(suggestion_text) suggestion_text += ' (copied to clipboard)' self.show_bar_info(suggestion_text) def on_result_highlighted(self, result_idx): self.line_edit_timer.start(0) def reset_line_edit_after_move(self): self.search_line_edit.setText(self.query_text) def get_db(self): # Create a new new connection if required if self.db_conn is None: if self.data_type == "postgres": self.db_conn = dbutils.get_connection(self.conn_info) elif self.data_type == "mssql": self.db_conn = mssql_utils.get_mssql_conn(self.conn_info) return self.db_conn def change_configuration(self): self.search_line_edit.setText("") self.line_edit_timer.start(0) self.read_config(self.config_combo.currentText()) def read_config(self, key=""): # the following code reads the configuration file which setups the plugin to search in the correct database, # table and method settings = QgsSettings() settings.beginGroup("/Discovery") connection = settings.value(key + "connection", "", type=str) self.data_type = settings.value(key + "data_type", "", type=str) self.file = settings.value(key + "file", "", type=str) self.postgisschema = settings.value(key + "schema", "", type=str) self.postgistable = settings.value(key + "table", "", type=str) self.postgissearchcolumn = settings.value(key + "search_column", "", type=str) self.echosearchcolumn = settings.value(key + "echo_search_column", True, type=bool) self.postgisdisplaycolumn = settings.value(key + "display_columns", "", type=str) self.postgisgeomcolumn = settings.value(key + "geom_column", "", type=str) if settings.value("marker_time_enabled", True, type=bool): self.display_time = settings.value("marker_time", 5000, type=int) else: self.display_time = -1 if settings.value("bar_info_time_enabled", True, type=bool): self.bar_info_time = settings.value("bar_info_time", 30, type=int) else: self.bar_info_time = 0 self.info_to_clipboard = settings.value("info_to_clipboard", True, type=bool) scale_expr = settings.value(key + "scale_expr", "", type=str) bbox_expr = settings.value(key + "bbox_expr", "", type=str) if self.is_displayed: self.hide_marker() self.hide_rubber_band() self.is_displayed = False self.make_enabled(False) # assume the config is invalid first self.db_conn = None if self.data_type == "postgres": self.conn_info = dbutils.get_postgres_conn_info(connection) self.layer = None if len(connection) == 0 or len(self.postgisschema) == 0 or len(self.postgistable) == 0 or \ len(self.postgissearchcolumn) == 0 or len(self.postgisgeomcolumn) == 0: return if len(self.conn_info) == 0: iface.messageBar().pushMessage( "Discovery", "The database connection '%s' does not exist!" % connection, level=Qgis.Critical) return if self.data_type == "mssql": self.conn_info = mssql_utils.get_mssql_conn_info(connection) self.layer = None if len(connection) == 0 or len(self.postgisschema) == 0 or len(self.postgistable) == 0 or \ len(self.postgissearchcolumn) == 0 or len(self.postgisgeomcolumn) == 0: return if len(self.conn_info) == 0: iface.messageBar().pushMessage( "Discovery", "The database connection '%s' does not exist!" % connection, level=Qgis.Critical) return elif self.data_type == "gpkg": self.layer = QgsVectorLayer( self.file + '|layername=' + self.postgistable, self.postgistable, 'ogr') self.conn_info = None self.extra_expr_columns = [] self.scale_expr = None self.bbox_expr = None self.make_enabled(True) # optional scale expression when zooming in to results if len(scale_expr) != 0: expr = QgsExpression(scale_expr) if expr.hasParserError(): iface.messageBar().pushMessage("Discovery", "Invalid scale expression: " + expr.parserErrorString(), level=Qgis.Warning) else: self.scale_expr = scale_expr self.extra_expr_columns += expr.referencedColumns() # optional bbox expression when zooming in to results if len(bbox_expr) != 0: expr = QgsExpression(bbox_expr) if expr.hasParserError(): iface.messageBar().pushMessage("Discovery", "Invalid bbox expression: " + expr.parserErrorString(), level=Qgis.Warning) else: self.bbox_expr = bbox_expr self.extra_expr_columns += expr.referencedColumns() def show_config_dialog(self): dlg = config_dialog.ConfigDialog() if (self.config_combo.currentIndex() >= 0): dlg.configOptions.setCurrentIndex(self.config_combo.currentIndex()) if dlg.exec_(): dlg.write_config() self.config_combo.clear() for key in [ dlg.configOptions.itemText(i) for i in range(dlg.configOptions.count()) ]: self.config_combo.addItem(key) self.config_combo.setCurrentIndex(dlg.configOptions.currentIndex()) self.change_configuration() def make_enabled(self, enabled): self.search_line_edit.setEnabled(enabled) self.search_line_edit.setPlaceholderText( "Search for..." if enabled else "Search disabled: check configuration") def show_marker(self, point): for m in [self.marker, self.marker2]: m.setCenter(point) m.setOpacity(1.0) m.setVisible(True) if self.display_time == -1: self.is_displayed = True else: QTimer.singleShot(self.display_time, self.hide_marker) def hide_marker(self): opacity = self.marker.opacity() if opacity > 0.: # produce a fade out effect opacity -= 0.1 self.marker.setOpacity(opacity) self.marker2.setOpacity(opacity) QTimer.singleShot(100, self.hide_marker) else: self.marker.setVisible(False) self.marker2.setVisible(False) def show_line_rubber_band(self, geom): self.rubber_band.reset(geom.type()) self.rubber_band.setToGeometry(geom, None) self.rubber_band.setVisible(True) self.rubber_band.setOpacity(1.0) self.rubber_band.show() if self.display_time == -1: self.is_displayed = True else: QTimer.singleShot(self.display_time, self.hide_rubber_band) pass def hide_rubber_band(self): opacity = self.rubber_band.opacity() if opacity > 0.: # produce a fade out effect opacity -= 0.1 self.rubber_band.setOpacity(opacity) QTimer.singleShot(100, self.hide_rubber_band) else: self.rubber_band.setVisible(False) self.rubber_band.hide()
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 ExtendedLineEdit(QLineEdit): signal_send_movie = pyqtSignal(list) signal_search_movie = pyqtSignal(str) signal_request_movie_data = pyqtSignal(int) signal_set_loading = pyqtSignal(str, bool) DEBUG = False def __init__(self, parent=None): super(ExtendedLineEdit, self).__init__(parent) self.movies = [] self.completer_lw = QListWidget() self.model = self.completer_lw.model() self.completer = QCompleter(self.model, self) self.completer.setPopup(self.completer_lw) self.completer.setCompletionMode(QCompleter.UnfilteredPopupCompletion) self.completer.setCompletionRole( Qt.UserRole ) # Change role because default EditRole is paited to list somehow self.setCompleter(self.completer) self.textEdited.connect(self.text_edited) #self.completer.activated[QModelIndex].connect(self.on_completer_activated) #self.completer.activated[str].connect(self.on_completer_activated_str) self.completer_lw.itemClicked.connect(self.item_clicked) self.installEventFilter(self) def eventFilter(self, obj, event): if event.type() == QEvent.MouseButtonPress: if event.button() == Qt.LeftButton: self.completer.complete() return super(ExtendedLineEdit, self).eventFilter(obj, event) def setText(self, text: str) -> None: if self.DEBUG: print('setText', text, self.sender()) super(ExtendedLineEdit, self).setText(text) if not isinstance(self.sender(), QCompleter) and text: self.text_edited(text) @pyqtSlot(list) def update_movies_list(self, result): if self.DEBUG: print('update_movies_list', result[0], len(result) - 1) type = result.pop(0) if type == 'db': self.movies = [x + [ type, ] for x in result] self.completer_lw.clear() self.completer.complete() else: for item in result: self.movies.append(item + [ type, ]) for item in result: cwidget = MyWidget() cwidget.label_movie_name.setText(item[1] if item[1] else item[2]) cwidget.label_original_name.setText(item[2]) cwidget.label_source.setText(type) cwidget.label_year.setText(str(item[3])) completer_myQListWidgetItem = QListWidgetItem(self.completer_lw) completer_myQListWidgetItem.setSizeHint(cwidget.sizeHint()) completer_myQListWidgetItem.setData(Qt.UserRole, item[1]) self.completer_lw.addItem(completer_myQListWidgetItem) self.completer_lw.setItemWidget(completer_myQListWidgetItem, cwidget) if self.hasFocus() and not self.completer_lw.isVisible() and len( self.movies) > 0: self.completer.complete() @pyqtSlot(str) def text_edited(self, text): if self.DEBUG: print('text_edited', text, self.sender()) if text and isinstance(self.sender(), ExtendedLineEdit): self.signal_search_movie.emit(text) @pyqtSlot(str) def on_completer_activated_str(self, name: str): if self.DEBUG: print('on_completer_activated_str', name) @pyqtSlot(QModelIndex) def on_completer_activated(self, index: QModelIndex): if self.DEBUG: print('on_completer_activated', index.row()) item = self.movies[index.row()] if len(item) > 13: self.signal_send_movie.emit(item[:13]) self.signal_request_movie_data.emit(item[13]) else: self.signal_send_movie.emit(item) @pyqtSlot(QListWidgetItem) def item_clicked(self, item: QListWidgetItem): if self.DEBUG: print('item_clicked', item, [i[1] for i in self.movies]) index = self.completer_lw.indexFromItem(item) item = self.movies[index.row()] type = item[-1] if type == 'db': self.signal_send_movie.emit(item) else: self.signal_set_loading.emit('movie', True) if len(item) > 13: self.signal_send_movie.emit(item[:13]) self.signal_request_movie_data.emit(item[13]) else: self.signal_send_movie.emit(item) def reset(self): if self.DEBUG: print('reset') self.completer_lw.clear() self.clearFocus()
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)
def suggest(self, words): completer = QCompleter(words, self) completer.setCaseSensitivity(Qt.CaseInsensitive) self.comboxBox.setCompleter(completer) completer.complete()
class DiscoveryPlugin: def __init__(self, _iface): # Save reference to the QGIS interface self.iface = _iface # initialize plugin directory self.plugin_dir = os.path.dirname(__file__) # Variables to facilitate delayed queries and database connection management self.icon_loading = ':plugins/_Discovery_sqlite/icons/loading.gif' self.db_timer = QTimer() self.line_edit_timer = QTimer() self.line_edit_timer.setSingleShot(True) self.line_edit_timer.timeout.connect(self.reset_line_edit_after_move) self.next_query_time = None self.last_query_time = time.time() self.db_conn = None self.search_delay = 0.5 # s self.query_sql = '' self.query_text = '' self.query_dict = {} self.db_idle_time = 60.0 # s self.search_results = [] self.tool_bar = None self.search_line_edit = None self.completer = None self.marker = QgsVertexMarker(iface.mapCanvas()) self.marker.setIconSize(15) self.marker.setPenWidth(2) self.marker.setColor(QColor(226, 27, 28)) #51,160,44)) self.marker.setZValue(11) self.marker.setVisible(False) self.marker2 = QgsVertexMarker(iface.mapCanvas()) self.marker2.setIconSize(16) self.marker2.setPenWidth(4) self.marker2.setColor(QColor(255, 255, 255, 200)) self.marker2.setZValue(10) self.marker2.setVisible(False) def initGui(self): # Create a new toolbar self.tool_bar = self.iface.addToolBar(u'Панель поиска') self.tool_bar.setObjectName('Discovery_sqlite_Plugin') # Add search edit box self.search_line_edit = QgsFilterLineEdit() self.search_line_edit.setSelectOnFocus(True) self.search_line_edit.setShowSearchIcon(True) self.search_line_edit.setPlaceholderText( u'Поиск адреса или участка...') # self.search_line_edit.setMaximumWidth(768) self.tool_bar.addWidget(self.search_line_edit) # loading indicator self.load_movie = QMovie() self.label_load = QLabel() self.tool_bar.addWidget(self.label_load) # Set up the completer model = QStandardItemModel() self.completer = QCompleter([]) # Initialise with en empty list self.completer.setCaseSensitivity(Qt.CaseInsensitive) self.completer.setMaxVisibleItems(30) self.completer.setModelSorting( QCompleter.UnsortedModel) # Sorting done in PostGIS self.completer.setCompletionMode(QCompleter.UnfilteredPopupCompletion ) # Show all fetched possibilities self.completer.setModel(model) tableView = QTableView() tableView.verticalHeader().setVisible(False) tableView.horizontalHeader().setVisible(False) tableView.setSelectionBehavior(QTableView.SelectRows) tableView.setShowGrid(False) # tableView.verticalHeader().setSectionResizeMode(QHeaderView.ResizeToContents) tableView.verticalHeader().setSectionResizeMode(QHeaderView.Fixed) fontsize = QFontMetrics( tableView.verticalHeader().font()).height() + 2 #font size tableView.verticalHeader().setDefaultSectionSize( fontsize) #font size 15 tableView.horizontalHeader().setSectionResizeMode( QHeaderView.ResizeToContents) tableView.horizontalHeader().setStretchLastSection(True) tableView.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.completer.setCompletionColumn(0) self.completer.setPopup(tableView) self.completer.activated[QModelIndex].connect(self.on_result_selected) self.completer.highlighted[QModelIndex].connect( self.on_result_highlighted) self.search_line_edit.setCompleter(self.completer) # Connect any signals self.search_line_edit.textEdited.connect(self.on_search_text_changed) self.search_line_edit.returnPressed.connect(self.returnPressed) self.read_config() # Search results self.search_results = [] # Set up a timer to periodically perform db queries as required self.db_timer.timeout.connect(self.schedule_search) def read_config(self): qstring = '' self.data = self.settings_path() try: for line in self.data[1:]: words = line.split(';') postgissearchcolumn = words[2].strip() postgistable = words[0].strip() geomcolumn = words[3].strip() layername = words[4].strip() isSelect = int(words[5].strip()) connection = sqlite3.connect(os.path.join(self.dbfile.strip())) cur = connection.cursor() qstring = u'select {2} from {0} where length({1})>0 LIMIT 1'.format( postgistable, postgissearchcolumn, geomcolumn) cur.execute(qstring) connection.close() self.make_enabled(True) # assume the config is invalid first except Exception as E: print(E) self.make_enabled(False) # включить или выключить поисковую строку в зависимости от результата проверки настроек def make_enabled(self, enabled): self.search_line_edit.setEnabled(enabled) self.search_line_edit.setPlaceholderText( u"Поиск адреса или участка..." if enabled else u"Поиск отключен: проверьте конфигурацию") def settings_path(self): p = os.path.join(self.plugin_dir, 'layers.ini') f = codecs.open(p, 'r', encoding='cp1251') data = f.readlines() dbfileline = data[0] if dbfileline[:2] == u'\\\\': self.dbfile = dbfileline elif dbfileline[1] == u':': self.dbfile = dbfileline else: self.dbfile = os.path.join(self.plugin_dir, dbfileline) f.close() return data def unload(self): self.db_timer.stop() self.db_timer.timeout.disconnect(self.schedule_search) self.completer.highlighted[QModelIndex].disconnect( self.on_result_highlighted) self.completer.activated[QModelIndex].disconnect( self.on_result_selected) self.search_line_edit.textEdited.disconnect( self.on_search_text_changed) self.search_line_edit.returnPressed.disconnect(self.returnPressed) self.tool_bar.clear() # Clear all actions self.iface.mainWindow().removeToolBar(self.tool_bar) def clear_suggestions(self): model = self.completer.model() model.clear() # model.setStringList([]) def returnPressed(self): if self.completer.popup().isHidden(): self.do_search(self.search_line_edit.text()) # def setLoading(self, isLoading): # if self.label_load is None: # return # if isLoading: # load_movie = QMovie() # load_movie.setFileName(self.icon_loading) # self.label_load.setMovie(load_movie) # load_movie.start() # else: # load_movie = QMovie() # load_movie.stop() # self.label_load.setMovie(load_movie) def schedule_search(self): if self.next_query_time is not None and self.next_query_time < time.time( ): self.next_query_time = None # Prevent this query from being repeated self.last_query_time = time.time() self.do_search(self.search_line_edit.text()) self.db_timer.stop() # self.setLoading(False) self.search_line_edit.setShowSpinner(False) else: # self.setLoading(True) self.search_line_edit.setShowSpinner(True) if time.time() > self.last_query_time + self.db_idle_time: self.db_conn = None # def on_search_text_changed(self, new_search_text): def on_search_text_changed(self, new_search_text): # self.setLoading(False) self.search_line_edit.setShowSpinner(False) if len(new_search_text) < 3: self.db_timer.stop() self.clear_suggestions() return self.db_timer.start(300) self.next_query_time = time.time() + self.search_delay def do_search(self, new_search_text): if len(new_search_text) < 3: self.clear_suggestions() return self.clear_suggestions() self.query_text = new_search_text self.search_results = [] self.suggestions = [] for index, line in enumerate(self.data[1:]): curline_layer = line words = curline_layer.split(';') searchcolumn = words[2].strip() # поле со значением для поиска postgistable = words[0].strip() # таблица geomcolumn = words[3].strip() # поле с геометрией layername = words[4].strip( ) # имя слоя в легенде для соответствий и выделения isSelect = int( words[5].strip()) # выделять ли объект в слое layername descript = words[1].strip( ) # описание. Выводится в списке результатов query_text, query_dict = self.get_search_sql( new_search_text, searchcolumn, postgistable) query_sql = query_text query_dict = query_dict self.perform_search(query_sql, query_dict, descript, postgistable, layername, isSelect, searchcolumn) # QStringList - просто одна строка в выводе # if len(self.suggestions) > 0: # model = self.completer.model() # model.setStringList(self.suggestions) # print(model) # self.completer.complete() if len(self.suggestions) > 0: # model = self.completer.model() model = QStandardItemModel() font = QFont() font.setItalic(True) font.setPointSize(7) # заполняем модель for i, line in enumerate(self.suggestions): #icon pixmap = QPixmap(':plugins/_Discovery_sqlite/icons/' + line[2] + '.png') pixmap = pixmap.scaledToHeight(10) pixmap = pixmap.scaledToWidth(10) # itemImage = QStandardItem() # itemImage.setData(pixmap, Qt.DecorationRole) # model.setItem(i, 0, itemImage) itemLayer = QStandardItem(u"{1}[{0}]".format( line[1], u' ' * 50)) itemLayer.setFont(font) itemValue = QStandardItem(line[0]) itemValue.setData(pixmap, Qt.DecorationRole) model.setItem(i, 0, itemValue) model.setItem(i, 1, itemLayer) self.completer.setModel(model) self.completer.complete() else: model = self.completer.model() # self.suggestions.append(u"<Не найдено>") # для QStringList # model.setStringList(self.suggestions) # для QStringList model.setItem( 0, 0, QStandardItem('<Не найдено>')) # для QStandardItemModel self.completer.complete() def perform_search(self, query_sql, query_dict, descript, tablename, layername, isSelect, searchcolumn): cur = self.get_db_cur() cur.execute(query_sql, query_dict) for row in cur.fetchall(): geom, suggestion_text = row[0], row[1] self.search_results.append(geom) self.suggestions.append([ suggestion_text, descript, tablename, layername, isSelect, searchcolumn ]) # self.suggestions.append(suggestion_text) # для QStringList def get_search_sql(self, search_text, search_column, table): wildcarded_search_string = '' for part in search_text.split(): wildcarded_search_string += '%' + part #.lower() wildcarded_search_string += '%' wildcarded_search_string = wildcarded_search_string query_dict = {'search_text': wildcarded_search_string} # wildcarded_search_string = wildcarded_search_string.encode('cp1251') query_text = u"SELECT WKT_GEOMETRY AS geom, {0} AS suggestion_string FROM {1} WHERE ({0}) LIKE '{2}' ORDER BY {0} LIMIT 1000".format( search_column, table, wildcarded_search_string) # query_text = query_text.decode('cp1251') return query_text, query_dict def on_result_selected(self, result_index): resultIndexRow = result_index.row() if len(self.search_results) < 1: self.search_line_edit.setPlaceholderText(u'') return # What to do when the user makes a selection geometry_text = self.search_results[resultIndexRow] location_geom = QgsGeometry.fromWkt(geometry_text) canvas = self.iface.mapCanvas() # dst_srid = canvas.mapRenderer().destinationCrs().authid() # Ensure the geometry from the DB is reprojected to the same SRID as the map canvas location_centroid = location_geom.centroid().asPoint() result_text = self.completer.completionModel().index( resultIndexRow, 0).data() if self.suggestions[resultIndexRow][2] in ( u"adres_nd") and location_geom.type() == 0: # point self.show_marker(location_centroid) self.iface.mapCanvas().setExtent(location_geom.boundingBox()) self.iface.mapCanvas().zoomScale(1000) layer_build = self.find_layer(u"Здания") if layer_build != None: layer_build.selectByIds([]) for feat in layer_build.getFeatures( QgsFeatureRequest().setFilterRect( QgsRectangle(self.iface.mapCanvas().extent()))): if location_geom.intersects(feat.geometry()): # self.show_marker_feature(feat.geometry()) self.iface.setActiveLayer(layer_build) layer_build.selectByIds([feat.id()]) layer_build.triggerRepaint() return else: #not point layername = self.suggestions[resultIndexRow][3] isSelect = self.suggestions[resultIndexRow][4] searchcolumn = self.suggestions[resultIndexRow][5] box = location_geom.boundingBox() if box.height() > box.width(): max = box.height() else: max = box.width() box.grow(max * 0.10) self.iface.mapCanvas().setExtent(box) if isSelect == 1: selLayer = self.find_layer(layername) if selLayer is not None: for feat in selLayer.getFeatures( QgsFeatureRequest().setFilterRect(box)): # print(feat[searchcolumn], str(result_text).strip()) try: if str(feat[searchcolumn]) == str( result_text).strip(): self.iface.setActiveLayer(selLayer) selLayer.selectByIds([feat.id()]) selLayer.triggerRepaint() break except Exception as E: print(E) break self.show_marker_feature(location_geom) canvas.refresh() self.line_edit_timer.start(0) # self.db_timer.stop() def get_db_cur(self): # Create a new new connection if required if self.db_conn is None: self.db_conn = sqlite3.connect(os.path.join(self.dbfile.strip())) return self.db_conn.cursor() def on_result_highlighted(self, result_idx): self.line_edit_timer.start(0) def reset_line_edit_after_move(self): self.search_line_edit.setText(self.query_text) def find_layer(self, layer_name): for search_layer in self.iface.mapCanvas().layers(): if search_layer.name() == layer_name: return search_layer return None def show_marker(self, point): for m in [self.marker, self.marker2]: m.setCenter(point) m.setOpacity(1.0) m.setVisible(True) QTimer.singleShot(4000, self.hide_marker) def hide_marker(self): opacity = self.marker.opacity() if opacity > 0.: # produce a fade out effect opacity -= 0.1 self.marker.setOpacity(opacity) self.marker2.setOpacity(opacity) QTimer.singleShot(100, self.hide_marker) else: self.marker.setVisible(False) self.marker2.setVisible(False) def show_marker_feature(self, geom): if geom.type() == 2: #poly self.r = QgsRubberBand(iface.mapCanvas(), True) elif geom.type() == 1: #line self.r = QgsRubberBand(iface.mapCanvas(), False) self.r.setToGeometry(geom, None) self.r.setColor(QColor(255, 0, 0, 200)) self.r.setFillColor(QColor(255, 0, 0, 50)) self.r.setWidth(2) self.r.setZValue(9) QTimer.singleShot(4000, self.hide_marker_feature) def hide_marker_feature(self): opacity = self.r.opacity() if opacity > 0.: # produce a fade out effect opacity -= 0.1 self.r.setOpacity(opacity) QTimer.singleShot(100, self.hide_marker_feature) else: iface.mapCanvas().scene().removeItem(self.r)
class NewSessionDialog(ChildDialog): def __init__(self, parent, duplicate_window=False): ChildDialog.__init__(self, parent) self.ui = ui.new_session.Ui_DialogNewSession() self.ui.setupUi(self) self._is_duplicate = bool(duplicate_window) self.ui.currentSessionsFolder.setText(CommandLineArgs.session_root) self.ui.toolButtonFolder.clicked.connect(self._change_root_folder) self.ui.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False) self.ui.lineEdit.setFocus(Qt.OtherFocusReason) self.ui.lineEdit.textChanged.connect(self._text_changed) self.session_list = [] self.template_list = [] self.sub_folders = [] self.signaler.server_status_changed.connect( self._server_status_changed) self.signaler.add_sessions_to_list.connect(self._add_sessions_to_list) self.signaler.session_template_found.connect( self._add_templates_to_list) self.signaler.root_changed.connect(self._root_changed) self.to_daemon('/ray/server/list_sessions', 1) if self._is_duplicate: self.ui.labelTemplate.setVisible(False) self.ui.comboBoxTemplate.setVisible(False) self.ui.labelOriginalSessionName.setText( self.session.get_short_path()) self.ui.labelNewSessionName.setText( _translate('Duplicate', 'Duplicated session name :')) self.setWindowTitle(_translate('Duplicate', 'Duplicate Session')) else: self.ui.frameOriginalSession.setVisible(False) self.to_daemon('/ray/server/list_session_templates') if not self.daemon_manager.is_local: self.ui.toolButtonFolder.setVisible(False) self.ui.currentSessionsFolder.setVisible(False) self.ui.labelSessionsFolder.setVisible(False) self._init_templates_combo_box() self._set_last_template_selected() self._server_will_accept = False self._text_is_valid = False self._completer = QCompleter(self.sub_folders) self.ui.lineEdit.setCompleter(self._completer) self._server_status_changed(self.session.server_status) self._text_was_empty = True def _server_status_changed(self, server_status): self.ui.toolButtonFolder.setEnabled( bool(server_status == ray.ServerStatus.OFF)) self._server_will_accept = bool( server_status in (ray.ServerStatus.OFF, ray.ServerStatus.READY) and not self.server_copying) if self._is_duplicate: self._server_will_accept = bool( server_status == ray.ServerStatus.READY and not self.server_copying) if server_status != ray.ServerStatus.OFF: if self._root_folder_file_dialog is not None: self._root_folder_file_dialog.reject() self._root_folder_message_box.reject() self._prevent_ok() def _root_changed(self, session_root): self.ui.currentSessionsFolder.setText(session_root) self.session_list.clear() self.sub_folders.clear() self.to_daemon('/ray/server/list_sessions', 1) def _init_templates_combo_box(self): self.ui.comboBoxTemplate.clear() self.ui.comboBoxTemplate.addItem( _translate('session_template', "empty")) self.ui.comboBoxTemplate.addItem( _translate('session_template', "with JACK patch memory")) self.ui.comboBoxTemplate.addItem( _translate('session_template', "with JACK config memory")) self.ui.comboBoxTemplate.addItem( _translate('session_template', "with basic scripts")) misscount = self.ui.comboBoxTemplate.count() \ - 1 - len(ray.FACTORY_SESSION_TEMPLATES) for i in range(misscount): self.ui.comboBoxTemplate.addItem(ray.FACTORY_SESSION_TEMPLATES[-i]) self.ui.comboBoxTemplate.insertSeparator( len(ray.FACTORY_SESSION_TEMPLATES) + 1) def _set_last_template_selected(self): last_used_template = RS.settings.value('last_used_template', type=str) if last_used_template.startswith('///'): last_factory_template = last_used_template.replace('///', '', 1) for i in range(len(ray.FACTORY_SESSION_TEMPLATES)): factory_template = ray.FACTORY_SESSION_TEMPLATES[i] if factory_template == last_factory_template: self.ui.comboBoxTemplate.setCurrentIndex(i + 1) break else: if last_used_template in self.template_list: self.ui.comboBoxTemplate.setCurrentText(last_used_template) if not last_used_template: self.ui.comboBoxTemplate.setCurrentIndex(1) def _set_last_sub_folder_selected(self): last_subfolder = '' for sess in self.session.recent_sessions: if sess.startswith('/'): continue if '/' in sess: last_subfolder, sep, sess_name = sess.rpartition('/') break if last_subfolder and not self.ui.lineEdit.text(): self.ui.lineEdit.setText(last_subfolder + '/') def _add_sessions_to_list(self, session_names): self.session_list += session_names for session_name in session_names: if '/' in session_name: new_dir = os.path.dirname(session_name) if not new_dir in self.sub_folders: self.sub_folders.append(new_dir) self.sub_folders.sort() del self._completer self._completer = QCompleter([f + '/' for f in self.sub_folders]) self.ui.lineEdit.setCompleter(self._completer) if not session_names: # all sessions are listed, pre-fill last subfolder self._set_last_sub_folder_selected() def _add_templates_to_list(self, template_list): for template in template_list: if template not in self.template_list: self.template_list.append(template) if not self.template_list: return self.template_list.sort() self._init_templates_combo_box() for template_name in self.template_list: self.ui.comboBoxTemplate.addItem(template_name) self._set_last_template_selected() def _text_changed(self, text): self._text_is_valid = bool(text and not text.endswith('/') and text not in self.session_list) self._prevent_ok() if self._text_was_empty: if text: self._completer.setCompletionMode(QCompleter.PopupCompletion) self._completer.complete() self._text_was_empty = False elif not text: QTimer.singleShot(50, self._set_completer_for_empty_text) self._text_was_empty = True def _set_completer_for_empty_text(self): #print('set currow', self._completer.setCurrentIndex(0)) del self._completer self._completer = QCompleter([f + '/' for f in self.sub_folders]) self._completer.setCompletionMode(QCompleter.UnfilteredPopupCompletion) self.ui.lineEdit.setCompleter(self._completer) QTimer.singleShot(50, self._completer.complete) def _show_completer_at_start(self): self._completer.complete() def showEvent(self, event): ChildDialog.showEvent(self, event) QTimer.singleShot(800, self._show_completer_at_start) def _prevent_ok(self): self.ui.buttonBox.button(QDialogButtonBox.Ok).setEnabled( bool(self._server_will_accept and self._text_is_valid)) def get_session_short_path(self) -> str: return self.ui.lineEdit.text() def get_template_name(self) -> str: index = self.ui.comboBoxTemplate.currentIndex() if index == 0: return "" if index <= len(ray.FACTORY_SESSION_TEMPLATES): return '///' + ray.FACTORY_SESSION_TEMPLATES[index - 1] return self.ui.comboBoxTemplate.currentText()
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 GuiApplication(QApplication, BaseApplication): # TODO: figure out how to catch close/delete window from window frame def __init__(self, *args, **kw): QApplication.__init__(self, *args, **kw) BaseApplication.__init__(self) self.view = qtutils.create_form( "main.ui", opengl={"graphicsView": ChimeraGraphics}, connections={ "actionOpen.triggered": self.open, "actionQuit.triggered": self.quit, "lineEdit.textChanged": self.save_command, "lineEdit.returnPressed": self.process_command, "graphicsViewGL.mousePress": self.mouse_press, "graphicsViewGL.mouseRelease": self.mouse_release, "graphicsViewGL.mouseMove": self.mouse_drag, # TODO: why are't these needed? #"graphicsViewGL.keyPress": "lineEdit.event", #"graphicsViewGL.keyRelease": "lineEdit.event", }) self.view.setWindowTitle(self.applicationName()) self.statusbar = self.find_object("statusbar") assert self.statusbar is not None self.graphics = self.find_object("graphicsViewGL") assert self.graphics is not None self.graphics.makeCurrent() self.graphics.setFocusPolicy(Qt.WheelFocus) self.line_edit = self.find_object("lineEdit") assert self.line_edit is not None self.completer = QCompleter(self.line_edit) self.completer.setModel(QStringListModel(self.completer)) #self.completer.setCompletionMode(QCompleter.PopupCompletion) self.line_edit.setCompleter(self.completer) self._mouse_mode = None self.view.show() self.cursors = { # TODO: custom cursors "pick": Qt.PointingHandCursor, "vsphere_z": Qt.IBeamCursor, "vsphere_rot": Qt.ClosedHandCursor, "translate": Qt.SizeAllCursor, } self.timer = QTimer(self.view) self.active_timer = False from chimera2.trackchanges import track from chimera2 import scene track.add_handler(scene.View, self._update_cb) def _update_cb(self, *args, **kw): if self.graphics: self.graphics.updateGL() def physicalDotsPerInch(self): screen = self.primaryScreen() return screen.physicalDotsPerInch() def find_object(self, name): return self.view.findChild(QObject, name) @pyqtSlot() def open(self): # QFileDialog.getOpenFileName(QWidget parent=None, str caption='', str directory='', str filter='', str initialFilter='', QFileDialog.Options options=0) -> (str, str) from chimera2 import io filename, filter = QFileDialog.getOpenFileName( self.view, caption="Open File", filter=io.qt_open_file_filter()) if filename: from chimera2 import commands commands.cmd_open(filename) @property def mouse_mode(self): return self._mouse_mode @mouse_mode.setter def mouse_mode(self, mode): if mode == self._mouse_mode: return self._mouse_mode = mode cursor = self.cursors.get(mode, None) if cursor: self.graphics.setCursor(cursor) else: self.graphics.setCursor(QCursor()) @pyqtSlot(QEvent) def mouse_press(self, event): buttons = event.buttons() x = event.x() y = event.y() if buttons & Qt.RightButton: self.graphics.pick(x, y) self.mouse_mode = "pick" elif buttons & Qt.MiddleButton: self.mouse_mode = "translate" self.xy = event.globalPos() elif buttons & Qt.LeftButton: zrot = self.graphics.vsphere_press(x, y) if zrot: self.mouse_mode = "vsphere_z" else: self.mouse_mode = "vsphere_rot" @pyqtSlot(QEvent) def mouse_release(self, event): if self.mouse_mode in ("vsphere_z", "vsphere_rot"): self.graphics.vsphere_release() self.mouse_mode = None @pyqtSlot(QEvent) def mouse_drag(self, event): if self.mouse_mode in ("vsphere_z", "vsphere_rot"): x = event.x() y = event.y() throttle = event.modifiers() & Qt.ShiftModifier zrot = self.graphics.vsphere_drag(x, y, throttle) if zrot: self.mouse_mode = "vsphere_z" else: self.mouse_mode = "vsphere_rot" elif self.mouse_mode == "translate": xy = event.globalPos() delta = xy - self.xy self.xy = xy self.graphics.translate_xy(delta) @pyqtSlot(str) def save_command(self, text): self.command.parse_text(text) self.completer.setCompletionPrefix(self.command.completion_prefix) self.completer.model().setStringList(self.command.completions) self.completer.complete() @pyqtSlot() def process_command(self, cmd=None): self.status("") BaseApplication.process_command(self)
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 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)