Ejemplo n.º 1
0
    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)
Ejemplo n.º 2
0
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.")
Ejemplo n.º 3
0
 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)
Ejemplo n.º 4
0
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)
Ejemplo n.º 5
0
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
Ejemplo n.º 6
0
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())
Ejemplo n.º 7
0
    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)
Ejemplo n.º 8
0
    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)
Ejemplo n.º 9
0
    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_()
Ejemplo n.º 10
0
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.")
Ejemplo n.º 11
0
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:])
Ejemplo n.º 12
0
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()
Ejemplo n.º 13
0
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)
Ejemplo n.º 16
0
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)