Exemplo n.º 1
0
class OWWordList(OWWidget):
    name = "Word List"
    description = "Create a list of words."
    icon = "icons/WordList.svg"
    priority = 1000

    class Inputs:
        words = Input("Words", Table)

    class Outputs:
        selected_words = Output("Selected Words", Table)
        words = Output("Words", Table)

    class Warning(OWWidget.Warning):
        no_string_vars = Msg("Input needs at least one Text variable.")

    NONE, CACHED, LIBRARY = range(3)  # library list modification types

    want_main_area = False
    resizing_enabled = True

    settingsHandler = DomainContextHandler()
    word_list_library: List[Dict] = Setting([
        {
            "name": WordList.generate_word_list_name([]),
            "words": []
        },
    ])
    word_list_index: int = Setting(0)
    words_var: Optional[StringVariable] = ContextSetting(None)
    update_rule_index: int = Setting(UpdateRules.INTERSECT)
    words: List[str] = Setting(None, schema_only=True)
    selected_words: Set[str] = Setting(set(), schema_only=True)

    def __init__(self):
        super().__init__(self)
        flags = Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable
        self.library_model = PyListModel([], self, flags=flags)
        self.words_model = PyListModel([], self, flags=flags, enable_dnd=True)

        self.library_view: QListView = None
        self.words_view: ListView = None

        self.__input_words_model = DomainModel(valid_types=(StringVariable, ))
        self.__input_words: Optional[Table] = None

        self.__library_box: QGroupBox = gui.vBox(None, "Library")
        self.__input_box: QGroupBox = gui.vBox(None, "Input")
        self.__words_box: QGroupBox = gui.vBox(None, box=True)
        self.__update_rule_rb: QRadioButton = None

        self.__add_word_action: QAction = None
        self.__remove_word_action: QAction = None

        self._setup_gui()
        self._restore_state()
        self.settingsAboutToBePacked.connect(self._save_state)

    def _setup_gui(self):
        layout = QGridLayout()
        gui.widgetBox(self.controlArea, orientation=layout)

        self._setup_library_box()
        self._setup_input_box()
        self._setup_words_box()

        layout.addWidget(self.__library_box, 0, 0)
        layout.addWidget(self.__input_box, 1, 0)
        layout.addWidget(self.__words_box, 0, 1, 0, 1)

    def _setup_library_box(self):
        self.library_view = QListView(
            editTriggers=QListView.DoubleClicked | QListView.EditKeyPressed,
            minimumWidth=200,
            sizePolicy=QSizePolicy(QSizePolicy.Ignored, QSizePolicy.Expanding),
        )
        self.library_view.setItemDelegate(WordListItemDelegate(self))
        self.library_view.setModel(self.library_model)
        self.library_view.selectionModel().selectionChanged.connect(
            self.__on_library_selection_changed)

        self.__library_box.layout().setSpacing(1)
        self.__library_box.layout().addWidget(self.library_view)

        actions_widget = ModelActionsWidget()
        actions_widget.layout().setSpacing(1)

        action = QAction("+", self)
        action.setToolTip("Add a new word list to the library")
        action.triggered.connect(self.__on_add_word_list)
        actions_widget.addAction(action)

        action = QAction("\N{MINUS SIGN}", self)
        action.setToolTip("Remove word list from library")
        action.triggered.connect(self.__on_remove_word_list)
        actions_widget.addAction(action)

        action = QAction("Update", self)
        action.setToolTip("Save changes in the editor to library")
        action.setShortcut(QKeySequence(QKeySequence.Save))
        action.triggered.connect(self.__on_update_word_list)
        actions_widget.addAction(action)

        gui.rubber(actions_widget.layout())

        action = QAction("More", self, toolTip="More actions")

        new_from_file = QAction("Import Words from File", self)
        new_from_file.triggered.connect(self.__on_import_word_list)

        save_to_file = QAction("Save Words to File", self)
        save_to_file.setShortcut(QKeySequence(QKeySequence.SaveAs))
        save_to_file.triggered.connect(self.__on_save_word_list)

        menu = QMenu(actions_widget)
        menu.addAction(new_from_file)
        menu.addAction(save_to_file)
        action.setMenu(menu)
        button = actions_widget.addAction(action)
        button.setPopupMode(QToolButton.InstantPopup)
        self.__library_box.layout().addWidget(actions_widget)

    def __on_library_selection_changed(self, selected: QItemSelection, *_):
        index = [i.row() for i in selected.indexes()]
        if index:
            current = index[0]
            word_list: WordList = self.library_model[current]
            self.word_list_index = current
            self.selected_words = set()
            self.words_model.wrap(list(word_list.cached_words))
            self._apply_update_rule()

    def __on_add_word_list(self):
        taken = [l.name for l in self.library_model]
        name = WordList.generate_word_list_name(taken)
        word_list = WordList(name, self.words_model[:])
        self.library_model.append(word_list)
        self._set_selected_word_list(len(self.library_model) - 1)

    def __on_remove_word_list(self):
        index = self._get_selected_word_list_index()
        if index is not None:
            del self.library_model[index]
            self._set_selected_word_list(max(index - 1, 0))
            self._apply_update_rule()

    def __on_update_word_list(self):
        self._set_word_list_modified(mod_type=self.LIBRARY)

    def __on_import_word_list(self):
        filename, _ = QFileDialog.getOpenFileName(
            self, "Open Word List", os.path.expanduser("~/"),
            "Text files (*.txt)\nAll files(*.*)")
        if filename:
            name = os.path.basename(filename)
            with open(filename, encoding="utf-8") as f:
                words = [line.strip() for line in f.readlines()]
            self.library_model.append(WordList(name, words, filename=filename))
            self._set_selected_word_list(len(self.library_model) - 1)
            self._apply_update_rule()

    def __on_save_word_list(self):
        index = self._get_selected_word_list_index()
        if index is not None:
            word_list = self.library_model[index]
            filename = word_list.filename
        else:
            filename = os.path.expanduser("~/")

        filename, _ = QFileDialog.getSaveFileName(
            self, "Save Word List", filename,
            "Text files (*.txt)\nAll files(*.*)")
        if filename:
            head, tail = os.path.splitext(filename)
            if not tail:
                filename = head + ".txt"

            with open(filename, "w", encoding="utf-8") as f:
                for word in self.words_model:
                    f.write(f"{word}\n")

    def _setup_input_box(self):
        gui.comboBox(self.__input_box,
                     self,
                     "words_var",
                     label="Word variable:",
                     orientation=Qt.Vertical,
                     model=self.__input_words_model,
                     callback=self._apply_update_rule)
        gui.radioButtons(self.__input_box,
                         self,
                         "update_rule_index",
                         UpdateRules.ITEMS,
                         label="Update: ",
                         orientation=Qt.Vertical,
                         callback=self.__on_update_rule_changed)
        self.__input_box.setEnabled(False)

    def __on_update_rule_changed(self):
        self._enable_words_actions()
        self._apply_update_rule()

    def _setup_words_box(self):
        self.words_view = ListView()
        self.words_view.drop_finished.connect(self.__on_words_data_changed)
        self.words_view.setModel(self.words_model)
        self.words_view.selectionModel().selectionChanged.connect(
            self.__on_words_selection_changed)

        self.words_model.dataChanged.connect(self.__on_words_data_changed)

        self.__words_box.layout().setSpacing(1)
        self.__words_box.layout().addWidget(self.words_view)

        actions_widget = ModelActionsWidget()
        actions_widget.layout().setSpacing(1)

        action = QAction("+", self.words_view, toolTip="Add a new word")
        action.triggered.connect(self.__on_add_word)
        actions_widget.addAction(action)
        self.__add_word_action = action

        action = QAction("\N{MINUS SIGN}", self, toolTip="Remove word")
        action.triggered.connect(self.__on_remove_word)
        actions_widget.addAction(action)
        self.__remove_word_action = action

        gui.rubber(actions_widget)

        action = QAction("Sort", self)
        action.setToolTip("Sort words alphabetically")
        action.triggered.connect(self.__on_apply_sorting)
        actions_widget.addAction(action)

        self.__words_box.layout().addWidget(actions_widget)

    def __on_words_data_changed(self):
        self._set_word_list_modified(mod_type=self.CACHED)
        self.commit()

    def __on_words_selection_changed(self):
        self.commit()

    def __on_add_word(self):
        row = self.words_model.rowCount()
        if not self.words_model.insertRow(self.words_model.rowCount()):
            return
        with disconnected(self.words_view.selectionModel().selectionChanged,
                          self.__on_words_selection_changed):
            self._set_selected_words([0])
            index = self.words_model.index(row, 0)
            self.words_view.setCurrentIndex(index)
            self.words_model.setItemData(index, {Qt.EditRole: ""})
        self.words_view.edit(index)

    def __on_remove_word(self):
        rows = self.words_view.selectionModel().selectedRows(0)
        if not rows:
            return

        indices = sorted([row.row() for row in rows], reverse=True)
        with disconnected(self.words_view.selectionModel().selectionChanged,
                          self.__on_words_selection_changed):
            for index in indices:
                self.words_model.removeRow(index)
            if self.words_model:
                self._set_selected_words([max(0, indices[-1] - 1)])
        self.__on_words_data_changed()

    def __on_apply_sorting(self):
        if not self.words_model:
            return
        words = self.words_model[:]
        mask = np.zeros(len(words), dtype=bool)
        selection = self._get_selected_words_indices()
        if selection:
            mask[selection] = True

        indices = np.argsort(words)
        self.words_model.wrap([words[i] for i in indices])
        self._set_word_list_modified(mod_type=self.CACHED)
        if selection:
            self._set_selected_words(list(np.flatnonzero(mask[indices])))
        else:
            self.commit()

    @Inputs.words
    def set_words(self, words: Optional[Table]):
        self.closeContext()
        self.__input_words = words
        self._check_input_words()
        self._init_controls()
        self.openContext(self.__input_words)
        self._apply_update_rule()

    def _check_input_words(self):
        self.Warning.no_string_vars.clear()
        if self.__input_words:
            metas = self.__input_words.domain.metas
            if not any(isinstance(m, StringVariable) for m in metas):
                self.Warning.no_string_vars()
                self.__input_words = None

    def _init_controls(self):
        words = self.__input_words
        domain = words.domain if words is not None else None
        self.__input_words_model.set_domain(domain)
        if len(self.__input_words_model) > 0:
            self.words_var = self.__input_words_model[0]
        self.__input_box.setEnabled(bool(self.__input_words_model))
        self._enable_words_actions()

    def _enable_words_actions(self):
        if bool(self.__input_words_model) \
                and self.update_rule_index != UpdateRules.LIBRARY:
            self.words_view.setEditTriggers(QListView.NoEditTriggers)
            self.__add_word_action.setEnabled(False)
            self.__remove_word_action.setEnabled(False)
        else:
            self.words_view.setEditTriggers(QListView.DoubleClicked
                                            | QListView.EditKeyPressed)
            self.__add_word_action.setEnabled(True)
            self.__remove_word_action.setEnabled(True)

    def _apply_update_rule(self):
        lib_index = self._get_selected_word_list_index()
        lib_words, in_words, update_rule = [], [], UpdateRules.LIBRARY
        if lib_index is not None:
            lib_words = self.library_model[lib_index].cached_words
        else:
            lib_words = self.words_model[:]
        if self.__input_words is not None:
            in_words = self.__input_words.get_column_view(self.words_var)[0]
            in_words = list(in_words)
            update_rule = self.update_rule_index

        UpdateRules.update(self.words_model, lib_words, in_words, update_rule)
        if lib_index is not None:
            cached = self.library_model[lib_index].cached_words
            modified = WordList.NotModified if cached == self.words_model[:] \
                else WordList.Modified
            self.library_model[lib_index].update_rule_flag = modified
            self._set_word_list_modified(mod_type=self.NONE)
            self.library_view.repaint()

        # Apply selection. selection_changed invokes commit().
        # If there is no selection, call commit explicitly.
        if any(w in self.selected_words for w in self.words_model):
            self.set_selected_words()
            self.words_view.repaint()
        else:
            self.commit()

    def commit(self):
        selection = self._get_selected_words_indices()
        self.selected_words = set(np.array(self.words_model)[selection])

        words, selected_words = None, None
        if self.words_model:
            words_var = StringVariable("Words")
            words_var.attributes = {"type": "words"}
            domain = Domain([], metas=[words_var])
            _words = Table.from_list(domain, [[w] for w in self.words_model])
            _words.name = "Words"
            if selection:
                selected_words = _words[selection]
            words = create_annotated_table(_words, selection)
        self.Outputs.words.send(words)
        self.Outputs.selected_words.send(selected_words)

    def _set_word_list_modified(self, mod_type):
        index = self._get_selected_word_list_index()
        if index is not None:
            if mod_type == self.LIBRARY:
                self.library_model[index].words = self.words_model[:]
                self.library_model[index].cached_words = self.words_model[:]
                self.library_model[index].update_rule_flag \
                    = WordList.NotModified
            elif mod_type == self.CACHED:
                self.library_model[index].cached_words = self.words_model[:]
            elif mod_type == self.NONE:
                pass
            else:
                raise NotImplementedError
            self.library_model.emitDataChanged(index)
            self.library_view.repaint()

    def _set_selected_word_list(self, index: int):
        sel_model: QItemSelectionModel = self.library_view.selectionModel()
        sel_model.select(self.library_model.index(index, 0),
                         QItemSelectionModel.ClearAndSelect)

    def _get_selected_word_list_index(self) -> Optional[int]:
        rows = self.library_view.selectionModel().selectedRows()
        return rows[0].row() if rows else None

    def _set_selected_words(self, indices: List[int]):
        selection = QItemSelection()
        sel_model: QItemSelectionModel = self.words_view.selectionModel()
        for i in indices:
            selection.append(QItemSelectionRange(self.words_model.index(i, 0)))
        sel_model.select(selection, QItemSelectionModel.ClearAndSelect)

    def _get_selected_words_indices(self) -> List[int]:
        rows = self.words_view.selectionModel().selectedRows()
        return [row.row() for row in rows]

    def set_selected_words(self):
        if self.selected_words:
            indices = [
                i for i, w in enumerate(self.words_model)
                if w in self.selected_words
            ]
            self._set_selected_words(indices)

    def _restore_state(self):
        source = [WordList.from_dict(s) for s in self.word_list_library]
        self.library_model.wrap(source)
        # __on_library_selection_changed() (invoked by _set_selected_word_list)
        # clears self.selected_words
        selected_words = self.selected_words
        self._set_selected_word_list(self.word_list_index)

        if self.words is not None:
            self.words_model.wrap(list(self.words))
            self._set_word_list_modified(mod_type=self.CACHED)
            if selected_words:
                self.selected_words = selected_words
                self.set_selected_words()
            elif len(self.word_list_library) > self.word_list_index and \
                self.word_list_library[self.word_list_index] != self.words:
                self.commit()

    def _save_state(self):
        self.word_list_library = [s.as_dict() for s in self.library_model]
        self.words = self.words_model[:]

    def send_report(self):
        library = self.library_model[self.word_list_index].name \
            if self.library_model else "/"
        settings = [("Library", library)]
        if self.__input_words:
            self.report_data("Input Words", self.__input_words)
            settings.append(("Word variable", self.words_var))
            rule = UpdateRules.ITEMS[self.update_rule_index]
            settings.append(("Update", rule))
        self.report_items("Settings", settings)
        self.report_paragraph("Words", ", ".join(self.words_model[:]))
Exemplo n.º 2
0
class OWOntology(OWWidget, ConcurrentWidgetMixin):
    name = "Ontology"
    description = ""
    icon = "icons/Ontology.svg"
    priority = 1110
    keywords = []

    CACHED, LIBRARY = range(2)  # library list modification types
    RUN_BUTTON, INC_BUTTON = "Generate", "Include"

    settingsHandler = DomainContextHandler()
    ontology_library: List[Dict] = Setting([
        {"name": Ontology.generate_name([]), "ontology": {}},
    ])
    ontology_index: int = Setting(0)
    ontology: OntoType = Setting((), schema_only=True)
    include_children = Setting(True)
    auto_commit = Setting(True)

    class Inputs:
        words = Input("Words", Table)

    class Outputs:
        words = Output("Words", Table, dynamic=False)

    class Warning(OWWidget.Warning):
        no_words_column = Msg("Input is missing 'Words' column.")

    class Error(OWWidget.Error):
        load_error = Msg("{}")

    def __init__(self):
        OWWidget.__init__(self)
        ConcurrentWidgetMixin.__init__(self)
        self.__onto_handler = OntologyHandler()

        flags = Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable
        self.__model = PyListModel([], self, flags=flags)
        self.__input_model = QStandardItemModel()
        self.__library_view: QListView = None
        self.__input_view: ListViewSearch = None
        self.__ontology_view: EditableTreeView = None
        self.ontology_info = ""

        self._setup_gui()
        self._restore_state()
        self.settingsAboutToBePacked.connect(self._save_state)

    def _setup_gui(self):
        # control area
        library_box: QGroupBox = gui.vBox(self.controlArea, "Library")
        library_box.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Maximum)

        edit_triggers = QListView.DoubleClicked | QListView.EditKeyPressed
        self.__library_view = QListView(
            editTriggers=int(edit_triggers),
            minimumWidth=200,
            sizePolicy=QSizePolicy(QSizePolicy.Ignored, QSizePolicy.Expanding),
        )
        self.__library_view.setFixedHeight(100)
        self.__library_view.setItemDelegate(LibraryItemDelegate(self))
        self.__library_view.setModel(self.__model)
        self.__library_view.selectionModel().selectionChanged.connect(
            self.__on_selection_changed
        )

        actions_widget = ModelActionsWidget()
        actions_widget.layout().setSpacing(1)

        tool_tip = "Add a new ontology to the library"
        action = QAction("+", self, toolTip=tool_tip)
        action.triggered.connect(self.__on_add)
        actions_widget.addAction(action)

        tool_tip = "Remove the ontology from the library"
        action = QAction("\N{MINUS SIGN}", self, toolTip=tool_tip)
        action.triggered.connect(self.__on_remove)
        actions_widget.addAction(action)

        tool_tip = "Save changes in the editor to the library"
        action = QAction("Update", self, toolTip=tool_tip)
        action.triggered.connect(self.__on_update)
        actions_widget.addAction(action)

        gui.rubber(actions_widget)

        action = QAction("More", self, toolTip="More actions")

        new_from_file = QAction("Import Ontology from File", self)
        new_from_file.triggered.connect(self.__on_import_file)

        new_from_url = QAction("Import Ontology from URL", self)
        new_from_url.triggered.connect(self.__on_import_url)

        save_to_file = QAction("Save Ontology to File", self)
        save_to_file.triggered.connect(self.__on_save)

        menu = QMenu(actions_widget)
        menu.addAction(new_from_file)
        menu.addAction(new_from_url)
        menu.addAction(save_to_file)
        action.setMenu(menu)
        button = actions_widget.addAction(action)
        button.setPopupMode(QToolButton.InstantPopup)

        vlayout = QVBoxLayout()
        vlayout.setSpacing(1)
        vlayout.setContentsMargins(0, 0, 0, 0)
        vlayout.addWidget(self.__library_view)
        vlayout.addWidget(actions_widget)

        library_box.layout().setSpacing(1)
        library_box.layout().addLayout(vlayout)

        input_box: QGroupBox = gui.vBox(self.controlArea, "Input")
        self.__input_view = ListViewSearch(
            sizePolicy=QSizePolicy(QSizePolicy.Ignored, QSizePolicy.Expanding),
            selectionMode=QListView.ExtendedSelection,
            dragEnabled=True,
        )
        self.__input_view.setModel(self.__input_model)
        self.__input_view.selectionModel().selectionChanged.connect(
            self._enable_include_button
        )

        self.__inc_button = gui.button(
            None, self, self.INC_BUTTON, enabled=False,
            toolTip="Include selected words into the ontology",
            autoDefault=False, callback=self.__on_toggle_include
        )

        input_box.layout().setSpacing(1)
        input_box.layout().addWidget(self.__input_view)
        input_box.layout().addWidget(self.__inc_button)

        self.__run_button = gui.button(
            self.controlArea, self, self.RUN_BUTTON,
            callback=self.__on_toggle_run
        )
        gui.checkBox(
            self.controlArea, self, "include_children", "Include subtree",
            box="Output", callback=self.commit.deferred
        )
        box = gui.vBox(self.controlArea, "Ontology info")
        gui.label(box, self, "%(ontology_info)s")

        gui.auto_send(self.buttonsArea, self, "auto_commit")

        # main area
        ontology_box: QGroupBox = gui.vBox(self.mainArea, box=True)

        self.__ontology_view = EditableTreeView(self)
        self.__ontology_view.dataChanged.connect(
            self.__on_ontology_data_changed
        )
        self.__ontology_view.selectionChanged.connect(self.commit.deferred)

        ontology_box.layout().setSpacing(1)
        ontology_box.layout().addWidget(self.__ontology_view)

        self._enable_include_button()

    def __on_selection_changed(self, selection: QItemSelection, *_):
        self.Error.load_error.clear()
        if selection.indexes():
            self.ontology_index = row = selection.indexes()[0].row()
            data = self.__model[row].cached_word_tree
            self.__ontology_view.set_data(data)
            self.__update_score()
            error_msg = self.__model[row].error_msg
            if error_msg:
                self.Error.load_error(error_msg)

    def __on_add(self):
        name = Ontology.generate_name([l.name for l in self.__model])
        data = self.__ontology_view.get_data()
        self.__model.append(Ontology(name, data))
        self.__set_selected_row(len(self.__model) - 1)

    def __on_remove(self):
        index = self.__get_selected_row()
        if index is not None:
            del self.__model[index]
            self.__set_selected_row(max(index - 1, 0))

    def __on_update(self):
        self.__set_current_modified(self.LIBRARY)

    def __on_import_file(self):
        ontology = read_from_file(self)
        self._import_ontology(ontology)

    def __on_import_url(self):
        ontology = read_from_url(self)
        self._import_ontology(ontology)

    def __on_save(self):
        index = self.__get_selected_row()
        if index is not None:
            filename = self.__model[index].filename
            if filename:
                filename, _ = os.path.splitext(filename)
        else:
            filename = os.path.expanduser("~/")
        save_ontology(self, filename, self.__ontology_view.get_data())
        QApplication.setActiveWindow(self)

    def __on_toggle_include(self):
        if self.task is not None:
            self._cancel_tasks()
        else:
            self._run_insert()

    def __on_toggle_run(self):
        if self.task is not None:
            self._cancel_tasks()
        else:
            self._run()

    def __on_ontology_data_changed(self):
        self.__set_current_modified(self.CACHED)
        self.__update_score()
        self._enable_include_button()
        self.commit.deferred()

    @Inputs.words
    def set_words(self, words: Optional[Table]):
        self.Warning.no_words_column.clear()
        self.__input_model.clear()
        if words:
            if WORDS_COLUMN_NAME in words.domain and words.domain[
                    WORDS_COLUMN_NAME].attributes.get("type") == "words":
                for word in words.get_column_view(WORDS_COLUMN_NAME)[0]:
                    self.__input_model.appendRow(QStandardItem(word))
            else:
                self.Warning.no_words_column()

    @gui.deferred
    def commit(self):
        if self.include_children:
            words = self.__ontology_view.get_selected_words_with_children()
        else:
            words = self.__ontology_view.get_selected_words()
        words_table = self._create_output_table(sorted(words))
        self.Outputs.words.send(words_table)

    @staticmethod
    def _create_output_table(words: List[str]) -> Optional[Table]:
        if not words:
            return None
        return create_words_table(words)

    def _cancel_tasks(self):
        self.cancel()
        self.__inc_button.setText(self.INC_BUTTON)
        self.__run_button.setText(self.RUN_BUTTON)

    def _run(self):
        self.__run_button.setText("Stop")
        words = self.__ontology_view.get_words()
        handler = self.__onto_handler.generate
        self.start(_run, handler, (words,))

    def _run_insert(self):
        self.__inc_button.setText("Stop")
        tree = self.__ontology_view.get_data()
        words = self.__get_selected_input_words()
        handler = self.__onto_handler.insert
        self.start(_run, handler, (tree, words))

    def on_done(self, data: Dict):
        self.__inc_button.setText(self.INC_BUTTON)
        self.__run_button.setText(self.RUN_BUTTON)
        self.__ontology_view.set_data(data, keep_history=True)
        self.__set_current_modified(self.CACHED)
        self.__update_score()

    def __update_score(self):
        tree = self.__ontology_view.get_data()
        score = round(self.__onto_handler.score(tree), 2) \
            if len(tree) == 1 and list(tree.values())[0] else "/"
        self.ontology_info = f"Score: {score}"

    def on_exception(self, ex: Exception):
        raise ex

    def on_partial_result(self, _: Any):
        pass

    def onDeleteWidget(self):
        self.shutdown()
        super().onDeleteWidget()

    def __set_selected_row(self, row: int):
        self.__library_view.selectionModel().select(
            self.__model.index(row, 0), QItemSelectionModel.ClearAndSelect
        )

    def __get_selected_row(self) -> Optional[int]:
        rows = self.__library_view.selectionModel().selectedRows()
        return rows[0].row() if rows else None

    def __set_current_modified(self, mod_type: int):
        index = self.__get_selected_row()
        if index is not None:
            if mod_type == self.LIBRARY:
                ontology = self.__ontology_view.get_data()
                self.__model[index].word_tree = ontology
                self.__model[index].cached_word_tree = ontology
                self.__model[index].update_rule_flag = Ontology.NotModified
            elif mod_type == self.CACHED:
                ontology = self.__ontology_view.get_data()
                self.__model[index].cached_word_tree = ontology
            else:
                raise NotImplementedError
            self.__model.emitDataChanged(index)
            self.__library_view.repaint()

    def __get_selected_input_words(self) -> List[str]:
        return [self.__input_view.model().data(index) for index in
                self.__input_view.selectedIndexes()]

    def _import_ontology(self, ontology: Ontology):
        if ontology is not None:
            self.__model.append(ontology)
            self.__set_selected_row(len(self.__model) - 1)
        QApplication.setActiveWindow(self)

    def _restore_state(self):
        source = [Ontology.from_dict(s) for s in self.ontology_library]
        self.__model.wrap(source)
        self.__set_selected_row(self.ontology_index)
        if self.ontology:
            self.__ontology_view.set_data(self.ontology)
            self.__set_current_modified(self.CACHED)
            self.__update_score()
            self.commit.now()

    def _save_state(self):
        self.ontology_library = [s.as_dict() for s in self.__model]
        self.ontology = self.__ontology_view.get_data(with_selection=True)

    def _enable_include_button(self):
        tree = self.__ontology_view.get_data()
        words = self.__get_selected_input_words()
        enabled = len(tree) == 1 and len(words) > 0
        self.__inc_button.setEnabled(enabled)

    def send_report(self):
        model = self.__model
        library = model[self.ontology_index].name if model else "/"
        self.report_items("Settings", [("Library", library)])

        ontology = self.__ontology_view.get_data()
        style = """
        <style>
            ul {
                padding-top: 0px;
                padding-right: 0px;
                padding-bottom: 0px;
                padding-left: 20px;
            }
        </style>
        """
        self.report_raw("Ontology", style + _tree_to_html(ontology))