From 1447bb20ff4afec3d633804e619d8f7e18d7050c Mon Sep 17 00:00:00 2001 From: Manuel Namici Date: Mon, 18 Jul 2022 18:19:12 -0700 Subject: [PATCH 1/9] plugins: ontology_importer: make db path into a constant Also, use a more appropriate name for the database. --- eddy/plugins/ontology-importer/ontology_importer.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/eddy/plugins/ontology-importer/ontology_importer.py b/eddy/plugins/ontology-importer/ontology_importer.py index 11556e8f..c11b57bb 100644 --- a/eddy/plugins/ontology-importer/ontology_importer.py +++ b/eddy/plugins/ontology-importer/ontology_importer.py @@ -88,6 +88,8 @@ from eddy.ui.fields import IntegerField from eddy.ui.progress import BusyProgressDialog +K_IMPORTS_DB = '@data/imports.sqlite' + class OntologyImporterPlugin(AbstractPlugin): @@ -886,7 +888,7 @@ def onToolbarButtonClick(self): QtCore.QCoreApplication.processEvents() - db_filename = expandPath('@data/db.db') + db_filename = expandPath(K_IMPORTS_DB) dir = os.path.dirname(db_filename) if not os.path.exists(dir): os.makedirs(dir) @@ -1118,7 +1120,7 @@ def __init__(self, project): self.project_iri = str(self.project.ontologyIRI) self.project_version = self.project.version if len(self.project.version) > 0 else '1.0' - self.db_filename = expandPath('@data/db.db') + self.db_filename = expandPath(K_IMPORTS_DB) dir = os.path.dirname(self.db_filename) if not os.path.exists(dir): os.makedirs(dir) @@ -1410,7 +1412,7 @@ def __init__(self, not_drawn, project): self.DataOneOf = self.vm.getJavaClass("org.semanticweb.owlapi.model.OWLDataOneOf") self.DataUnionOf = self.vm.getJavaClass("org.semanticweb.owlapi.model.OWLDataUnionOf") - self.db_filename = expandPath('@data/db.db') + self.db_filename = expandPath(K_IMPORTS_DB) dir = os.path.dirname(self.db_filename) if not os.path.exists(dir): os.makedirs(dir) From 3e5ca65516c8bcaeeb40cfacce84bfa3bc128ac7 Mon Sep 17 00:00:00 2001 From: Manuel Namici Date: Mon, 18 Jul 2022 18:56:53 -0700 Subject: [PATCH 2/9] plugins: ontology_importer: add initial versioning of database Each db revision is simply an incremental number, which is stored in the sqlite pragma `user_version`. The current version of the plugin database is stored in the spec file. Later releases will have to include the migration infrastructure, as a set of SQL commands to change the schema and move data from one version to the immediate successor. See issue #203 for reference. --- .../ontology-importer/ontology_importer.py | 160 ++++++++++++------ eddy/plugins/ontology-importer/plugin.spec | 3 + 2 files changed, 109 insertions(+), 54 deletions(-) diff --git a/eddy/plugins/ontology-importer/ontology_importer.py b/eddy/plugins/ontology-importer/ontology_importer.py index c11b57bb..4601f18f 100644 --- a/eddy/plugins/ontology-importer/ontology_importer.py +++ b/eddy/plugins/ontology-importer/ontology_importer.py @@ -36,6 +36,7 @@ import ast import os import sqlite3 +import textwrap from PyQt5 import ( QtCore, @@ -47,12 +48,13 @@ from eddy.core.commands.common import CommandItemsRemove from eddy.core.commands.diagram import CommandDiagramResize from eddy.core.commands.edges import CommandEdgeAdd -from eddy.core.commands.iri import CommandChangeIRIOfNode, CommandChangeLiteralOfNode +from eddy.core.commands.iri import CommandChangeIRIOfNode from eddy.core.commands.iri import CommandIRIAddAnnotationAssertion from eddy.core.commands.nodes import CommandNodeAdd from eddy.core.commands.project import CommandProjectAddAnnotationProperty, CommandProjectAddPrefix from eddy.core.common import HasWidgetSystem from eddy.core.datatypes.graphol import Item +from eddy.core.functions.fsystem import fremove from eddy.core.functions.misc import isEmpty from eddy.core.functions.path import expandPath from eddy.core.functions.signals import connect @@ -80,7 +82,7 @@ from eddy.core.owl import ( AnnotationAssertion, IllegalNamespaceError, - OWL2Datatype, Facet, + Facet, ) from eddy.core.owl import IRI from eddy.core.owl import Literal @@ -89,6 +91,63 @@ from eddy.ui.progress import BusyProgressDialog K_IMPORTS_DB = '@data/imports.sqlite' +K_SCHEMA_SCRIPT = """ +PRAGMA user_version = {version}; + +CREATE TABLE IF NOT EXISTS ontology ( + iri TEXT, + version TEXT, + PRIMARY KEY (iri, version) +); + +CREATE TABLE IF NOT EXISTS project ( + iri TEXT, + version TEXT, + PRIMARY KEY (iri, version) +); + +CREATE TABLE IF NOT EXISTS importation ( + project_iri TEXT, + project_version TEXT, + ontology_iri TEXT, + ontology_version TEXT, + session_id TEXT, + PRIMARY KEY (project_iri, project_version, ontology_iri, ontology_version), + FOREIGN KEY (project_iri, project_version) + REFERENCES project(iri, version), + FOREIGN KEY (ontology_iri, ontology_version) + REFERENCES ontology(iri, version) +); + +CREATE TABLE IF NOT EXISTS axiom ( + axiom TEXT, + type_of_axiom TEXT, + func_axiom TEXT, + ontology_iri TEXT, + ontology_version TEXT, + iri_dict TEXT, + PRIMARY KEY (axiom, ontology_iri, ontology_version), + FOREIGN KEY (ontology_iri, ontology_version) + REFERENCES ontology(iri, version) +); + +CREATE TABLE IF NOT EXISTS drawn ( + project_iri TEXT, + project_version TEXT, + ontology_iri TEXT, + ontology_version TEXT, + axiom TEXT, + session_id TEXT, + PRIMARY KEY (project_iri, project_version, ontology_iri, ontology_version, axiom), + FOREIGN KEY (project_iri, project_version, ontology_iri, ontology_version, session_id) + REFERENCES importation(project_iri, project_version, + ontology_iri, ontology_version, + session_id) + ON DELETE CASCADE, + FOREIGN KEY (axiom, ontology_iri, ontology_version) + REFERENCES axiom(axiom, ontology_iri, ontology_version) +); +""" class OntologyImporterPlugin(AbstractPlugin): @@ -893,67 +952,19 @@ def onToolbarButtonClick(self): if not os.path.exists(dir): os.makedirs(dir) - schema_script = ''' - create table if not exists ontology ( - iri text, - version test, - PRIMARY KEY (iri, version) - ); - - create table if not exists project ( - iri text, - version test, - PRIMARY KEY (iri, version) - ); - - create table if not exists importation ( - project_iri text, - project_version text, - ontology_iri text, - ontology_version text, - session_id text, - PRIMARY KEY (project_iri, project_version, ontology_iri, ontology_version), - FOREIGN KEY (project_iri, project_version) references project(iri, version), - FOREIGN KEY (ontology_iri, ontology_version) references ontology(iri, version) - ); - - create table if not exists axiom ( - axiom text, - type_of_axiom text, - func_axiom text, - ontology_iri text, - ontology_version text, - iri_dict text, - PRIMARY KEY (axiom, ontology_iri, ontology_version), - FOREIGN KEY (ontology_iri, ontology_version) references ontology(iri, version) - ); - - create table if not exists drawn ( - project_iri text, - project_version text, - ontology_iri text, - ontology_version text, - axiom text, - session_id text, - PRIMARY KEY (project_iri, project_version, ontology_iri, ontology_version, axiom), - FOREIGN KEY (project_iri, project_version, ontology_iri, ontology_version, session_id) references importation(project_iri, project_version, ontology_iri, ontology_version, session_id) on delete cascade, - FOREIGN KEY (axiom, ontology_iri, ontology_version) references axiom(axiom, ontology_iri, ontology_version) - );''' - db_is_new = not os.path.exists(db_filename) conn = sqlite3.connect(db_filename) cursor = conn.cursor() QtCore.QCoreApplication.processEvents() + # TODO: Move to plugin init() if db_is_new: - #print('Creazione dello schema') - conn.executescript(schema_script) + conn.executescript(K_SCHEMA_SCRIPT.format( + version=self.spec.get('database', 'version'))) conn.commit() - QtCore.QCoreApplication.processEvents() - #print('Nuova Importazione') self.project.version = self.project.version if len( self.project.version) > 0 else '1.0' @@ -1082,6 +1093,40 @@ def onNoSave(self): importation = Importation(self.project) importation.removeFromDB() + def checkDatabase(self): + """ + Checks whether the currently stored import database is compatible + with the current version supported by the plugin. + """ + db = expandPath(K_IMPORTS_DB) + if os.path.exists(db): + # CHECK THAT VERSION IS COMPATIBLE + conn = sqlite3.connect(db) + cursor = conn.cursor() + version = self.spec.getint('database', 'version') + nversion = int(cursor.execute('PRAGMA user_version').fetchone()[0]) + if nversion > self.spec.getint('database', 'version'): + msgbox = QtWidgets.QMessageBox() + msgbox.setIconPixmap(QtGui.QIcon(':/icons/48/ic_warning_black').pixmap(48)) + msgbox.setWindowIcon(QtGui.QIcon(':/icons/128/ic_eddy')) + msgbox.setWindowTitle('Error initializing plugin: {}'.format(self.name())) + msgbox.setText(textwrap.dedent(""" + Incompatible import database version {nversion} > {version}.

+ This means that it was opened with a more recent version + of the {name} plugin.

+ In order to use this version of the {name} plugin you will + need to recreate the import database.
+ Do you want to proceed?

+ WARNING: this will delete all the OWL 2 imports in progress! + """.format(version=version, nversion=nversion, name=self.name()))) + msgbox.setTextFormat(QtCore.Qt.RichText) + msgbox.setStandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) + if msgbox.exec_() == QtWidgets.QMessageBox.Yes: + fremove(db) + else: + raise DatabaseError( + 'Incompatible import database version {} > {}'.format(nversion, version)) + def dispose(self): """ Executed whenever the plugin is going to be destroyed. @@ -1091,6 +1136,8 @@ def start(self): """ Perform initialization tasks for the plugin. """ + # VERIFY EXISTING DATABASE COMPATIBILITY + self.checkDatabase() # INITIALIZE THE WIDGETS self.myButton = QtWidgets.QToolButton(self.session, icon=QtGui.QIcon(':/icons/24/ic_system_update'), statusTip='Import OWL file') @@ -6915,7 +6962,6 @@ def addAnnotationAssertions(self, iri): CommandIRIAddAnnotationAssertion(self.project, iri, annotationAss)) - # WIDGET FORM to set Space between Items # class AbstractItemSpaceForm(QtWidgets.QDialog): """ @@ -7003,6 +7049,7 @@ def onSpaceFieldChanged(self, space): self.confirmationBox.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(enabled) self.setFixedSize(self.sizeHint()) + class SpaceForm(AbstractItemSpaceForm): def __init__(self, parent=None): @@ -7013,3 +7060,8 @@ def __init__(self, parent=None): """ super().__init__(parent) self.setWindowTitle('Set Space between ClassNodes') + + +class DatabaseError(RuntimeError): + """Raised whenever there is a problem with the import database.""" + pass diff --git a/eddy/plugins/ontology-importer/plugin.spec b/eddy/plugins/ontology-importer/plugin.spec index 1fad0314..58c38617 100644 --- a/eddy/plugins/ontology-importer/plugin.spec +++ b/eddy/plugins/ontology-importer/plugin.spec @@ -36,3 +36,6 @@ contact: fraraccio.1760072@studenti.uniroma1.it id: ontology_importer name: OntologyImporter version: 0.1 + +[database] +version: 1 From c595e2c4103564d6c0538a4d01ab937ab0bbeab3 Mon Sep 17 00:00:00 2001 From: Manuel Namici Date: Wed, 20 Jul 2022 12:58:41 -0700 Subject: [PATCH 3/9] plugins: owl_importer: rework plugin start() and implement dispose() --- .../ontology-importer/ontology_importer.py | 54 +++++++++++++------ 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/eddy/plugins/ontology-importer/ontology_importer.py b/eddy/plugins/ontology-importer/ontology_importer.py index 4601f18f..8c51b606 100644 --- a/eddy/plugins/ontology-importer/ontology_importer.py +++ b/eddy/plugins/ontology-importer/ontology_importer.py @@ -57,7 +57,7 @@ from eddy.core.functions.fsystem import fremove from eddy.core.functions.misc import isEmpty from eddy.core.functions.path import expandPath -from eddy.core.functions.signals import connect +from eddy.core.functions.signals import connect, disconnect from eddy.core.items.nodes.attribute import AttributeNode from eddy.core.items.nodes.common.label import NodeLabel from eddy.core.items.nodes.complement import ComplementNode @@ -159,6 +159,7 @@ def __init__(self, spec, session): :type session: session """ super().__init__(spec, session) + self.afwset = set() self.vm = None self.space = 150 @@ -834,9 +835,10 @@ def importPrefixes(self): command = CommandProjectAddPrefix(self.project, prefix, namespace) self.session.undostack.push(command) - def onToolbarButtonClick(self): - - ### IMPORT FILE OWL ### + def doOpenOntologyFile(self): + """ + Starts the import process by selecting an OWL 2 ontology file. + """ dialog = QtWidgets.QFileDialog(self.session.mdi, "open owl file", expandPath('~'), "owl file (*.owl)") dialog.setAcceptMode(QtWidgets.QFileDialog.AcceptOpen) @@ -1056,8 +1058,10 @@ def onToolbarButtonClick(self): except Exception as e: raise e - def onSecondButtonClick(self): - + def doOpenAxiomImportDialog(self): + """ + Opens the axioms import dialog. + """ try: # TRY TO OPEN IMPORTATIONS ASSOCIATED WITH THIS PROJECT # importation = Importation(self.project) @@ -1131,6 +1135,14 @@ def dispose(self): """ Executed whenever the plugin is going to be destroyed. """ + # DISCONNECT SIGNALS/SLOTS + self.debug('Disconnecting from active session') + disconnect(self.session.sgnNoSaveProject, self.onNoSave) + + # UNINSTALL WIDGETS FROM THE ACTIVE SESSION + self.debug('Uninstalling OWL 2 importer controls from "view" toolbar') + for action in self.afwset: + self.session.widget('view_toolbar').removeAction(action) def start(self): """ @@ -1140,21 +1152,29 @@ def start(self): self.checkDatabase() # INITIALIZE THE WIDGETS - self.myButton = QtWidgets.QToolButton(self.session, icon=QtGui.QIcon(':/icons/24/ic_system_update'), statusTip='Import OWL file') - self.myButton2 = QtWidgets.QToolButton(self.session, icon=QtGui.QIcon(':/icons/48/ic_format_list_bulleted_black'), statusTip='Select axioms from imported ontologies') - # tooltip - self.myButton.setToolTip('Import OWL file') - self.myButton2.setToolTip('Select axioms from imported ontologies') + self.debug('Creating OWL 2 importer control widgets') + # noinspection PyArgumentList + self.addWidget(QtWidgets.QToolButton( + icon=QtGui.QIcon(':/icons/24/ic_system_update'), + statusTip='Import OWL file', toolTip='Import OWL file', + enabled=True, checkable=False, clicked=self.doOpenOntologyFile, + objectName='owl2_importer_open')) + # noinspection PyArgumentList + self.addWidget(QtWidgets.QToolButton( + icon=QtGui.QIcon(':/icons/48/ic_format_list_bulleted_black'), + statusTip='Select axioms from imported ontologies', + toolTip='Select axioms from imported ontologies', + enabled=True, checkable=False, clicked=self.doOpenAxiomImportDialog, + objectName='owl2_importer_axioms')) # CREATE VIEW TOOLBAR BUTTONS - self.session.widget('view_toolbar').addSeparator() - self.session.widget('view_toolbar').addWidget(self.myButton) - # self.session.widget('view_toolbar').addSeparator() - self.session.widget('view_toolbar').addWidget(self.myButton2) + self.debug('Installing OWL 2 importer control widgets') + toolbar = self.session.widget('view_toolbar') + self.afwset.add(toolbar.addSeparator()) + self.afwset.add(toolbar.addWidget(self.widget('owl2_importer_open'))) + self.afwset.add(toolbar.addWidget(self.widget('owl2_importer_axioms'))) # CONFIGURE SIGNALS/SLOTS - connect(self.myButton.clicked, self.onToolbarButtonClick) - connect(self.myButton2.clicked, self.onSecondButtonClick) connect(self.session.sgnNoSaveProject, self.onNoSave) From 847662cf7b19614b7a38f6dbc0992de415489b18 Mon Sep 17 00:00:00 2001 From: Manuel Namici Date: Wed, 20 Jul 2022 13:02:18 -0700 Subject: [PATCH 4/9] plugins: owl_importer: use scoped access to QtCore.Qt module --- .../ontology-importer/ontology_importer.py | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/eddy/plugins/ontology-importer/ontology_importer.py b/eddy/plugins/ontology-importer/ontology_importer.py index 8c51b606..efeef16b 100644 --- a/eddy/plugins/ontology-importer/ontology_importer.py +++ b/eddy/plugins/ontology-importer/ontology_importer.py @@ -43,7 +43,6 @@ QtGui, QtWidgets ) -from PyQt5.QtCore import Qt from eddy.core.commands.common import CommandItemsRemove from eddy.core.commands.diagram import CommandDiagramResize @@ -1567,8 +1566,8 @@ def __init__(self, not_drawn, project): check = QtWidgets.QTreeWidgetItem(classLabel, [str(ax)]) basefont = check.font(0).family() check.setFont(0, QtGui.QFont(basefont, 8.7, QtGui.QFont.Normal)) - check.setFlags(Qt.ItemFlag.ItemIsUserCheckable | Qt.ItemFlag.ItemIsEnabled) - check.setCheckState(0, Qt.CheckState.Unchecked) + check.setFlags(QtCore.Qt.ItemFlag.ItemIsUserCheckable | QtCore.Qt.ItemFlag.ItemIsEnabled) + check.setCheckState(0, QtCore.Qt.CheckState.Unchecked) self.checkBoxes.append(check) @@ -1580,8 +1579,8 @@ def __init__(self, not_drawn, project): check = QtWidgets.QTreeWidgetItem(objPropLabel, [str(ax)]) basefont = check.font(0).family() check.setFont(0, QtGui.QFont(basefont, 8.7, QtGui.QFont.Normal)) - check.setFlags(Qt.ItemFlag.ItemIsUserCheckable | Qt.ItemFlag.ItemIsEnabled) - check.setCheckState(0, Qt.CheckState.Unchecked) + check.setFlags(QtCore.Qt.ItemFlag.ItemIsUserCheckable | QtCore.Qt.ItemFlag.ItemIsEnabled) + check.setCheckState(0, QtCore.Qt.CheckState.Unchecked) self.checkBoxes.append(check) @@ -1593,8 +1592,8 @@ def __init__(self, not_drawn, project): check = QtWidgets.QTreeWidgetItem(dataPropLabel, [str(ax)]) basefont = check.font(0).family() check.setFont(0, QtGui.QFont(basefont, 8.7, QtGui.QFont.Normal)) - check.setFlags(Qt.ItemFlag.ItemIsUserCheckable | Qt.ItemFlag.ItemIsEnabled) - check.setCheckState(0, Qt.CheckState.Unchecked) + check.setFlags(QtCore.Qt.ItemFlag.ItemIsUserCheckable | QtCore.Qt.ItemFlag.ItemIsEnabled) + check.setCheckState(0, QtCore.Qt.CheckState.Unchecked) self.checkBoxes.append(check) @@ -1606,8 +1605,8 @@ def __init__(self, not_drawn, project): check = QtWidgets.QTreeWidgetItem(indivLabel, [str(ax)]) basefont = check.font(0).family() check.setFont(0, QtGui.QFont(basefont, 8.7, QtGui.QFont.Normal)) - check.setFlags(Qt.ItemFlag.ItemIsUserCheckable | Qt.ItemFlag.ItemIsEnabled) - check.setCheckState(0, Qt.CheckState.Unchecked) + check.setFlags(QtCore.Qt.ItemFlag.ItemIsUserCheckable | QtCore.Qt.ItemFlag.ItemIsEnabled) + check.setCheckState(0, QtCore.Qt.CheckState.Unchecked) self.checkBoxes.append(check) @@ -1620,12 +1619,12 @@ def __init__(self, not_drawn, project): check = QtWidgets.QTreeWidgetItem(othersLabel, [str(ax)]) basefont = check.font(0).family() check.setFont(0, QtGui.QFont(basefont, 8.7, QtGui.QFont.Normal)) - check.setFlags(Qt.ItemFlag.ItemIsUserCheckable | Qt.ItemFlag.ItemIsEnabled) - check.setCheckState(0, Qt.CheckState.Unchecked) + check.setFlags(QtCore.Qt.ItemFlag.ItemIsUserCheckable | QtCore.Qt.ItemFlag.ItemIsEnabled) + check.setCheckState(0, QtCore.Qt.CheckState.Unchecked) self.checkBoxes.append(check) - self.table.sortItems(0, Qt.AscendingOrder) + self.table.sortItems(0, QtCore.Qt.AscendingOrder) self.table.setHeaderHidden(True) self.table.header().setStretchLastSection(False) @@ -1711,9 +1710,9 @@ def checkOnClick(self, index): if axiom not in self.labels: if state == QtCore.Qt.Checked: - item.setCheckState(0, Qt.CheckState.Unchecked) + item.setCheckState(0, QtCore.Qt.CheckState.Unchecked) else: - item.setCheckState(0, Qt.CheckState.Checked) + item.setCheckState(0, QtCore.Qt.CheckState.Checked) def checkAxiom(self, item, column): From 3347215391b396afdc15d347d6d507a805ba3b11 Mon Sep 17 00:00:00 2001 From: Manuel Namici Date: Wed, 20 Jul 2022 13:05:03 -0700 Subject: [PATCH 5/9] plugins: owl_importer: change name 'OntologyImporter' -> 'Ontology Importer' --- eddy/plugins/ontology-importer/plugin.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eddy/plugins/ontology-importer/plugin.spec b/eddy/plugins/ontology-importer/plugin.spec index 58c38617..8c31c08f 100644 --- a/eddy/plugins/ontology-importer/plugin.spec +++ b/eddy/plugins/ontology-importer/plugin.spec @@ -34,7 +34,7 @@ author: Maria Rosaria contact: fraraccio.1760072@studenti.uniroma1.it id: ontology_importer -name: OntologyImporter +name: Ontology Importer version: 0.1 [database] From 2718912af5f6da41230c805113e80ea480a3f973 Mon Sep 17 00:00:00 2001 From: Manuel Namici Date: Wed, 20 Jul 2022 13:58:41 -0700 Subject: [PATCH 6/9] plugins: ontology_importer: use custom file dialog and proper owl file extension As recently done for export, allow to reopen the last browsed folder and to open owl files in any of the commonly used extensions. --- eddy/plugins/ontology-importer/ontology_importer.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/eddy/plugins/ontology-importer/ontology_importer.py b/eddy/plugins/ontology-importer/ontology_importer.py index efeef16b..474ba6b0 100644 --- a/eddy/plugins/ontology-importer/ontology_importer.py +++ b/eddy/plugins/ontology-importer/ontology_importer.py @@ -53,6 +53,7 @@ from eddy.core.commands.project import CommandProjectAddAnnotationProperty, CommandProjectAddPrefix from eddy.core.common import HasWidgetSystem from eddy.core.datatypes.graphol import Item +from eddy.core.datatypes.system import File from eddy.core.functions.fsystem import fremove from eddy.core.functions.misc import isEmpty from eddy.core.functions.path import expandPath @@ -87,6 +88,7 @@ from eddy.core.owl import Literal from eddy.core.plugin import AbstractPlugin from eddy.ui.fields import IntegerField +from eddy.ui.file import FileDialog from eddy.ui.progress import BusyProgressDialog K_IMPORTS_DB = '@data/imports.sqlite' @@ -838,11 +840,11 @@ def doOpenOntologyFile(self): """ Starts the import process by selecting an OWL 2 ontology file. """ - dialog = QtWidgets.QFileDialog(self.session.mdi, "open owl file", expandPath('~'), "owl file (*.owl)") - + dialog = FileDialog(self.session) dialog.setAcceptMode(QtWidgets.QFileDialog.AcceptOpen) dialog.setFileMode(QtWidgets.QFileDialog.ExistingFile) dialog.setViewMode(QtWidgets.QFileDialog.Detail) + dialog.setNameFilters([File.Owl.value]) if dialog.exec_(): From c0f8372cd52eead45d3c02dc0480252953fe2a29 Mon Sep 17 00:00:00 2001 From: Manuel Namici Date: Wed, 20 Jul 2022 14:06:59 -0700 Subject: [PATCH 7/9] plugins: ontology_importer: immediately call super's accept in axioms dialog This avoids a delay between the user pressing the OK button and the axioms dialog closing and showing the progress report, which can leave the user confused and trying to press the OK button multiple times. --- eddy/plugins/ontology-importer/ontology_importer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eddy/plugins/ontology-importer/ontology_importer.py b/eddy/plugins/ontology-importer/ontology_importer.py index 474ba6b0..23e78d7a 100644 --- a/eddy/plugins/ontology-importer/ontology_importer.py +++ b/eddy/plugins/ontology-importer/ontology_importer.py @@ -1799,9 +1799,10 @@ def reject(self): super().reject() def accept(self): - # k: axiom (::string), value: manchester_axiom (::Axiom) # (to keep track of the string axiom to insert in DRAWN table) + super().accept() + with BusyProgressDialog('Drawing Axioms', 0.5): axiomsToDraw = {} @@ -1832,7 +1833,6 @@ def accept(self): # if axioms can't be parsed -> can't draw message # cantDraw.append(ax) - super().accept() if cantDraw: invalid = ', '.join(cantDraw) From b39616cc7ed16d883a70eb08e3ff8e069245f00b Mon Sep 17 00:00:00 2001 From: Manuel Namici Date: Wed, 20 Jul 2022 16:09:31 -0700 Subject: [PATCH 8/9] ui: forms: add NewDiagramForm.name() method to retrieve the name Avoids having to access the form internal fields which could break in the case of changes. --- eddy/ui/forms.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/eddy/ui/forms.py b/eddy/ui/forms.py index 6508e23f..0bc12073 100644 --- a/eddy/ui/forms.py +++ b/eddy/ui/forms.py @@ -475,6 +475,17 @@ def onNameFieldChanged(self, name): self.confirmationBox.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(enabled) self.setFixedSize(self.sizeHint()) + ############################################# + # INTERFACE + ################################# + + def name(self): + """ + Returns the new diagram name. + :rtype: str + """ + return self.nameField.value() + class NewDiagramForm(AbstractDiagramForm): """ From 35f865dadc506e1739895df49e0e0a3312c06361 Mon Sep 17 00:00:00 2001 From: Manuel Namici Date: Wed, 20 Jul 2022 16:10:56 -0700 Subject: [PATCH 9/9] plugins: ontology_importer: rework the new diagram dialog interface Merge the diagram creation with the spacing form into a single diagram properties form which allows also to set the diagram dimension. One less dialog to click through. --- .../ontology-importer/ontology_importer.py | 169 ++++++++---------- 1 file changed, 77 insertions(+), 92 deletions(-) diff --git a/eddy/plugins/ontology-importer/ontology_importer.py b/eddy/plugins/ontology-importer/ontology_importer.py index 23e78d7a..e23b0ca8 100644 --- a/eddy/plugins/ontology-importer/ontology_importer.py +++ b/eddy/plugins/ontology-importer/ontology_importer.py @@ -32,7 +32,6 @@ # # ########################################################################## -from abc import ABCMeta import ast import os import sqlite3 @@ -45,7 +44,7 @@ ) from eddy.core.commands.common import CommandItemsRemove -from eddy.core.commands.diagram import CommandDiagramResize +from eddy.core.commands.diagram import CommandDiagramResize, CommandDiagramAdd from eddy.core.commands.edges import CommandEdgeAdd from eddy.core.commands.iri import CommandChangeIRIOfNode from eddy.core.commands.iri import CommandIRIAddAnnotationAssertion @@ -54,8 +53,8 @@ from eddy.core.common import HasWidgetSystem from eddy.core.datatypes.graphol import Item from eddy.core.datatypes.system import File +from eddy.core.diagram import Diagram from eddy.core.functions.fsystem import fremove -from eddy.core.functions.misc import isEmpty from eddy.core.functions.path import expandPath from eddy.core.functions.signals import connect, disconnect from eddy.core.items.nodes.attribute import AttributeNode @@ -87,8 +86,8 @@ from eddy.core.owl import IRI from eddy.core.owl import Literal from eddy.core.plugin import AbstractPlugin -from eddy.ui.fields import IntegerField from eddy.ui.file import FileDialog +from eddy.ui.forms import NewDiagramForm from eddy.ui.progress import BusyProgressDialog K_IMPORTS_DB = '@data/imports.sqlite' @@ -847,18 +846,20 @@ def doOpenOntologyFile(self): dialog.setNameFilters([File.Owl.value]) if dialog.exec_(): - self.filePath = dialog.selectedFiles() ### SET SPACE BETWEEN ITEMS ### - space_form = SpaceForm(self.session) - if space_form.exec_(): - - self.space = space_form.spaceField.value() + form = DiagramPropertiesForm(self.project, parent=self.session) + if form.exec_(): + self.space = form.spacing() ### CREATE NEW DIAGRAM ### - self.session.doNewDiagram() - diagram = self.session.mdi.activeDiagram() + diagram = Diagram.create(form.name(), form.diagramSize(), self.project) + connect(diagram.sgnItemAdded, self.project.doAddItem) + connect(diagram.sgnItemRemoved, self.project.doRemoveItem) + connect(diagram.selectionChanged, self.session.doUpdateState) + self.session.undostack.push(CommandDiagramAdd(diagram, self.project)) + self.session.sgnFocusDiagram.emit(diagram) self.vm = getJavaVM() if not self.vm.isRunning(): @@ -1068,8 +1069,8 @@ def doOpenAxiomImportDialog(self): importation = Importation(self.project) axs, not_dr, dr = importation.open() if not_dr: - window = AxiomsWindow(not_dr, self.project) - window.exec_() + dialog = AxiomSelectionDialog(not_dr, self.project) + dialog.exec_() else: msgbox = QtWidgets.QMessageBox() msgbox.setIconPixmap(QtGui.QIcon(':/icons/48/ic_warning_black').pixmap(48)) @@ -1433,7 +1434,7 @@ def removeFromDB(self): conn.commit() -class AxiomsWindow(QtWidgets.QDialog, HasWidgetSystem): +class AxiomSelectionDialog(QtWidgets.QDialog, HasWidgetSystem): def __init__(self, not_drawn, project): @@ -6983,104 +6984,88 @@ def addAnnotationAssertions(self, iri): CommandIRIAddAnnotationAssertion(self.project, iri, annotationAss)) -# WIDGET FORM to set Space between Items # -class AbstractItemSpaceForm(QtWidgets.QDialog): +class DiagramPropertiesForm(NewDiagramForm): """ - Base class for diagram dialogs. + Subclass of `NewDiagramForm` which allows also to input the + spacing between class items in the generated diagram. """ - __metaclass__ = ABCMeta + MinSize = 5000 + MaxSize = 50000 + MinSpace = 150 + MaxSpace = 500 - def __init__(self, parent=None): + def __init__(self, project=None, **kwargs): """ - Initialize the dialog. - :type parent: QtWidgets.QWidget + Initialize the new diagram properties dialog. """ - super().__init__(parent) - - - ################################# - # FORM AREA - ################################# - - self.spaceField = IntegerField(self) - self.spaceField.setMinimumWidth(400) - self.spaceField.setMaxLength(4) - self.spaceField.setPlaceholderText('Space...') - connect(self.spaceField.textChanged, self.onSpaceFieldChanged) - - self.warnLabel = QtWidgets.QLabel(self) - self.warnLabel.setContentsMargins(0, 0, 0, 0) - self.warnLabel.setProperty('class', 'invalid') - self.warnLabel.setVisible(False) - - ############################################# - # CONFIRMATION AREA - ################################# - - self.confirmationBox = QtWidgets.QDialogButtonBox(QtCore.Qt.Horizontal, self) - self.confirmationBox.addButton(QtWidgets.QDialogButtonBox.Ok) - self.confirmationBox.addButton(QtWidgets.QDialogButtonBox.Cancel) - self.confirmationBox.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(False) - - ############################################# - # SETUP DIALOG LAYOUT - ################################# - - self.mainLayout = QtWidgets.QVBoxLayout(self) - self.mainLayout.setContentsMargins(10, 10, 10, 10) - self.mainLayout.addWidget(self.spaceField) - self.mainLayout.addWidget(self.warnLabel) - self.mainLayout.addWidget(self.confirmationBox, 0, QtCore.Qt.AlignRight) + super().__init__(project, **kwargs) + self.sizeLabel = QtWidgets.QLabel('Diagram size:', self) + # noinspection PyArgumentList + self.sizeSlider = QtWidgets.QSlider( + QtCore.Qt.Horizontal, self, toolTip='New diagram size', + minimum=DiagramPropertiesForm.MinSize, maximum=DiagramPropertiesForm.MaxSize, + objectName='size_slider') + self.sizeValue = QtWidgets.QLabel(f'{self.sizeSlider.value()} px', self) + self.spaceLabel = QtWidgets.QLabel('Item spacing:', self) + # noinspection PyArgumentList + self.spaceSlider = QtWidgets.QSlider( + QtCore.Qt.Horizontal, self, toolTip='Spacing between class items', + minimum=DiagramPropertiesForm.MinSpace, maximum=DiagramPropertiesForm.MaxSpace, + objectName='spacing_slider') + self.valueLabel = QtWidgets.QLabel(f'{self.spaceSlider.value()} px', self) + + self.propertiesGroup = QtWidgets.QGroupBox('Properties', self) + self.sizeLayout = QtWidgets.QHBoxLayout() + self.sizeLayout.addWidget(self.sizeLabel) + self.sizeLayout.addWidget(self.sizeSlider) + self.sizeLayout.addWidget(self.sizeValue) + self.spacingLayout = QtWidgets.QHBoxLayout() + self.spacingLayout.addWidget(self.spaceLabel) + self.spacingLayout.addWidget(self.spaceSlider) + self.spacingLayout.addWidget(self.valueLabel) + self.propertiesLayout = QtWidgets.QVBoxLayout(self.propertiesGroup) + self.propertiesLayout.addLayout(self.sizeLayout) + self.propertiesLayout.addLayout(self.spacingLayout) + + self.mainLayout.insertWidget(self.mainLayout.indexOf(self.warnLabel), self.propertiesGroup) + self.mainLayout.insertSpacing(self.mainLayout.indexOf(self.warnLabel), 4) self.setFixedSize(self.sizeHint()) - self.setWindowIcon(QtGui.QIcon(':/icons/128/ic_eddy')) - - connect(self.confirmationBox.accepted, self.accept) - connect(self.confirmationBox.rejected, self.reject) + self.setWindowTitle('Set new diagram properties') + connect(self.sizeSlider.valueChanged, self.onSizeChanged) + connect(self.spaceSlider.valueChanged, self.onSpacingChanged) ############################################# # SLOTS ################################# - @QtCore.pyqtSlot(str) - def onSpaceFieldChanged(self, space): + def onSizeChanged(self, value: int) -> None: """ - Executed when the content of the input field changes. - :type space: int + Executed when the size slider value changes. """ - enabled = False - caption = '' - - if space != '': - - space = int(space.strip()) - if not space: - caption = '' - enabled = False - else: - if space < 130 or space > 500: - caption = "Space must be integer in range [130, 500]" - enabled = False - else: - caption = '' - enabled = True + self.sizeValue.setText(f'{self.diagramSize()} px') - self.warnLabel.setText(caption) - self.warnLabel.setVisible(not isEmpty(caption)) - self.confirmationBox.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(enabled) - self.setFixedSize(self.sizeHint()) + def onSpacingChanged(self, value: int) -> None: + """ + Executed when the spacing slider value changes. + """ + self.valueLabel.setText(f'{self.spacing()} px') + ############################################# + # INTERFACE + ################################# -class SpaceForm(AbstractItemSpaceForm): + def diagramSize(self) -> int: + """ + Returns the currently selected diagram size. + """ + return self.sizeSlider.value() - def __init__(self, parent=None): + def spacing(self) -> int: """ - Initialize the new diagram dialog. - :type project: Project - :type parent: QtWidgets.QWidget + Returns the currently selected spacing. """ - super().__init__(parent) - self.setWindowTitle('Set Space between ClassNodes') + return self.spaceSlider.value() class DatabaseError(RuntimeError):