def build_filter_display(self): """Creates Filter UI""" filter_txt = QLineEdit(self) filter_txt.setReadOnly(False) filter_txt.setText('date:today') filter_txt.setFocus() filter_txt.textChanged.connect(self.filter_notes) filter_txt.returnPressed.connect(self.filter_notes) filter_txt.setStyleSheet("border: 0px;") filter_txt.setToolTip("Filter Input") self.filter_txt = filter_txt completer = QCompleter( self.connection_manager.get_primary_connector().get_common_words()) completer.setCompletionMode(QCompleter.PopupCompletion) completer.setCaseSensitivity(Qt.CaseInsensitive) completer.setModelSorting(QCompleter.CaseInsensitivelySortedModel) completer.popup().setStyleSheet(""" QListView { background-color: #272822; color: #aaa; selection-background-color: #511; } """) filter_txt.setCompleter(completer)
class MainWindow(QMainWindow): def __init__(self, parent=None): super(MainWindow, self).__init__(parent) self.createMenu() self.completingTextEdit = TextEdit() self.completer = QCompleter(self) self.completer.setModel(self.modelFromFile(':/resources/wordlist.txt')) self.completer.setModelSorting(QCompleter.CaseInsensitivelySortedModel) self.completer.setCaseSensitivity(Qt.CaseInsensitive) self.completer.setWrapAround(False) self.completingTextEdit.setCompleter(self.completer) self.setCentralWidget(self.completingTextEdit) self.resize(500, 300) self.setWindowTitle("Completer") def createMenu(self): exitAction = QAction("Exit", self) aboutAct = QAction("About", self) aboutQtAct = QAction("About Qt", self) exitAction.triggered.connect(QApplication.instance().quit) aboutAct.triggered.connect(self.about) aboutQtAct.triggered.connect(QApplication.instance().aboutQt) fileMenu = self.menuBar().addMenu("File") fileMenu.addAction(exitAction) helpMenu = self.menuBar().addMenu("About") helpMenu.addAction(aboutAct) helpMenu.addAction(aboutQtAct) def modelFromFile(self, fileName): f = QFile(fileName) if not f.open(QFile.ReadOnly): return QStringListModel(self.completer) QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) words = [] while not f.atEnd(): line = f.readLine().trimmed() if line.length() != 0: try: line = str(line, encoding='ascii') except TypeError: line = str(line) words.append(line) QApplication.restoreOverrideCursor() return QStringListModel(words, self.completer) def about(self): QMessageBox.about(self, "About", "This example demonstrates the different features of the " "QCompleter class.")
def __init__(self): super().__init__() completer = QCompleter(Context.cardboard_names) completer.setCaseSensitivity(Qt.CaseInsensitive) completer.setFilterMode(Qt.MatchStartsWith) completer.setModelSorting(QCompleter.CaseInsensitivelySortedModel) completer.setCompletionMode(QCompleter.InlineCompletion) self.setCompleter(completer)
class LineEdit(QLineEdit): def __init__(self, parent=None): super().__init__(parent) self.completer = QCompleter(g.insModel.getInstrumentsName()) self.completer.setModelSorting(QCompleter.CaseInsensitivelySortedModel) self.completer.setCaseSensitivity(Qt.CaseInsensitive) self.completer.setWrapAround(True) self.setCompleter(self.completer)
class Completer(object): """Comleter class to use in the query text editor.""" # ---------------------------------------------------------------------- def __init__(self): """Initialize Completer class with the keywords and functions.""" with io.open( r'completer_data\keywords.txt', 'r', encoding='utf-8') as f: lowercase_keywords = [k.rstrip().lower() for k in f.readlines()] uppercase_keywords = [k.upper() for k in lowercase_keywords] titlecase_keywords = [k.title() for k in lowercase_keywords] with io.open( r'completer_data\functions.txt', 'r', encoding='utf-8') as f: titlecase_funcs = [f.rstrip() for f in f.readlines()] uppercase_funcs = [f.upper() for f in titlecase_funcs] lowercase_funcs = [f.lower() for f in titlecase_funcs] all_keywords_and_funcs = [ lowercase_keywords, uppercase_keywords, titlecase_keywords, lowercase_funcs, uppercase_funcs, titlecase_funcs, ] self.standard_items = [ keyword for sublist in all_keywords_and_funcs for keyword in sublist ] self.completer = QCompleter(self.standard_items) self.completer.setModelSorting(QCompleter.CaseInsensitivelySortedModel) self.completer.setCaseSensitivity(Qt.CaseInsensitive) self.completer.setWrapAround(False) return # ---------------------------------------------------------------------- def update_completer_string_list(self, items): """Update completer string list to include additional strings. The list of additional strings include geodatabase items. """ cur_items = [] titlecase_items = [i.title() for i in items] uppercase_items = [i.upper() for i in items] lowercase_items = [i.lower() for i in items] cur_items.extend(self.standard_items) cur_items.extend(titlecase_items + uppercase_items + lowercase_items) self.completer.model().setStringList(cur_items) return
class PostSelectionWidget(QDialog): def __init__(self, app, parent=None): super(PostSelectionWidget, self).__init__(parent=parent) self.app = app self.model = PostsListModel(app) self.filtermodel = QSortFilterProxyModel() self.filtermodel.setSourceModel(self.model) self.resize(450, 250) self.layout = QVBoxLayout() self.setLayout(self.layout) view = QListView(self) self.completer = QCompleter(parent=self) self.completer.setCompletionMode(QCompleter.InlineCompletion) self.completer.setCompletionColumn(0) self.completer.setCompletionRole(Qt.DisplayRole) self.completer.setCaseSensitivity(Qt.CaseInsensitive) self.completer.setModelSorting(QCompleter.CaseInsensitivelySortedModel) self.completer.setModel(self.model) self._edit = QLineEdit('') self._edit.setCompleter(self.completer) self._edit.textEdited.connect(self.setfilter) self.layout.addWidget(self._edit) self.layout.addWidget(view) view.setModel(self.filtermodel) view.clicked.connect(self.select) view.doubleClicked.connect(self.select_and_edit) self._button = QPushButton('Edit') self._button.clicked.connect(self.edit) self.layout.addWidget(self._button) def setfilter(self, text): regexp = QRegExp("%s*" % text, Qt.CaseInsensitive, QRegExp.Wildcard) self.filtermodel.setFilterRegExp(regexp) def select(self, index): text = self.filtermodel.data(index) self._edit.setText(text) def select_and_edit(self, index): text = self.filtermodel.data(index) self._edit.setText(text) self.app.edit_post(text) def edit(self): self.app.edit_post(self._edit.text())
def __init__(self, namespace, parent): """Constructor. Args: namespace: The local namespace of the interpreter. """ super().__init__(parent) self.update_font() objreg.get('config').changed.connect(self.update_font) self.textChanged.connect(self.on_text_changed) self._rlcompleter = rlcompleter.Completer(namespace) qcompleter = QCompleter(self) self._model = QStringListModel(qcompleter) qcompleter.setModel(self._model) qcompleter.setCompletionMode(QCompleter.UnfilteredPopupCompletion) qcompleter.setModelSorting(QCompleter.CaseSensitivelySortedModel) self.setCompleter(qcompleter) self._history = cmdhistory.History() self.returnPressed.connect(self.on_return_pressed)
def __init__(self, namespace, parent): """Constructor. Args: namespace: The local namespace of the interpreter. """ super().__init__(parent) self.update_font() objreg.get('config').changed.connect(self.update_font) self.textChanged.connect(self.on_text_changed) self._rlcompleter = rlcompleter.Completer(namespace) qcompleter = QCompleter(self) self._model = QStringListModel(qcompleter) qcompleter.setModel(self._model) qcompleter.setCompletionMode( QCompleter.UnfilteredPopupCompletion) qcompleter.setModelSorting( QCompleter.CaseSensitivelySortedModel) self.setCompleter(qcompleter) self._history = cmdhistory.History() self.returnPressed.connect(self.on_return_pressed)
def insertFromPath(self): dialogue = QDialog(self) layout = QHBoxLayout() dialogue.setLayout(layout) editor = QLineEdit() layout.addWidget(editor) completer = QCompleter() completer.setCompletionMode(QCompleter.UnfilteredPopupCompletion) completer.setCaseSensitivity(Qt.CaseInsensitive) completer.setFilterMode(Qt.MatchContains) completer.setModelSorting(QCompleter.CaseInsensitivelySortedModel) editor.setCompleter(completer) model = QStringListModel() completion_paths = self.hive_finder.all_hives model.setStringList(completion_paths) completer.setModel(model) def on_return(): widget = self.tab_widget.currentWidget() if not isinstance(widget, NodeEditorSpace): return reference_path = editor.text() widget.addNodeAtMouse(reference_path, NodeTypes.HIVE) dialogue.close() editor.returnPressed.connect(on_return) dialogue.setWindowTitle("Add Hive From Path") dialogue.setAttribute(Qt.WA_DeleteOnClose) dialogue.exec_()
class MainWindow(QMainWindow): def __init__(self, parent=None): super(MainWindow, self).__init__(parent) self.createMenu() self.completingTextEdit = TextEdit() self.completer = QCompleter(self) self.completer.setModel(self.modelFromFile(':/resources/wordlist.txt')) self.completer.setModelSorting(QCompleter.CaseInsensitivelySortedModel) self.completer.setCaseSensitivity(Qt.CaseInsensitive) self.completer.setWrapAround(False) self.completingTextEdit.setCompleter(self.completer) self.setCentralWidget(self.completingTextEdit) self.resize(500, 300) self.setWindowTitle("Completer") def createMenu(self): exitAction = QAction("Exit", self) aboutAct = QAction("About", self) aboutQtAct = QAction("About Qt", self) exitAction.triggered.connect(QApplication.instance().quit) aboutAct.triggered.connect(self.about) aboutQtAct.triggered.connect(QApplication.instance().aboutQt) fileMenu = self.menuBar().addMenu("File") fileMenu.addAction(exitAction) helpMenu = self.menuBar().addMenu("About") helpMenu.addAction(aboutAct) helpMenu.addAction(aboutQtAct) def modelFromFile(self, fileName): f = QFile(fileName) if not f.open(QFile.ReadOnly): return QStringListModel(self.completer) QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) words = [] while not f.atEnd(): line = f.readLine().trimmed() if line.length() != 0: try: line = str(line, encoding='ascii') except TypeError: line = str(line) words.append(line) QApplication.restoreOverrideCursor() return QStringListModel(words, self.completer) def about(self): QMessageBox.about( self, "About", "This example demonstrates the different features of the " "QCompleter class.")
class PairCreatorWindow(QDialog): """ Dialog window for creating a student pair. """ def __init__(self, number, parent: QWidget = None): super().__init__(parent) self._number: int = number self._edit_pair: StudentPair = None self._dates: DatePair = DatePair() # window settings self.setWindowFlag(Qt.WindowContextHelpButtonHint, False) self.setWindowTitle(self.tr("Creator")) self.setMinimumSize(640, 350) self.resize(800, 400) # general settings window self.group_box_general = QGroupBox(self.tr("General")) self.layout_general = QFormLayout(self.group_box_general) # title self.label_title = QLabel(self.tr("Title")) self.layout_general.setWidget(0, QFormLayout.LabelRole, self.label_title) self.line_edit_title = QLineEdit("") self.layout_general.setWidget(0, QFormLayout.FieldRole, self.line_edit_title) # lecturer self.label_lecturer = QLabel(self.tr("Lecturer")) self.layout_general.setWidget(1, QFormLayout.LabelRole, self.label_lecturer) self.line_edit_lecturer = QLineEdit("") self.layout_general.setWidget(1, QFormLayout.FieldRole, self.line_edit_lecturer) self.completer = QCompleter(LecturerPair.get_lecturers()) self.completer.setModelSorting(QCompleter.CaseSensitivelySortedModel) self.completer.setCaseSensitivity(Qt.CaseInsensitive) self.completer.setFilterMode(Qt.MatchContains) self.line_edit_lecturer.setCompleter(self.completer) # type self.label_type = QLabel(self.tr("Type")) self.layout_general.setWidget(2, QFormLayout.LabelRole, self.label_type) self.combo_box_type = QComboBox() self.layout_general.setWidget(2, QFormLayout.FieldRole, self.combo_box_type) for name, attrib in TypePairAttrib.items(): self.combo_box_type.addItem(name, attrib) # classes self.label_classes = QLabel(self.tr("Classes")) self.layout_general.setWidget(3, QFormLayout.LabelRole, self.label_classes) self.line_edit_classes = QLineEdit("") self.layout_general.setWidget(3, QFormLayout.FieldRole, self.line_edit_classes) # subgroup self.label_subgroup = QLabel(self.tr("Subgroup")) self.layout_general.setWidget(4, QFormLayout.LabelRole, self.label_subgroup) self.combo_box_subgroup = QComboBox() self.layout_general.setWidget(4, QFormLayout.FieldRole, self.combo_box_subgroup) for name, attrib in SubgroupPairAttrib.items(): self.combo_box_subgroup.addItem(name, attrib) # time setting self.group_box_time = QGroupBox(self.tr("Time")) self.layout_time = QFormLayout(self.group_box_time) self.label_start = QLabel(self.tr("Start")) self.layout_time.setWidget(0, QFormLayout.LabelRole, self.label_start) self.combo_box_start = QComboBox() self.layout_time.setWidget(0, QFormLayout.FieldRole, self.combo_box_start) self.label_end = QLabel(self.tr("End")) self.layout_time.setWidget(1, QFormLayout.LabelRole, self.label_end) self.combo_box_end = QComboBox() self.layout_time.setWidget(1, QFormLayout.FieldRole, self.combo_box_end) self.combo_box_start.addItems(TimePair.time_starts()) self.combo_box_start.setCurrentIndex(self._number) self.combo_box_end.addItems(TimePair.time_ends()) self.combo_box_end.setCurrentIndex(self._number) # date setting self.group_box_date = QGroupBox(self.tr("Date")) self.layout_date_edit = QHBoxLayout(self.group_box_date) self.list_widget_date = QListWidget(self.group_box_date) self.layout_date_edit.addWidget(self.list_widget_date) self.layout_date_edit_navigate = QVBoxLayout() self.layout_date_edit.addLayout(self.layout_date_edit_navigate) self.push_button_add_date = QPushButton(self.tr("Add")) self.layout_date_edit_navigate.addWidget(self.push_button_add_date) self.push_button_edit_date = QPushButton(self.tr("Edit")) self.layout_date_edit_navigate.addWidget(self.push_button_edit_date) self.push_button_remove_date = QPushButton(self.tr("Remove")) self.layout_date_edit_navigate.addWidget(self.push_button_remove_date) self.layout_date_edit_navigate.addStretch(1) # navigate self.layout_navigate = QHBoxLayout() self.layout_navigate.addStretch(1) self.push_button_ok = QPushButton(self.tr("OK")) self.layout_navigate.addWidget(self.push_button_ok) self.push_button_cancel = QPushButton(self.tr("Cancel")) self.layout_navigate.addWidget(self.push_button_cancel) # layout settings self.layout_general_time = QVBoxLayout() self.layout_general_time.addWidget(self.group_box_general) self.layout_general_time.addWidget(self.group_box_time) self.layout_center = QHBoxLayout() self.layout_center.addLayout(self.layout_general_time) self.layout_center.addWidget(self.group_box_date) self.layout_main = QVBoxLayout() self.layout_main.addLayout(self.layout_center) self.layout_main.addLayout(self.layout_navigate) self.setLayout(self.layout_main) # connection self.combo_box_start.currentIndexChanged.connect( self.combo_box_start_changed) self.list_widget_date.itemDoubleClicked.connect( self.push_button_edit_date_clicked) self.push_button_add_date.clicked.connect( self.push_button_add_date_clicked) self.push_button_edit_date.clicked.connect( self.push_button_edit_date_clicked) self.push_button_remove_date.clicked.connect( self.push_button_remove_date_clicked) self.push_button_ok.clicked.connect(self.push_button_ok_clicked) self.push_button_cancel.clicked.connect( self.push_button_cancel_clicked) def save(self) -> bool: """ Saves the created / edited a student pair. Returns True/False depending on whether the save was successful or not. """ title = self.line_edit_title.text().strip() if title == "": QMessageBox.information(self, self.tr("Information"), self.tr("Title field is empty")) return False lecturer = self.line_edit_lecturer.text().strip() if lecturer == "": QMessageBox.information(self, self.tr("Information"), self.tr("Lecturer field is empty")) return False classes = self.line_edit_classes.text().strip() if classes == "": QMessageBox.information(self, self.tr("Information"), self.tr("Classes field is empty")) return False pair_type = self.combo_box_type.currentData(Qt.UserRole) subgroup = self.combo_box_subgroup.currentData(Qt.UserRole) start_time = self.combo_box_start.currentText() end_time = self.combo_box_end.currentText() if self.list_widget_date.count() == 0: QMessageBox.information(self, self.tr("Information"), self.tr("No dates")) return False new_pair = StudentPair() new_pair["title"].set_title(title) new_pair["lecturer"].set_lecturer(lecturer) new_pair["type"].set_type(pair_type) new_pair["classroom"].set_classroom(classes) new_pair["subgroup"].set_subgroup(subgroup) new_pair["time"].set_time(start_time, end_time) for date in self._dates: new_pair["dates"].add_date(date) self._edit_pair = new_pair return True def set_pair(self, pair: StudentPair) -> None: """ Method to set the pair to edit. :param pair: Pair """ self._edit_pair = pair self.line_edit_title.setText(str(self._edit_pair["title"])) self.line_edit_lecturer.setText(str(self._edit_pair["lecturer"])) self.combo_box_type.setCurrentText(str(self._edit_pair["type"])) self.line_edit_classes.setText(str(self._edit_pair["classroom"])) self.combo_box_subgroup.setCurrentText(str( self._edit_pair["subgroup"])) time: TimePair = self._edit_pair["time"] if time is not None: number = time.get_number() self.combo_box_start.setCurrentIndex(number) self.combo_box_end.clear() self.combo_box_end.addItems(TimePair.time_ends()[number:]) self.combo_box_end.setCurrentIndex(time.duration() - 1) self._dates = self._edit_pair["dates"] self.update_list_widget_date() def get_pair(self) -> (StudentPair, None): """ Returns an editable pair. """ return self._edit_pair def push_button_add_date_clicked(self) -> None: """ Slot for add date button. """ date_creator = DateCreatorWindow(self) while True: date_creator.exec_() create_date = date_creator.get_date() if create_date is not None: try: self._dates.add_date(create_date) self.update_list_widget_date() break except InvalidDatePair as ex: QMessageBox.critical(self, self.tr("Invalid date pair"), str(ex)) else: break def push_button_edit_date_clicked(self) -> None: """ Slot for edit date button. """ item = self.list_widget_date.currentItem() if item is None: QMessageBox.information(self, self.tr("Information"), self.tr("No date selected")) return original_date = item.data(Qt.UserRole) self._dates.remove_date(item.data(Qt.UserRole)) date_editor = DateCreatorWindow(self) date_editor.set_date(original_date.copy()) while True: date_editor.exec_() edit_date = date_editor.get_date() if edit_date is not None: try: self._dates.add_date(edit_date) self.update_list_widget_date() break except InvalidDatePair as ex: QMessageBox.critical(self, self.tr("Invalid date pair"), str(ex)) else: self._dates.add_date(original_date) break def push_button_remove_date_clicked(self) -> None: """ Slot for remove date button. """ item = self.list_widget_date.currentItem() if item is None: QMessageBox.information(self, self.tr("Information"), self.tr("No date selected")) return self._dates.remove_date(item.data(Qt.UserRole)) self.update_list_widget_date() def update_list_widget_date(self) -> None: """ Updates the list of dates in the window. """ self.list_widget_date.clear() for date in self._dates: item = QListWidgetItem(str(date)) item.setData(Qt.UserRole, date) self.list_widget_date.addItem(item) def push_button_ok_clicked(self) -> None: """ Slot for ok button. """ if self.save(): self.close() def push_button_cancel_clicked(self) -> None: """ Slot for cancel button. """ self._edit_pair = None self.close() def combo_box_start_changed(self, index) -> None: """ Slot to change the end time of the pair. """ old_index = self.combo_box_end.currentIndex() self.combo_box_end.clear() self.combo_box_end.addItems(TimePair.time_ends()[index:])
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 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 mainGUI(QMainWindow): def __init__(self, parent=None): # super().__init__() # QMainWindow.__init__(self, None) super(mainGUI, self).__init__(parent) self.initUI() def initUI(self): # -------- Window Settings ------------------------------------------- # self.setGeometry(100,100,700,700) self.setFixedSize(700, 700) self.setWindowTitle("AutoComplete Sentence Tool") # self.show() # -------- Status Bar ------------------------------------------- self.status = self.statusBar() # -------- Sentence List ------------------------------------------- self.sentencelabel = QLabel(self) self.sentencelabel.setText('Sentence List') self.sentencelabel.setStyleSheet("color: blue;" "font: bold 18pt 'Times New Roman';") self.sentencelabel.setAlignment(Qt.AlignCenter) self.sentencelabel.setGeometry(50, 70, 400, 30) # self.sentencelist = QTextEdit(self) font = QFont() font.setFamily('Courier') font.setFixedPitch(True) font.setPointSize(10) self.sentencelist = TextEdit(self) # self.sentencelist.setFont(font) self.sentencelist.setStyleSheet("color: rgb(255, 0, 0);") self.completer = QCompleter(self) self.completer.setModel(self.modelFromFile(':/resources/wordlist.txt')) self.completer.setModelSorting(QCompleter.CaseInsensitivelySortedModel) self.completer.setCaseSensitivity(Qt.CaseInsensitive) self.completer.setWrapAround(False) self.sentencelist.setCompleter(self.completer) self.sentencelist.setGeometry(50, 100, 400, 550) # self.sentencelist.textChanged.connect(self.updateSentenceList) # -------- Word List ------------------------------------------- self.wordlabel = QLabel(self) self.wordlabel.setText('Word List') self.wordlabel.setStyleSheet("color: blue;" "font: bold 18pt 'Times New Roman';") self.wordlabel.setAlignment(Qt.AlignCenter) self.wordlabel.setGeometry(500, 70, 150, 30) self.wordlist = QTextEdit(self) self.wordlist.setGeometry(500, 100, 150, 550) infile = open('resources/wordlist.txt', 'r') self.wordlist.setPlainText(infile.read()) infile.close() self.wordlist.textChanged.connect(self.updateWordsList) self.words = self.wordlist.document().toPlainText() keywordPatterns = self.words.split('\n') while '' in keywordPatterns: keywordPatterns.remove('') for i, wd in enumerate(keywordPatterns): keywordPatterns[i] = "\\b" + keywordPatterns[i] + "\\b" self.highlighter = Highlighter(keywordPatterns, self.sentencelist.document()) # -------- Statusbar ------------------------------------------- self.status = self.statusBar() self.sentencelist.cursorPositionChanged.connect(self.CursorPosition) def updateWordsList(self): self.words = self.wordlist.document().toPlainText() outfile = open('resources/wordlist.txt', 'w') outfile.write(self.words) outfile.close() # print(self.words.split('\n')) self.completer.setModel(self.modelFromFile(':/resources/wordlist.txt')) keywordPatterns = self.words.split('\n') while '' in keywordPatterns: keywordPatterns.remove('') for i, wd in enumerate(keywordPatterns): keywordPatterns[i] = "\\b" + keywordPatterns[i].upper() + "\\b" self.highlighter.updateKeywordPatterns(keywordPatterns) def updateSentenceList(self): self.sentences = self.sentencelist.document().toPlainText() if len(self.sentences.split('\n')): for sentence in self.sentences.split('\n'): pass def modelFromFile(self, fileName): f = QFile(fileName) if not f.open(QFile.ReadOnly): return QStringListModel(self.completer) QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) words = [] while not f.atEnd(): line = f.readLine().trimmed() if line.length() != 0: try: line = str(line, encoding='ascii') except TypeError: line = str(line) words.append(line) QApplication.restoreOverrideCursor() return QStringListModel(words, self.completer) def CursorPosition(self): line = self.sentencelist.textCursor().blockNumber() col = self.sentencelist.textCursor().columnNumber() linecol = ("Line: " + str(line) + " | " + "Column: " + str(col)) self.status.showMessage(linecol)
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 TabController(QWidget): # Adding signal catCreated = pyqtSignal(Tag) catClicked = pyqtSignal(Tag) catUpdated = pyqtSignal(Tag) childClicked = pyqtSignal(str) def __init__(self, parent, docker, window, editSpace_theme='light'): super(QWidget, self).__init__(parent) self.layout = QVBoxLayout(self) self.editSpace = None # Used for displaying source code self.graphview = None # Used for the graphical display self.aiml = None # THIS SHOULD BE THE ONLY MODEL IN THE SYSTEM self.docker = docker self.window = window self.up_to_date = True self.editSpace_theme = editSpace_theme # Initialize tab screen self.tabs = QTabWidget() self.tab1 = QWidget() self.tab2 = QWidget() self.tabs.resize(300, 200) # Add tabs self.tabs.addTab(self.tab1, "Text Display") self.tabs.addTab(self.tab2, "Graph Display") # Create tabs self.add_editspace(self.tab1) self.add_graphview(self.tab2) # Add tabs to widget self.layout.addWidget(self.tabs) self.setLayout(self.layout) # Make Connection self.docker.catCreated.connect(self.categoryCreated) self.window.catCreated.connect(self.categoryCreated) self.docker.catUpdated.connect( self.categoryUpdated ) # connecting signal from EditorWindow to update Node self.editSpace.textChanged.connect(self.editsMade) def editsMade(self): self.tabs.setStyleSheet('QTabBar::tab {background-color: red;}') self.up_to_date = False if DEBUG: print("Text has been changed!!") def add_editspace(self, tab): tab.layout = QVBoxLayout(self) # Setting main editing area where Files will be displayed and can be edited self.editSpace = QCodeEditor(self, theme_color=self.editSpace_theme) # list for Completer model words = [ "<aiml></aiml>", "<topic></topic>", "<category></category>", "<pattern></pattern>", "<template></template>", "<condition></condition>", "<li></li>", "<random></random>", "<set></set>", "<think></think>", "<that></that>", "<oob></oob>", "<robot></robot>", "<options></options>", "<option></option>", "<image></image>", "<video></video>", "<filename></filename>", "<get name=\"\" />", "<srai/>", "<star/>" ] # Setting completer self.completer = QCompleter(self.editSpace) self.completer.setCompletionMode(QCompleter.UnfilteredPopupCompletion) # self.completer.setCompletionMode(QCompleter.PopupCompletion) print( f"modelFromFile returning: {QStringListModel(words, self.completer).stringList()}" ) # self.completer.setModel(self.modelFromFile('GUI/style/keywords.txt')) self.completer.setModel(QStringListModel(words, self.completer)) self.completer.setModelSorting(QCompleter.CaseInsensitivelySortedModel) self.completer.setCaseSensitivity(Qt.CaseInsensitive) self.completer.setWrapAround(False) self.editSpace.setCompleter(self.completer) self.tab1.layout.addWidget(self.editSpace) tab.setLayout(tab.layout) def modelFromFile(self, fileName): if getattr(sys, 'frozen', False): path = os.path.dirname(sys.executable) path = path[:-4] + fileName else: path = os.path.abspath(os.path.dirname( os.path.abspath(__file__))) + fileName if DEBUG: print(f"path: {path}") with open(path, "r") as f: words = f.read().splitlines() QApplication.restoreOverrideCursor() if DEBUG: print( f"modelFromFile returning: {QStringListModel(words, self.completer).stringList()}" ) return QStringListModel(words, self.completer) def add_graphview(self, tab): tab.layout = QGridLayout(self) # Setting of backdrop for view categories as nodes. self.graphview = EditorWidget(self) # Building legend and zoom buttons self.add_legend() # Adding widgets to layout self.tab2.layout.addWidget(self.legendLabel, 0, 0) self.tab2.layout.addWidget(self.zoom_out, 0, 1) self.tab2.layout.addWidget(self.zoom_in, 0, 2) self.tab2.layout.addWidget(self.graphview, 1, 0, 1, 3) # Making button connections self.zoom_out.clicked.connect(self.zoom_out_clicked) self.zoom_in.clicked.connect(self.zoom_in_clicked) # Setting layout tab.setLayout(tab.layout) def add_legend(self): # Creating buttons to zoom in and out of the graph scene self.zoom_out = QPushButton("-") self.zoom_out.resize(50, 50) self.zoom_in = QPushButton("+") self.zoom_in.resize(50, 50) # Creating legend to clarify what fields in nodes mean self.legendLabel = QLabel() self.legendLabel.setFont(QFont("Sanserif", 10)) self.legendLabel.setText( "1st textbox represents the Pattern Tag\n" "2nd textbox represents the That Tag\n" "3rd textbox represents the Template Tag\n" "Yellow node is node currently selected, Red nodes are children, Turquoise nodes are parents." ) self.legendLabel.setStyleSheet( "QLabel {background-color: black; color: white; border: 1px solid " "#01DFD7; border-radius: 5px;}") def zoom_in_clicked(self): if DEBUG: print("Zoom In Clicked") zoomFactor = self.graphview.view.zoomInFactor zoomFactor += (self.graphview.view.zoomStep * 0.25) self.graphview.view.scale(zoomFactor, zoomFactor) def zoom_out_clicked(self): if DEBUG: print("Zoom Out Clicked") zoomFactor = self.graphview.view.zoomInFactor zoomFactor -= (self.graphview.view.zoomStep * 0.5) self.graphview.view.scale(zoomFactor, zoomFactor) def create_category_code_editor(self, cat): try: if self.aiml is not None: if DEBUG: print(f"Current aiml Model:\n{self.aiml}") if DEBUG: print("Ok to add category") self.aiml.append(cat) if DEBUG: print("appended category to AIML object") self.catCreated.emit(self.aiml) return cat else: if DEBUG: print("CodeEditor is equal to None") self.aiml = AIML() # self.clear() self.aiml.append(cat) if DEBUG: print("appended category to AIML object") self.catCreated.emit(self.aiml) return cat except Exception as ex: handleError(ex) print( "Exception caught in TabController - create_category_code_editor()" ) print(ex) def create_category_graph_view(self, cat): try: if cat.type == "topic": # Iterate through topic and place categories for category in cat.tags: thatToCheck = self.graphview.getLastSentence(category) if DEBUG: print("got last sentence of category") title = "Category: " + category.cat_id aNode = Node(self.graphview.scene, title, category) if DEBUG: print("created node") aNode.content.wdg_label.displayVisuals(category) if DEBUG: print("displayed contents on node") if thatToCheck is not None: for that in thatToCheck: self.graphview.findChildNodes(aNode, that) # FIXME: Nodes only get placed if there are <that> tags otherwise get stacked in default place. self.graphview.findParentNodes(aNode) self.graphview.placeNodes(self.graphview.scene.nodes) for node in self.graphview.scene.nodes: node.updateConnectedEdges() aNode.content.catClicked.connect( self.graphview.categoryClicked ) # connecting signals coming from Content Widget # NOTE: When addChildClicked has been implemented then this can be uncommented # if DEBUG: print("trying to connect addChild button") # aNode.content.childClicked.connect(self.graphview.addChildClicked) # connecting signals coming from Content Widget elif cat.type == "comment": print("Comment found, don't display comments on graphview.") else: thatToCheck = self.graphview.getLastSentence(cat) if DEBUG: print("got last sentence of category") title = "Category: " + cat.cat_id aNode = Node(self.graphview.scene, title, cat) if DEBUG: print("created node") aNode.content.wdg_label.displayVisuals(cat) if DEBUG: print("displayed contents on node") if thatToCheck is not None: for that in thatToCheck: self.graphview.findChildNodes(aNode, that) self.graphview.findParentNodes(aNode) self.graphview.placeNodes(self.graphview.scene.nodes) for node in self.graphview.scene.nodes: node.updateConnectedEdges() aNode.content.catClicked.connect( self.graphview.categoryClicked ) # connecting signals coming from Content Widget except Exception as ex: print( "Exception caught in TabController - create_category_graph_view()" ) print(ex) handleError(ex) # slot function for a category being created and displaying on editSpace @pyqtSlot(Tag) def categoryCreated(self, cat): try: if DEBUG: print("In TabController Slot - categoryCreated()") cat = self.create_category_code_editor(cat) self.create_category_graph_view(cat) except Exception as ex: print("Exception caught in TabController - categoryCreated()") print(ex) handleError(ex) # Slot function for updating categories. @pyqtSlot(Tag) def categoryUpdated(self, cat): if DEBUG: print("slot in TabController - categoryUpdated()") try: updatedCat = self.aiml.update(cat) # NOTE: This should be a safer alternative rather than updating # then requiring user to manually compile. But this still # runs into the same issue as compiling manually after # moving and updating nodes. # self.editSpace.setPlainText(str(self.aiml)) # self.window.onCompile() if DEBUG: print(f'Updated aiml object:\n{self.aiml}') self.graphview.updateNode(cat) # Updating graphview display if DEBUG: print("EditorWidget successfully updated") if DEBUG: print(f"aiml object to set (TabController):\n{self.aiml}") self.catUpdated.emit( self.aiml ) # Sending the updated aiml object to the CodeEditor. except Exception as ex: print("Exception caught in TabController - categoryUpdated()") print(ex) handleError(ex)