diff --git a/eddy/core/diagram.py b/eddy/core/diagram.py
index 83974ea7..da8c1282 100644
--- a/eddy/core/diagram.py
+++ b/eddy/core/diagram.py
@@ -96,6 +96,7 @@ class Diagram(QtWidgets.QGraphicsScene):
Extension of QtWidgets.QGraphicsScene which implements a single Graphol diagram.
Additionally to built-in signals, this class emits:
+ * sgnDragDropEvent: whenever data is dropped onto the Diagram.
* sgnItemAdded: whenever an element is added to the Diagram.
* sgnItemInsertionCompleted: whenever an item 'MANUAL' insertion process is completed.
* sgnItemRemoved: whenever an element is removed from the Diagram.
@@ -110,6 +111,7 @@ class Diagram(QtWidgets.QGraphicsScene):
MaxFontSize = 40
SelectionRadius = 4
+ sgnDragDropEvent = QtCore.pyqtSignal(QtWidgets.QGraphicsScene, QtWidgets.QGraphicsSceneDragDropEvent)
sgnItemAdded = QtCore.pyqtSignal(QtWidgets.QGraphicsScene, QtWidgets.QGraphicsItem)
sgnItemInsertionCompleted = QtCore.pyqtSignal(QtWidgets.QGraphicsItem, int)
sgnItemRemoved = QtCore.pyqtSignal(QtWidgets.QGraphicsScene, QtWidgets.QGraphicsItem)
@@ -235,15 +237,25 @@ def dropEvent(self, dropEvent: QtWidgets.QGraphicsSceneDragDropEvent) -> None:
Executed when a dragged element is dropped on the diagram.
"""
super().dropEvent(dropEvent)
- if dropEvent.mimeData().hasFormat('text/plain') and Item.valueOf(dropEvent.mimeData().text()):
+ self.sgnDragDropEvent.emit(self, dropEvent)
+ if (
+ dropEvent.dropAction() == QtCore.Qt.DropAction.CopyAction
+ and dropEvent.mimeData().hasFormat('text/plain')
+ and Item.valueOf(dropEvent.mimeData().text())
+ ):
snapToGrid = self.session.action('toggle_grid').isChecked()
node = self.factory.create(Item.valueOf(dropEvent.mimeData().text()))
node.setPos(snap(dropEvent.scenePos(), Diagram.GridSize, snapToGrid))
data = dropEvent.mimeData().data(dropEvent.mimeData().text())
- if int(dropEvent.mimeData().text()) in {Item.ConceptNode, Item.AttributeNode,
- Item.RoleNode, Item.IndividualNode,
- Item.ValueDomainNode, Item.LiteralNode,
- Item.FacetNode}:
+ if Item.valueOf(dropEvent.mimeData().text()) in {
+ Item.ConceptNode,
+ Item.AttributeNode,
+ Item.RoleNode,
+ Item.IndividualNode,
+ Item.ValueDomainNode,
+ Item.LiteralNode,
+ Item.FacetNode,
+ }:
if not data:
# For new nodes (e.g. drag and drop from palette)
if isinstance(node, FacetNode):
diff --git a/eddy/core/exporters/owl2.py b/eddy/core/exporters/owl2.py
index 836dfe96..2542a898 100644
--- a/eddy/core/exporters/owl2.py
+++ b/eddy/core/exporters/owl2.py
@@ -89,10 +89,6 @@
)
from eddy.core.functions.signals import connect
from eddy.core.jvm import getJavaVM
-from eddy.core.metadata import (
- LiteralValue,
- NamedEntity,
-)
from eddy.core.ndc import (
ADMS,
NDCDataset,
@@ -481,17 +477,6 @@ def onErrored(self, exception):
self.reject()
- @QtCore.pyqtSlot(str)
- def onMetadataFetchErrored(self, message):
- """
- Executed when a metadata fetch request fails.
- :type message: str
- """
- self.session.addNotification(textwrap.dedent(f"""
- ERROR:\n
- {message}
- """))
-
@QtCore.pyqtSlot(str)
def onNDCMetadataMissing(self, uri):
"""
@@ -570,7 +555,6 @@ def run(self):
connect(worker.sgnStarted, self.onStarted)
connect(worker.sgnCompleted, self.onCompleted)
connect(worker.sgnErrored, self.onErrored)
- connect(worker.sgnMetadataFetchErrored, self.onMetadataFetchErrored)
connect(worker.sgnNDCMetadataMissing, self.onNDCMetadataMissing)
connect(worker.sgnProgress, self.onProgress)
self.startThread('OWL2Export', worker)
@@ -582,7 +566,6 @@ class OWLOntologyExporterWorker(AbstractWorker):
"""
sgnCompleted = QtCore.pyqtSignal()
sgnErrored = QtCore.pyqtSignal(Exception)
- sgnMetadataFetchErrored = QtCore.pyqtSignal(str)
sgnNDCMetadataMissing = QtCore.pyqtSignal(str)
sgnProgress = QtCore.pyqtSignal(int, int)
sgnStarted = QtCore.pyqtSignal()
@@ -638,7 +621,6 @@ def __init__(self, project, path=None, **kwargs):
self._axioms = set()
self._converted = dict()
self._converted_meta_individuals = dict()
- self.metadataProperty = self.project.getIRI('urn:x-graphol:origin')
self.df = None
self.man = None
@@ -1405,43 +1387,8 @@ def createAnnotationAssertionAxioms(self, node):
if OWLAxiom.Annotation in self.axiomsList:
for annotation in node.iri.annotationAssertions:
subject = self.IRI.create(str(annotation.subject))
- if annotation.assertionProperty == self.metadataProperty:
- uri = annotation.value
- # FIXME: this is a sync request!!!
- result, response = self.nmanager.getSync(str(uri))
- if not result:
- msg = f'Retrieval of {uri} failed: {response}'
- LOGGER.warning(msg)
- self.sgnMetadataFetchErrored.emit(msg)
- continue
- try:
- for assertion in NamedEntity.from_dict(json.loads(response)).annotations:
- if isinstance(assertion.object, LiteralValue):
- from eddy.core.owl import Annotation
- value = Annotation(
- self.project.getIRI(str(assertion.predicate.iri)),
- assertion.object.value,
- type=assertion.object.datatype,
- language=assertion.object.language,
- parent=self.project,
- )
- elif isinstance(assertion.object, NamedEntity):
- value = Annotation(
- self.project.getIRI(str(assertion.predicate.iri)),
- self.project.getIRI(str(assertion.object.iri)),
- parent=self.project,
- )
- else:
- LOGGER.warning(f'Skipping annotation with bnode object {assertion}')
- value = self.getOWLApiAnnotation(value)
- self.addAxiom(self.df.getOWLAnnotationAssertionAxiom(subject, value))
- except Exception as e:
- msg = f'Failed to parse metadata for {uri}'
- LOGGER.warning(f'{msg}: {response}')
- self.sgnMetadataFetchErrored.emit(f'{msg}: See log for details.')
- else:
- value = self.getOWLApiAnnotation(annotation)
- self.addAxiom(self.df.getOWLAnnotationAssertionAxiom(subject, value))
+ value = self.getOWLApiAnnotation(annotation)
+ self.addAxiom(self.df.getOWLAnnotationAssertionAxiom(subject, value))
def createClassAssertionAxiom(self, edge):
"""
diff --git a/eddy/core/metadata.py b/eddy/core/metadata.py
deleted file mode 100644
index d8059221..00000000
--- a/eddy/core/metadata.py
+++ /dev/null
@@ -1,435 +0,0 @@
-# -*- coding: utf-8 -*-
-
-##########################################################################
-# #
-# Eddy: a graphical editor for the specification of Graphol ontologies #
-# Copyright (C) 2015 Daniele Pantaleone #
-# #
-# This program is free software: you can redistribute it and/or modify #
-# it under the terms of the GNU General Public License as published by #
-# the Free Software Foundation, either version 3 of the License, or #
-# (at your option) any later version. #
-# #
-# This program is distributed in the hope that it will be useful, #
-# but WITHOUT ANY WARRANTY; without even the implied warranty of #
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
-# GNU General Public License for more details. #
-# #
-# You should have received a copy of the GNU General Public License #
-# along with this program. If not, see . #
-# #
-# ##################### ##################### #
-# #
-# Graphol is developed by members of the DASI-lab group of the #
-# Dipartimento di Ingegneria Informatica, Automatica e Gestionale #
-# A.Ruberti at Sapienza University of Rome: http://www.dis.uniroma1.it #
-# #
-# - Domenico Lembo #
-# - Valerio Santarelli #
-# - Domenico Fabio Savo #
-# - Daniele Pantaleone #
-# - Marco Console #
-# #
-##########################################################################
-
-from __future__ import annotations
-
-from abc import (
- ABCMeta,
- abstractmethod,
-)
-from enum import unique
-from typing import (
- List,
- Optional,
- cast,
-)
-
-from PyQt5 import (
- QtCore,
- QtNetwork,
-)
-from rdflib import (
- BNode,
- Graph,
- IdentifiedNode,
- Literal,
- URIRef,
-)
-
-from eddy.core.datatypes.common import IntEnum_
-
-# Graph instance used to keep track of namespaces
-K_GRAPH = Graph(bind_namespaces='none')
-
-
-class RepositoryMonitor(QtCore.QObject):
- """
- This class can be used to listen for changes in the saved repository list.
- """
- sgnUpdated = QtCore.pyqtSignal()
- _instance = None
-
- def __new__(cls):
- """
- Implements the singleton pattern.
- """
- if cls._instance is None:
- cls._instance = super().__new__(cls)
- return cls._instance
-
-
-K_REPO_MONITOR = RepositoryMonitor()
-
-
-@unique
-class MetadataRequest(IntEnum_):
- """
- This class defines the attributes used in metadata REST requests.
- """
- RepositoryAttribute = QtNetwork.QNetworkRequest.Attribute(7001)
-
-
-class Repository:
- """
- A repository of metadata as a RESTful API endpoint.
- """
-
- def __init__(self, name: str, uri: URIRef | str):
- """Initialize the repository instance."""
- self._name = name
- self._uri = uri
- # TODO: Authentication mechanims will be added later
-
- @property
- def name(self):
- """Returns the repository name."""
- return self._name
-
- @property
- def uri(self):
- """Returns the repository uri."""
- return self._uri
-
- #############################################
- # INTERFACE
- #################################
-
- @classmethod
- def load(cls) -> List[Repository]:
- """Load the repositories list from user preferences."""
- repos = [] # type: List[Repository]
- settings = QtCore.QSettings()
- for index in range(settings.beginReadArray('metadata/repositories')):
- settings.setArrayIndex(index)
- repos.append(Repository(
- name=settings.value('name'),
- uri=settings.value('uri'),
- ))
- return repos
-
- @classmethod
- def save(cls, repositories: List[Repository]) -> None:
- """Save the repository in the user preferences."""
- settings = QtCore.QSettings()
- settings.beginWriteArray('metadata/repositories')
- for index, repo in enumerate(repositories):
- settings.setArrayIndex(index)
- settings.setValue('name', repo.name)
- settings.setValue('uri', repo.uri)
- settings.endArray()
- K_REPO_MONITOR.sgnUpdated.emit()
-
-
-class Node(metaclass=ABCMeta):
- """
- Base class for any API object.
- """
-
- @classmethod
- @abstractmethod
- def from_dict(cls, data: dict, **kwargs: dict) -> Node:
- """Creates a new node from the given dict."""
- pass
-
- @abstractmethod
- def to_dict(self, deep: bool = False) -> dict:
- """Serializes the object to a dict."""
- pass
-
- @abstractmethod
- def n3(self) -> str:
- """Returns the N-TRIPLES (n3) representation of the object."""
- pass
-
-
-class Entity(Node, metaclass=ABCMeta):
- """
- Base class for any named or unnamed entity.
- """
-
- def __init__(self, id_: str) -> None:
- """Initialize the entity."""
- super().__init__()
- self._id = id_
- self._types = []
- self._annotations = []
-
- @property
- def id(self) -> str:
- """Return the identifier for this entity."""
- return self._id
-
- @property
- @abstractmethod
- def name(self) -> IdentifiedNode:
- """Return the IRI or BNode for this entity."""
- pass
-
- @property
- def types(self) -> List[NamedEntity]:
- """Return the list of types of this entity."""
- return self._types
-
- @property
- def annotations(self) -> List[Annotation]:
- """Return the list of annotations of this entity."""
- return self._annotations
-
- def __eq__(self, other: Entity) -> bool:
- return self.__class__ == other.__class__ and self.id == other.id
-
- def __repr__(self) -> str:
- return f"{self.__class__.__name__}({self.id})"
-
-
-class NamedEntity(Entity):
- """
- Represents entities that are uniquely identified with an IRI.
- """
-
- def __init__(self, id_: str, iri: URIRef | str) -> None:
- """Initialize the named entity."""
- super().__init__(id_)
- self._iri = iri if isinstance(iri, URIRef) else URIRef(iri)
-
- @property
- def iri(self) -> URIRef:
- """Return the iri associated with this named entity."""
- return self._iri
-
- @property
- def name(self) -> IdentifiedNode:
- return self.iri
-
- @classmethod
- def from_dict(cls, data: dict, **kwargs) -> Node:
- ent = NamedEntity(data["id"], data["iri"]) # noqa
- if "types" in data:
- for t in data["types"]:
- ent.types.append(cast(NamedEntity, NamedEntity.from_dict(t)))
- if "annotations" in data:
- for a in data["annotations"]:
- ant = cast(Annotation, Annotation.from_dict(a, subject=ent.to_dict()))
- ent.annotations.append(ant)
- return ent
-
- def to_dict(self, deep: bool = False) -> dict:
- res = {
- "id": self.id,
- "iri": self.name,
- }
- if deep:
- res |= {
- "types": [t.to_dict() for t in self.types],
- "annotations": [a.to_dict() for a in self.annotations],
- }
- return res
-
- def n3(self) -> str:
- return self.iri.n3(K_GRAPH.namespace_manager)
-
- def __eq__(self, other: Entity) -> bool:
- return super().__eq__(other) and self.iri == cast(NamedEntity, other).iri
-
- def __repr__(self) -> str:
- return f"{self.__class__.__name__}({self.id}, \"{self.iri}\")"
-
-
-class AnonymousEntity(Entity):
- """
- Represents entities not identified by an IRI (i.e. blank nodes in RDF).
- """
-
- def __init__(self, id_: str, bnode: BNode | str) -> None:
- """Initialize the anonymous entity."""
- super().__init__(id_)
- self._bnode = bnode if isinstance(bnode, BNode) else BNode(bnode)
-
- @property
- def bnode(self) -> BNode:
- """Returns the blank node associated with this entity."""
- return self._bnode
-
- @property
- def name(self) -> IdentifiedNode:
- return self.bnode
-
- @classmethod
- def from_dict(cls, data: dict, **kwargs) -> Node:
- ent = NamedEntity(data["id"], data["bnode"]) # noqa
- if "types" in data:
- for t in data["types"]:
- ent.types.append(cast(NamedEntity, NamedEntity.from_dict(t)))
- if "annotations" in data:
- for a in data["annotations"]:
- ent.annotations.append(
- cast(Annotation, Annotation.from_dict(a, subject=ent.to_dict())))
- return ent
-
- def to_dict(self, deep: bool = False) -> dict:
- res = {
- "id": self.id,
- "bnode": self.name,
- }
- if deep:
- res |= {
- "types": [t.to_dict() for t in self.types],
- "annotations": [a.to_dict() for a in self.annotations],
- }
- return res
-
- def n3(self) -> str:
- return self.bnode.n3(K_GRAPH.namespace_manager)
-
- def __eq__(self, other: Entity) -> bool:
- return super().__eq__(other) and self.bnode == cast(AnonymousEntity, other).bnode
-
- def __repr__(self) -> str:
- return f"{self.__class__.__name__}({self.id}, \"{self.bnode}\")"
-
-
-class LiteralValue(Node):
- """
- Represent a literal value associated with an annotation assertion.
- """
-
- def __init__(self, value: str, language: Optional[str] = None,
- datatype: Optional[URIRef | str] = None) -> None:
- """Initialize the literal."""
- super().__init__()
- self._value = value
- self._language = language
- self._datatype = datatype
- # Check that literal is well-formed
- if self._language is not None and self._datatype is not None:
- raise ValueError("Literals with a datatype cannot have a language tag.")
-
- @property
- def value(self) -> str:
- """Return the value of this literal."""
- return self._value
-
- @property
- def language(self) -> Optional[str]:
- """Return the language tag of this literal, or `None` if there is no tag."""
- return self._language
-
- @property
- def datatype(self) -> Optional[URIRef | str]:
- """Return the datatype of this literal, or `None` if there is no datatype."""
- return self._datatype
-
- @classmethod
- def from_dict(cls, data: dict, **kwargs) -> Node:
- return cls(data["value"], data["language"], data["datatype"]) # noqa
-
- def to_dict(self, deep: bool = False) -> dict:
- return {
- "value": self.value,
- "language": self.language,
- "datatype": self.datatype
- }
-
- def n3(self) -> str:
- return Literal(self.value, self.language, self.datatype).n3(K_GRAPH.namespace_manager)
-
- def __eq__(self, other: Entity) -> bool:
- return (
- super().__eq__(other)
- and self.value == cast(LiteralValue, other).value
- and self.language == cast(LiteralValue, other).language
- and self.datatype == cast(LiteralValue, other).datatype
- )
-
- def __repr__(self) -> str:
- if self.language:
- return f"{self.__class__.__name__}(\"{self.value}\"@{self.language})"
- else:
- return f"{self.__class__.__name__}(\"{self.value}\"^^{self.datatype})"
-
-
-class Annotation(Node):
- """
- Represent an annotation assertion.
- """
-
- def __init__(self, subject: Entity, predicate: NamedEntity, object: Node) -> None:
- """Initialize the annotation."""
- self._subject = subject
- self._predicate = predicate
- self._object = object
-
- @property
- def subject(self) -> Entity:
- """Return the annotation subject."""
- return self._subject
-
- @property
- def predicate(self) -> NamedEntity:
- """Return the annotation predicate."""
- return self._predicate
-
- @property
- def object(self) -> Node:
- """Return the annotation object."""
- return self._object
-
- @classmethod
- def from_dict(cls, data: dict, **kwargs) -> Node:
- s = kwargs["subject"]
- p = data["property"]
- v = data["value"]
-
- sub = NamedEntity.from_dict(s) if "iri" in s else AnonymousEntity.from_dict(s)
- prop = NamedEntity.from_dict(p)
-
- if "id" in v:
- obj = NamedEntity.from_dict(v) if "iri" in v else AnonymousEntity.from_dict(v)
- else:
- obj = LiteralValue.from_dict(v)
-
- return Annotation(cast(Entity, sub), cast(NamedEntity, prop), obj)
-
- def to_dict(self, deep: bool = False) -> dict:
- return {
- "property": self.predicate.to_dict(),
- "value": self.object.to_dict()
- }
-
- def n3(self) -> str:
- sub = self.subject.n3()
- pred = self.predicate.n3()
- obj = self.object.n3()
- return f'{sub} {pred} {obj}'
-
- def __eq__(self, other: Annotation) -> bool:
- return (
- self.__class__ == other.__class__
- and self.subject == other.subject
- and self.predicate == other.predicate
- and self.object == other.object
- )
-
- def __repr__(self) -> str:
- return f"{self.__class__.__name__}({self.subject}, {self.predicate}, {self.object})"
diff --git a/eddy/plugins/metadata-importer/metadata_importer/__init__.py b/eddy/plugins/metadata-importer/metadata_importer/__init__.py
deleted file mode 100644
index 4328c9b3..00000000
--- a/eddy/plugins/metadata-importer/metadata_importer/__init__.py
+++ /dev/null
@@ -1,167 +0,0 @@
-# -*- coding: utf-8 -*-
-
-##########################################################################
-# #
-# Eddy: a graphical editor for the specification of Graphol ontologies #
-# Copyright (C) 2015 Daniele Pantaleone #
-# #
-# This program is free software: you can redistribute it and/or modify #
-# it under the terms of the GNU General Public License as published by #
-# the Free Software Foundation, either version 3 of the License, or #
-# (at your option) any later version. #
-# #
-# This program is distributed in the hope that it will be useful, #
-# but WITHOUT ANY WARRANTY; without even the implied warranty of #
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
-# GNU General Public License for more details. #
-# #
-# You should have received a copy of the GNU General Public License #
-# along with this program. If not, see . #
-# #
-# ##################### ##################### #
-# #
-# Graphol is developed by members of the DASI-lab group of the #
-# Dipartimento di Ingegneria Informatica, Automatica e Gestionale #
-# A.Ruberti at Sapienza University of Rome: http://www.dis.uniroma1.it #
-# #
-# - Domenico Lembo #
-# - Valerio Santarelli #
-# - Domenico Fabio Savo #
-# - Daniele Pantaleone #
-# - Marco Console #
-# #
-##########################################################################
-
-from PyQt5 import (
- QtCore,
- QtGui,
-)
-
-from eddy.core.functions.signals import connect, disconnect
-from eddy.core.metadata import K_REPO_MONITOR
-from eddy.core.output import getLogger
-from eddy.core.plugin import AbstractPlugin
-from eddy.ui.dock import DockWidget
-from .widgets import MetadataImporterWidget # noqa
-
-LOGGER = getLogger()
-
-
-class MetadataImporterPlugin(AbstractPlugin):
- """
- Search and import ontology metadata from an external service.
- """
- sgnProjectChanged = QtCore.pyqtSignal(str)
- sgnUpdateState = QtCore.pyqtSignal()
-
- def __init__(self, spec, session):
- """
- Initialises a new instance of the metadata importer plugin.
- :type spec: PluginSpec
- :type session: Session
- """
- super().__init__(spec, session)
-
- #############################################
- # EVENTS
- #################################
-
- def eventFilter(self, source: QtCore.QObject, event: QtCore.QEvent) -> bool:
- """
- Filters events if this object has been installed as an event filter for the watched object.
- """
- if event.type() == QtCore.QEvent.Resize:
- widget = source.widget()
- widget.redraw()
- return super().eventFilter(source, event)
-
- #############################################
- # SLOTS
- #################################
-
- @QtCore.pyqtSlot()
- def onSessionReady(self):
- """
- Executed whenever the main session completes the startup sequence.
- """
- widget = self.widget('metadata_importer') # type: MetadataImporterWidget
- # CONNECT TO PROJECT SPECIFIC SIGNALS
- self.debug('Connecting to project: %s', self.project.name)
- connect(self.project.sgnPrefixAdded, widget.onPrefixChanged)
- connect(self.project.sgnPrefixModified, widget.onPrefixChanged)
- connect(self.project.sgnPrefixRemoved, widget.onPrefixChanged)
- connect(K_REPO_MONITOR.sgnUpdated, widget.doUpdateState)
- widget.onPrefixChanged()
- widget.doUpdateState()
-
- @QtCore.pyqtSlot()
- def doUpdateState(self):
- """
- Executed when the plugin session updates its state.
- """
- pass
-
- #############################################
- # HOOKS
- #################################
-
- def dispose(self):
- """
- Executed whenever the plugin is going to be destroyed.
- """
- # DISCONNECT FROM CURRENT PROJECT
- widget = self.widget('metadata_importer') # type: MetadataImporterWidget
- self.debug('Disconnecting from project: %s', self.project.name)
- disconnect(self.project.sgnPrefixAdded, widget.onPrefixChanged)
- disconnect(self.project.sgnPrefixModified, widget.onPrefixChanged)
- disconnect(self.project.sgnPrefixRemoved, widget.onPrefixChanged)
- disconnect(K_REPO_MONITOR.sgnUpdated, widget.doUpdateState)
-
- # DISCONNECT FROM ACTIVE SESSION
- self.debug('Disconnecting from active session')
- disconnect(self.session.sgnReady, self.onSessionReady)
- disconnect(self.session.sgnUpdateState, self.doUpdateState)
-
- def start(self):
- """
- Perform initialization tasks for the plugin.
- """
- # INITIALIZE THE WIDGET
- self.debug('Starting Metadata Importer plugin')
- widget = MetadataImporterWidget(self)
- widget.setObjectName('metadata_importer')
- self.addWidget(widget)
-
- # CREATE DOCKING AREA WIDGET
- self.debug('Creating docking area widget')
- widget = DockWidget('Metadata Importer', QtGui.QIcon(':icons/18/ic_explore_black'), self.session)
- widget.installEventFilter(self)
- widget.setAllowedAreas(
- QtCore.Qt.LeftDockWidgetArea
- | QtCore.Qt.RightDockWidgetArea
- | QtCore.Qt.BottomDockWidgetArea
- )
- widget.setObjectName('metadata_importer_dock')
- widget.setWidget(self.widget('metadata_importer'))
- self.addWidget(widget)
-
- # CREATE SHORTCUTS
- action = widget.toggleViewAction()
- action.setParent(self.session)
- action.setShortcut(QtGui.QKeySequence('Alt+8'))
-
- # CREATE ENTRY IN VIEW MENU
- self.debug('Creating docking area widget toggle in "view" menu')
- menu = self.session.menu('view')
- menu.addAction(self.widget('metadata_importer_dock').toggleViewAction())
-
- # INSTALL DOCKING AREA WIDGET
- self.debug('Installing docking area widget')
- self.session.addDockWidget(
- QtCore.Qt.LeftDockWidgetArea,
- self.widget('metadata_importer_dock'),
- )
-
- # CONFIGURE SIGNAL/SLOTS
- connect(self.session.sgnReady, self.onSessionReady)
- connect(self.session.sgnUpdateState, self.doUpdateState)
diff --git a/eddy/plugins/metadata-importer/metadata_importer/widgets.py b/eddy/plugins/metadata-importer/metadata_importer/widgets.py
deleted file mode 100644
index 67842f85..00000000
--- a/eddy/plugins/metadata-importer/metadata_importer/widgets.py
+++ /dev/null
@@ -1,962 +0,0 @@
-# -*- coding: utf-8 -*-
-
-##########################################################################
-# #
-# Eddy: a graphical editor for the specification of Graphol ontologies #
-# Copyright (C) 2015 Daniele Pantaleone #
-# #
-# This program is free software: you can redistribute it and/or modify #
-# it under the terms of the GNU General Public License as published by #
-# the Free Software Foundation, either version 3 of the License, or #
-# (at your option) any later version. #
-# #
-# This program is distributed in the hope that it will be useful, #
-# but WITHOUT ANY WARRANTY; without even the implied warranty of #
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
-# GNU General Public License for more details. #
-# #
-# You should have received a copy of the GNU General Public License #
-# along with this program. If not, see . #
-# #
-# ##################### ##################### #
-# #
-# Graphol is developed by members of the DASI-lab group of the #
-# Dipartimento di Ingegneria Informatica, Automatica e Gestionale #
-# A.Ruberti at Sapienza University of Rome: http://www.dis.uniroma1.it #
-# #
-# - Domenico Lembo #
-# - Valerio Santarelli #
-# - Domenico Fabio Savo #
-# - Daniele Pantaleone #
-# - Marco Console #
-# #
-##########################################################################
-
-from abc import (
- ABCMeta,
- abstractmethod,
-)
-import json
-import textwrap
-from typing import (
- Any,
- cast,
-)
-
-from PyQt5 import (
- QtCore,
- QtGui,
- QtNetwork,
- QtWidgets,
-)
-from rdflib import Graph
-from rdflib.namespace import NamespaceManager
-
-from eddy.core.commands.iri import CommandIRIAddAnnotationAssertion
-from eddy.core.datatypes.graphol import Item
-from eddy.core.functions.misc import first
-from eddy.core.functions.signals import connect
-from eddy.core.metadata import (
- Entity,
- K_GRAPH,
- LiteralValue,
- MetadataRequest,
- NamedEntity,
- Repository,
-)
-from eddy.core.output import getLogger
-from eddy.ui.fields import (
- ComboBox,
- IntegerField,
- StringField,
- TextField,
-)
-
-LOGGER = getLogger()
-
-
-class MetadataImporterWidget(QtWidgets.QWidget):
- """
- This class implements the metadata importer used to search external metadata sources.
- """
- sgnItemActivated = QtCore.pyqtSignal(QtGui.QStandardItem)
- sgnItemClicked = QtCore.pyqtSignal(QtGui.QStandardItem)
- sgnItemDoubleClicked = QtCore.pyqtSignal(QtGui.QStandardItem)
- sgnItemRightClicked = QtCore.pyqtSignal(QtGui.QStandardItem)
-
- def __init__(self, plugin):
- """
- Initialize the metadata importer widget.
- :type plugin: Session
- """
- super().__init__(plugin.session)
-
- self.plugin = plugin
- self.settings = QtCore.QSettings()
- self.iconAttribute = QtGui.QIcon(':/icons/18/ic_treeview_attribute')
- self.iconConcept = QtGui.QIcon(':/icons/18/ic_treeview_concept')
- self.iconInstance = QtGui.QIcon(':/icons/18/ic_treeview_instance')
- self.iconRole = QtGui.QIcon(':/icons/18/ic_treeview_role')
- self.iconValue = QtGui.QIcon(':/icons/18/ic_treeview_value')
-
- self.search = StringField(self)
- self.search.setAcceptDrops(False)
- self.search.setClearButtonEnabled(True)
- self.search.setPlaceholderText('Search...')
- self.search.setFixedHeight(30)
- self.combobox = QtWidgets.QComboBox(self)
- self.combobox.addItems(map(lambda r: r.name, Repository.load()))
- self.combobox.setCurrentIndex(self.settings.value('metadata/index', 0, int))
- self.model = QtGui.QStandardItemModel(self)
- self.proxy = MetadataImporterFilterProxyModel(self)
- self.proxy.setDynamicSortFilter(False)
- self.proxy.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)
- self.proxy.setSortCaseSensitivity(QtCore.Qt.CaseSensitive)
- self.proxy.setSourceModel(self.model)
- self.entityview = MetadataImporterView(self)
- self.entityview.setModel(self.proxy)
- self.details = MetadataInfoWidget(self)
-
- self.searchLayout = QtWidgets.QHBoxLayout()
- self.searchLayout.setContentsMargins(0, 0, 0, 0)
- self.searchLayout.addWidget(self.search)
- self.searchLayout.addWidget(self.combobox)
- self.mainLayout = QtWidgets.QVBoxLayout(self)
- self.mainLayout.setContentsMargins(0, 0, 0, 0)
- self.mainLayout.addLayout(self.searchLayout)
- self.mainLayout.addWidget(self.entityview)
- self.mainLayout.addWidget(self.details)
- self.setTabOrder(self.search, self.combobox)
- self.setTabOrder(self.combobox, self.entityview)
- self.setTabOrder(self.entityview, self.details)
-
- self.setContentsMargins(0, 0, 0, 0)
- self.setMinimumWidth(216)
- self.setStyleSheet("""
- QLineEdit,
- QLineEdit:editable,
- QLineEdit:hover,
- QLineEdit:pressed,
- QLineEdit:focus {
- border: none;
- border-radius: 0;
- background: #FFFFFF;
- color: #000000;
- padding: 4px 4px 4px 4px;
- }
- """)
-
- connect(self.entityview.activated, self.onItemActivated)
- connect(self.entityview.doubleClicked, self.onItemDoubleClicked)
- connect(self.entityview.pressed, self.onItemPressed)
- connect(self.search.textChanged, self.doFilterItem)
- connect(self.search.returnPressed, self.onReturnPressed)
- connect(self.combobox.currentIndexChanged, self.onRepositoryChanged)
- # connect(self.sgnItemActivated, self.session.doFocusItem)
- # connect(self.sgnItemDoubleClicked, self.session.doFocusItem)
- # connect(self.sgnItemRightClicked, self.session.doFocusItem)
-
- #############################################
- # PROPERTIES
- #################################
-
- @property
- def project(self):
- """
- Returns the reference to the active project.
- :rtype: Session
- """
- return self.session.project
-
- @property
- def session(self):
- """
- Returns the reference to the active session.
- :rtype: Session
- """
- return self.plugin.parent()
-
- #############################################
- # EVENTS
- #################################
-
- def paintEvent(self, paintEvent):
- """
- This is needed for the widget to pick the stylesheet.
- :type paintEvent: QPaintEvent
- """
- option = QtWidgets.QStyleOption()
- option.initFrom(self)
- painter = QtGui.QPainter(self)
- style = self.style()
- style.drawPrimitive(QtWidgets.QStyle.PE_Widget, option, painter, self)
-
- #############################################
- # SLOTS
- #################################
-
- @QtCore.pyqtSlot(str)
- def doFilterItem(self, key):
- """
- Executed when the search box is filled with data.
- :type key: str
- """
- self.proxy.setFilterFixedString(key)
- self.proxy.sort(QtCore.Qt.AscendingOrder)
-
- @QtCore.pyqtSlot()
- def doUpdateState(self):
- """
- Executed to refresh the metadata widget.
- """
- repos = Repository.load()
- if len(repos) > 0:
- index = self.combobox.currentIndex()
- # Prevent signals while refreshing the combobox
- status = self.combobox.blockSignals(True)
- self.combobox.clear()
- self.combobox.addItems(map(lambda r: r.name, repos))
- self.combobox.blockSignals(status)
- # Force repetition of current selection to trigger refresh
- self.combobox.setCurrentIndex(index)
- self.combobox.currentIndexChanged.emit(index)
- else:
- self.model.clear()
- self.details.repository = None
- self.details.entity = None
- self.details.stack()
-
- @QtCore.pyqtSlot(QtCore.QModelIndex)
- def onItemActivated(self, index):
- """
- Executed when an item in the list view is activated (e.g. by pressing Return or Enter key).
- :type index: QModelIndex
- """
- # noinspection PyArgumentList
- if QtWidgets.QApplication.mouseButtons() == QtCore.Qt.NoButton:
- item = self.model.itemFromIndex(self.proxy.mapToSource(index))
- if item:
- self.details.entity = item.data()
- self.sgnItemActivated.emit(item)
- # KEEP FOCUS ON THE TREE VIEW UNLESS SHIFT IS PRESSED
- if QtWidgets.QApplication.queryKeyboardModifiers() & QtCore.Qt.SHIFT:
- return
- self.entityview.setFocus()
- self.details.stack()
-
- @QtCore.pyqtSlot(QtCore.QModelIndex)
- def onItemDoubleClicked(self, index):
- """
- Executed when an item in the list view is double-clicked.
- :type index: QModelIndex
- """
- # noinspection PyArgumentList
- if QtWidgets.QApplication.mouseButtons() & QtCore.Qt.LeftButton:
- item = self.model.itemFromIndex(self.proxy.mapToSource(index))
- if item:
- self.details.entity = item.data()
- self.details.repository = item.data().repository
- self.sgnItemDoubleClicked.emit(item)
- self.details.stack()
-
- @QtCore.pyqtSlot(QtCore.QModelIndex)
- def onItemPressed(self, index):
- """
- Executed when an item in the treeview is clicked.
- :type index: QModelIndex
- """
- # noinspection PyArgumentList
- if QtWidgets.QApplication.mouseButtons() & QtCore.Qt.LeftButton:
- item = self.model.itemFromIndex(self.proxy.mapToSource(index))
- if item:
- self.details.entity = item.data()
- #self.details.repository = item.data().repository
- self.sgnItemDoubleClicked.emit(item)
- self.details.stack()
-
- @QtCore.pyqtSlot()
- @QtCore.pyqtSlot(str)
- @QtCore.pyqtSlot(str, str)
- def onPrefixChanged(self, _name: str = None, _ns: str = None):
- """
- Executed when a project prefix is changed to update the medatata namespace manager.
- """
- # There is currently no support for unbinding a namespace in rdflib,
- # so we have to resort to recreating it from scratch.
- # See: https://github.com/RDFLib/rdflib/issues/1932
- K_GRAPH.namespace_manager = NamespaceManager(Graph(), bind_namespaces='none')
- for prefix, ns in self.project.prefixDictItems():
- K_GRAPH.bind(prefix, ns, override=True)
- self.redraw()
-
- @QtCore.pyqtSlot()
- def onReturnPressed(self):
- """
- Executed when the Return or Enter key is pressed in the search field.
- """
- self.focusNextChild()
-
- @QtCore.pyqtSlot(int)
- def onRepositoryChanged(self, index):
- """
- Executed when the selected repository in the combobox changes.
- """
- name = self.combobox.itemText(index)
- repo = first(Repository.load(), filter_on_item=lambda i: i.name == name)
- self.model.clear()
- if repo:
- self.settings.setValue('metadata/index', self.combobox.currentIndex())
- url = QtCore.QUrl(repo.uri)
- url.setPath(f'{url.path()}/classes')
- request = QtNetwork.QNetworkRequest(url)
- request.setAttribute(MetadataRequest.RepositoryAttribute, repo)
- reply = self.session.nmanager.get(request)
- connect(reply.finished, self.onRequestCompleted)
- else:
- repo = None
- self.details.repository = repo
- self.details.entity = None
- self.details.stack()
-
- @QtCore.pyqtSlot()
- def onRequestCompleted(self):
- """
- Executed when a metadata request has completed to update the widget.
- """
- reply = self.sender()
- try:
- reply.deleteLater()
- if reply.isFinished() and reply.error() == QtNetwork.QNetworkReply.NoError:
- data = json.loads(str(reply.readAll(), encoding='utf-8'))
- entities = [NamedEntity.from_dict(d) for d in data if "iri" in d]
- for e in entities:
- e.repository = reply.request().attribute(MetadataRequest.RepositoryAttribute)
- try:
- itemText = K_GRAPH.namespace_manager.curie(e.iri, generate=False)
- except KeyError:
- itemText = e.iri
- item = QtGui.QStandardItem(self.iconConcept, f"{itemText}")
- item.setData(e)
- self.model.appendRow(item)
- self.session.statusBar().showMessage('Metadata fetch completed')
- elif reply.isFinished() and reply.error() != QtNetwork.QNetworkReply.NoError:
- msg = f'Failed to retrieve metadata: {reply.errorString()}'
- LOGGER.warning(msg)
- self.session.statusBar().showMessage(msg)
- except Exception as e:
- LOGGER.error(f'Failed to retrieve metadata: {e}')
-
- #############################################
- # INTERFACE
- #################################
-
- def redraw(self) -> None:
- """
- Redraw the content of the widget.
- """
- for index in range(self.model.rowCount()):
- item = self.model.item(index, 0)
- if isinstance(item.data(), NamedEntity):
- try:
- itemText = K_GRAPH.namespace_manager.curie(item.data().iri, generate=False)
- except KeyError:
- itemText = item.data().iri
- item.setText(itemText)
- self.entityview.update()
- self.details.redraw()
-
- def sizeHint(self):
- """
- Returns the recommended size for this widget.
- :rtype: QtCore.QSize
- """
- return QtCore.QSize(216, 266)
-
-
-class MetadataImporterView(QtWidgets.QListView):
- """
- This class implements the metadata importer list view.
- """
- def __init__(self, parent):
- """
- Initialize the metadata importer list view.
- :type parent: MetadataImporterWidget
- """
- super().__init__(parent)
- self.startPos = None
- self.setContextMenuPolicy(QtCore.Qt.PreventContextMenu)
- self.setEditTriggers(QtWidgets.QTreeView.NoEditTriggers)
- self.setFocusPolicy(QtCore.Qt.StrongFocus)
- self.setHorizontalScrollMode(QtWidgets.QTreeView.ScrollPerPixel)
- self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
- self.setSelectionMode(QtWidgets.QListView.SelectionMode.SingleSelection)
- self.setWordWrap(True)
- # self.setItemDelegate(MetadataImporterItemDelegate(self))
-
- #############################################
- # PROPERTIES
- #################################
-
- @property
- def session(self):
- """
- Returns the reference to the Session holding the MetadataImporter widget.
- :rtype: Session
- """
- return self.widget.session
-
- @property
- def widget(self):
- """
- Returns the reference to the MetadataImporter widget.
- :rtype: MetadataImporterWidget
- """
- return self.parent()
-
- #############################################
- # EVENTS
- #################################
-
- def mousePressEvent(self, mouseEvent):
- """
- Executed when the mouse is pressed on the treeview.
- :type mouseEvent: QMouseEvent
- """
- self.clearSelection()
- if mouseEvent.buttons() & QtCore.Qt.LeftButton:
- self.startPos = mouseEvent.pos()
- super().mousePressEvent(mouseEvent)
-
- def mouseMoveEvent(self, mouseEvent):
- """
- Executed when the mouse if moved while a button is being pressed.
- :type mouseEvent: QMouseEvent
- """
- if mouseEvent.buttons() & QtCore.Qt.LeftButton:
- distance = (mouseEvent.pos() - self.startPos).manhattanLength()
- if distance >= QtWidgets.QApplication.startDragDistance():
- index = first(self.selectedIndexes())
- if index:
- model = self.model().sourceModel()
- index = self.model().mapToSource(index)
- item = model.itemFromIndex(index)
- data = item.data()
- if data:
- if isinstance(data, Entity):
- mimeData = QtCore.QMimeData()
- mimeData.setText(str(Item.ConceptNode.value))
- buf = QtCore.QByteArray()
- buf.append(data.name)
- mimeData.setData(str(Item.ConceptNode.value), buf)
- drag = QtGui.QDrag(self)
- drag.setMimeData(mimeData)
- drag.exec_(QtCore.Qt.CopyAction)
-
- # Add assertion indicating source
- from eddy.core.owl import IRI, AnnotationAssertion
- subj = self.session.project.getIRI(str(data.name)) # type: IRI
- pred = self.session.project.getIRI('urn:x-graphol:origin')
- loc = QtCore.QUrl(data.repository.uri)
- loc.setPath(f'{loc.path()}/entities/{data.id}'.replace('//', '/'))
- obj = IRI(loc.toString())
- ast = AnnotationAssertion(subj, pred, obj)
- cmd = CommandIRIAddAnnotationAssertion(self.session.project, subj, ast)
- self.session.undostack.push(cmd)
-
- super().mouseMoveEvent(mouseEvent)
-
- def paintEvent(self, event: QtGui.QPaintEvent):
- """
- Overrides paintEvent to display a placeholder text.
- """
- super().paintEvent(event)
- if self.model().rowCount() == 0:
- painter = QtGui.QPainter(self.viewport())
- painter.save()
- painter.setPen(self.palette().placeholderText().color())
- fm = self.fontMetrics()
- bgMsg = 'No Metadata Available'
- elided_text = fm.elidedText(bgMsg, QtCore.Qt.ElideRight, self.viewport().width())
- painter.drawText(self.viewport().rect(), QtCore.Qt.AlignCenter, elided_text)
- painter.restore()
-
- #############################################
- # INTERFACE
- #################################
-
- def sizeHintForColumn(self, column):
- """
- Returns the size hint for the given column.
- This will make the column of the treeview as wide as the widget that contains the view.
- :type column: int
- :rtype: int
- """
- return max(super().sizeHintForColumn(column), self.viewport().width())
-
-
-class MetadataImporterFilterProxyModel(QtCore.QSortFilterProxyModel):
- """
- Extends QSortFilterProxyModel adding filtering functionalities for the metadata importer
- """
- def __init__(self, parent=None):
- super().__init__(parent)
-
- #############################################
- # INTERFACE
- #################################
-
- def filterAcceptsRow(self, sourceRow: int, sourceParent: QtCore.QModelIndex) -> bool:
- """
- Overrides filterAcceptsRow to include extra filtering conditions
- :type sourceRow: int
- :type sourceParent: QModelIndex
- :rtype: bool
- """
- return sourceParent.isValid() or super().filterAcceptsRow(sourceRow, sourceParent)
-
-
-class MetadataInfoWidget(QtWidgets.QScrollArea):
- """
- This class implements the metadata detail widget.
- """
- def __init__(self, parent: QtWidgets.QWidget) -> None:
- """
- Initialize the metadata info box.
- """
- super().__init__(parent)
-
- self.repository = None
- self.entity = None
- self.stacked = QtWidgets.QStackedWidget(self)
- self.stacked.setContentsMargins(0, 0, 0, 0)
- self.infoEmpty = EmptyInfo(self.stacked)
- self.infoRepository = RepositoryInfo(self.stacked)
- self.infoEntity = EntityInfo(self.stacked)
- self.stacked.addWidget(self.infoEmpty)
- self.stacked.addWidget(self.infoRepository)
- self.stacked.addWidget(self.infoEntity)
- self.setContentsMargins(0, 0, 0, 0)
- self.setMinimumSize(QtCore.QSize(216, 120))
- self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
- self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
- self.setWidget(self.stacked)
- self.setWidgetResizable(True)
-
- self.setStyleSheet("""
- MetadataInfoWidget {
- background: #FFFFFF;
- }
- MetadataInfoWidget Header {
- background: #5A5050;
- padding-left: 4px;
- color: #FFFFFF;
- }
- MetadataInfoWidget Key {
- background: #BBDEFB;
- border-top: none;
- border-right: none;
- border-bottom: 1px solid #BBDEFB;
- border-left: none;
- padding: 0 0 0 4px;
- }
- MetadataInfoWidget Button,
- MetadataInfoWidget Button:focus,
- MetadataInfoWidget Button:hover,
- MetadataInfoWidget Button:hover:focus,
- MetadataInfoWidget Button:pressed,
- MetadataInfoWidget Button:pressed:focus,
- MetadataInfoWidget Text,
- MetadataInfoWidget Integer,
- MetadataInfoWidget String,
- MetadataInfoWidget Select,
- MetadataInfoWidget Parent {
- background: #E3F2FD;
- border-top: none;
- border-right: none;
- border-bottom: 1px solid #BBDEFB !important;
- border-left: 1px solid #BBDEFB !important;
- padding: 0 0 0 4px;
- text-align:left;
- }
- MetadataInfoWidget Button::menu-indicator {
- image: none;
- }
- MetadataInfoWidget Select:!editable,
- MetadataInfoWidget Select::drop-down:editable {
- background: #FFFFFF;
- }
- MetadataInfoWidget Select:!editable:on,
- MetadataInfoWidget Select::drop-down:editable:on {
- background: #FFFFFF;
- }
- MetadataInfoWidget QCheckBox {
- background: #FFFFFF;
- spacing: 0;
- margin-left: 4px;
- margin-top: 2px;
- }
- MetadataInfoWidget QCheckBox::indicator:disabled {
- background-color: #BABABA;
- }
- """)
-
- scrollbar = self.verticalScrollBar()
- scrollbar.installEventFilter(self)
-
- #############################################
- # EVENTS
- #################################
-
- def eventFilter(self, source: QtCore.QObject, event: QtCore.QEvent) -> bool:
- """
- Filter incoming events.
- """
- if source is self.verticalScrollBar():
- if event.type() in {QtCore.QEvent.Show, QtCore.QEvent.Hide}:
- self.redraw()
- return super().eventFilter(source, event)
-
- #############################################
- # INTERFACE
- #################################
-
- def redraw(self) -> None:
- """
- Redraw the content of the widget.
- """
- width = self.width()
- scrollbar = self.verticalScrollBar()
- if scrollbar.isVisible():
- width -= scrollbar.width()
- widget = self.stacked.currentWidget()
- widget.setFixedWidth(width)
- sizeHint = widget.sizeHint()
- height = sizeHint.height()
- self.stacked.setFixedWidth(width)
- # self.stacked.setFixedHeight(clamp(height, 0))
-
- def stack(self) -> None:
- """
- Set the current stacked widget.
- """
- if self.entity:
- show = self.infoEntity
- show.updateData(self.repository, self.entity)
- elif self.repository:
- show = self.infoRepository
- show.updateData(self.repository)
- else:
- show = self.infoEmpty
-
- prev = self.stacked.currentWidget()
- self.stacked.setCurrentWidget(show)
- self.redraw()
- if prev is not show:
- scrollbar = self.verticalScrollBar()
- scrollbar.setValue(0)
-
-
-#############################################
-# COMPONENTS
-#################################
-
-
-class Header(QtWidgets.QLabel):
- """
- This class implements the header of properties section.
- """
- def __init__(self, *args: Any) -> None:
- """
- Initialize the header.
- """
- super().__init__(*args)
- self.setAlignment(QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter)
- self.setFixedHeight(24)
-
-
-class Key(QtWidgets.QLabel):
- """
- This class implements the key of an info field.
- """
- def __init__(self, *args: Any) -> None:
- """
- Initialize the key.
- """
- super().__init__(*args)
- self.setFixedSize(88, 20)
-
-
-class Button(QtWidgets.QPushButton):
- """
- This class implements the button to which associate a QtWidgets.QMenu instance of an info field.
- """
- def __init__(self, *args: Any) -> None:
- """
- Initialize the button.
- """
- super().__init__(*args)
-
-
-class Integer(IntegerField):
- """
- This class implements the integer value of an info field.
- """
- def __init__(self, *args: Any) -> None:
- """
- Initialize the field.
- """
- super().__init__(*args)
- self.setFixedHeight(20)
-
-
-class String(StringField):
- """
- This class implements the string value of an info field.
- """
- def __init__(self, *args: Any) -> None:
- """
- Initialize the field.
- """
- super().__init__(*args)
- self.setFixedHeight(20)
- self.setReadOnly(True)
-
-
-class Text(TextField):
- """
- This class implements the string value of an info field.
- """
- def __init__(self, *args: Any) -> None:
- """
- Initialize the field.
- """
- super().__init__(*args)
- self.setFixedHeight(20 * (self.document().lineCount() + 1))
- self.setReadOnly(True)
-
-
-class Select(ComboBox):
- """
- This class implements the selection box of an info field.
- """
- def __init__(self, *args: Any) -> None:
- """
- Initialize the field.
- """
- super().__init__(*args)
- self.setSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Ignored)
- self.setFocusPolicy(QtCore.Qt.StrongFocus)
- self.setScrollEnabled(False)
-
-
-class Parent(QtWidgets.QWidget):
- """
- This class implements the parent placeholder to be used
- to store checkbox and radio button value fields.
- """
- def __init__(self, *args: Any) -> None:
- """
- Initialize the field.
- """
- super().__init__(*args)
- self.setFixedHeight(20)
-
- def paintEvent(self, paintEvent: QtGui.QPaintEvent) -> None:
- """
- This is needed for the widget to pick the stylesheet.
- """
- option = QtWidgets.QStyleOption()
- option.initFrom(self)
- painter = QtGui.QPainter(self)
- style = self.style()
- style.drawPrimitive(QtWidgets.QStyle.PE_Widget, option, painter, self)
-
-
-#############################################
-# INFO WIDGETS
-#################################
-
-class AbstractInfo(QtWidgets.QWidget):
- """
- This class implements the base information box.
- """
- __metaclass__ = ABCMeta
-
- def __init__(self, parent: QtWidgets.QWidget = None) -> None:
- """
- Initialize the base information box.
- """
- super().__init__(parent)
- self.setContentsMargins(0, 0, 0, 0)
-
- #############################################
- # INTERFACE
- #################################
-
- @abstractmethod
- def updateData(self, **kwargs: Any) -> None:
- """
- Fetch new information and fill the widget with data.
- """
- pass
-
-
-class RepositoryInfo(AbstractInfo):
- """
- This class implements the repository information box.
- """
- def __init__(self, parent: QtWidgets.QWidget = None) -> None:
- """
- Initialize the repository information box.
- """
- super().__init__(parent)
-
- self.nameKey = Key('Name', self)
- self.nameField = String(self)
- self.nameField.setReadOnly(True)
-
- self.uriKey = Key('Location', self)
- self.uriField = String(self)
- self.uriField.setReadOnly(True)
-
- self.versionKey = Key('Version', self)
- self.versionField = String(self)
- self.versionField.setReadOnly(True)
-
- self.infoHeader = Header('Repository Info', self)
- self.infoLayout = QtWidgets.QFormLayout()
- self.infoLayout.setSpacing(0)
- self.infoLayout.addRow(self.nameKey, self.nameField)
- self.infoLayout.addRow(self.uriKey, self.uriField)
- self.infoLayout.addRow(self.versionKey, self.versionField)
-
- self.mainLayout = QtWidgets.QVBoxLayout(self)
- self.mainLayout.setAlignment(QtCore.Qt.AlignTop)
- self.mainLayout.setContentsMargins(0, 0, 0, 0)
- self.mainLayout.setSpacing(0)
- self.mainLayout.insertWidget(0, self.infoHeader)
- self.mainLayout.insertLayout(1, self.infoLayout)
-
- #############################################
- # INTERFACE
- #################################
-
- def updateData(self, repository: Repository) -> None:
- """
- Fetch new information and fill the widget with data.
- """
- self.nameField.setValue(repository.name)
- self.uriField.setValue(repository.uri)
- self.versionField.setValue('1.0')
-
-
-class EntityInfo(AbstractInfo):
- """
- This class implements the information box for entities.
- """
- def __init__(self, parent: QtWidgets.QWidget = None) -> None:
- """
- Initialize the generic node information box.
- """
- super().__init__(parent)
-
- self.entity = None
- self.repoKey = Key('Repository', self)
- self.repoField = String(self)
- self.repoField.setReadOnly(True)
-
- self.idKey = Key('Entity ID', self)
- self.idField = String(self)
- self.idField.setReadOnly(True)
-
- self.iriKey = Key('Entity IRI', self)
- self.iriField = String(self)
- self.iriField.setReadOnly(True)
-
- self.nodePropHeader = Header('Entity properties', self)
- self.nodePropLayout = QtWidgets.QFormLayout()
- self.nodePropLayout.setSpacing(0)
- self.nodePropLayout.addRow(self.repoKey, self.repoField)
- self.nodePropLayout.addRow(self.idKey, self.idField)
- self.nodePropLayout.addRow(self.iriKey, self.iriField)
-
- self.typesHeader = Header('Entity Types', self)
- self.typesLayout = QtWidgets.QFormLayout()
- self.typesLayout.setSpacing(0)
-
- self.metadataHeader = Header('Entity Annotations', self)
- self.metadataLayout = QtWidgets.QFormLayout()
- self.metadataLayout.setSpacing(0)
-
- self.mainLayout = QtWidgets.QVBoxLayout(self)
- self.mainLayout.setAlignment(QtCore.Qt.AlignTop)
- self.mainLayout.setContentsMargins(0, 0, 0, 0)
- self.mainLayout.setSpacing(0)
- self.mainLayout.addWidget(self.nodePropHeader)
- self.mainLayout.addLayout(self.nodePropLayout)
- self.mainLayout.addWidget(self.typesHeader)
- self.mainLayout.addLayout(self.typesLayout)
- self.mainLayout.addWidget(self.metadataHeader)
- self.mainLayout.addLayout(self.metadataLayout)
-
- #############################################
- # INTERFACE
- #################################
-
- def updateData(self, repository: Repository, entity: Entity) -> None:
- """
- Fetch new information and fill the widget with data.
- """
- self.repoField.setValue(repository.uri)
- self.idField.setValue(entity.id)
- self.iriField.setValue(entity.name)
-
- # ENTITY TYPES
- while self.typesLayout.rowCount() > 0:
- self.typesLayout.removeRow(0)
- for t in entity.types:
- self.typesLayout.addRow(Key('Type', self), String(t.name, self))
-
- # ENTITY ANNOTATIONS
- while self.metadataLayout.rowCount() > 0:
- self.metadataLayout.removeRow(0)
- for a in entity.annotations:
- self.metadataLayout.addRow(Key('Property', self), String(a.predicate.n3(), self))
- if isinstance(a.object, LiteralValue):
- literal = cast(LiteralValue, a.object)
- value, lang, dtype = literal.value, literal.language, literal.datatype
- self.metadataLayout.addRow(Key('Value', self), Text(value, self))
- if lang:
- self.metadataLayout.addRow(Key('lang', self), String(lang, self))
- if dtype:
- self.metadataLayout.addRow(Key('dtype', self), String(dtype.n3(), self))
- else:
- self.metadataLayout.addRow(Key('Entity', self), String(a.object.n3(), self))
- self.metadataLayout.addItem(QtWidgets.QSpacerItem(10, 2))
-
-
-class EmptyInfo(QtWidgets.QTextEdit):
- """
- This class implements the information box when there is no metadata repository.
- """
-
- #############################################
- # INTERFACE
- #################################
-
- def paintEvent(self, event: QtGui.QPaintEvent):
- """
- Overrides paintEvent to display a placeholder text.
- """
- super().paintEvent(event)
- painter = QtGui.QPainter(self.viewport())
- painter.save()
- painter.setPen(self.palette().placeholderText().color())
- fm = self.fontMetrics()
- bgMsg = textwrap.dedent("""
- To start using the 'Metadata Importer', add a repository location
- in the 'Ontology Manager -> Metadata Repositories' tab.
- """)
- elided_text = fm.elidedText(bgMsg, QtCore.Qt.ElideRight, self.viewport().width())
- painter.drawText(self.viewport().rect(), QtCore.Qt.AlignCenter, elided_text)
- painter.restore()
diff --git a/eddy/plugins/metadata-importer/plugin.spec b/eddy/plugins/metadata-importer/plugin.spec
deleted file mode 100644
index 5889ebc6..00000000
--- a/eddy/plugins/metadata-importer/plugin.spec
+++ /dev/null
@@ -1,40 +0,0 @@
-# -*- coding: utf-8 -*-
-
-##########################################################################
-# #
-# Eddy: a graphical editor for the specification of Graphol ontologies #
-# Copyright (C) 2015 Daniele Pantaleone #
-# #
-# This program is free software: you can redistribute it and/or modify #
-# it under the terms of the GNU General Public License as published by #
-# the Free Software Foundation, either version 3 of the License, or #
-# (at your option) any later version. #
-# #
-# This program is distributed in the hope that it will be useful, #
-# but WITHOUT ANY WARRANTY; without even the implied warranty of #
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
-# GNU General Public License for more details. #
-# #
-# You should have received a copy of the GNU General Public License #
-# along with this program. If not, see . #
-# #
-# ##################### ##################### #
-# #
-# Graphol is developed by members of the DASI-lab group of the #
-# Dipartimento di Ingegneria Informatica, Automatica e Gestionale #
-# A.Ruberti at Sapienza University of Rome: http://www.dis.uniroma1.it #
-# #
-# - Domenico Lembo #
-# - Valerio Santarelli #
-# - Domenico Fabio Savo #
-# - Daniele Pantaleone #
-# - Marco Console #
-# #
-##########################################################################
-
-[plugin]
-author: OBDA Systems
-contact: info@obdasystems.com
-id: metadata_importer
-name: Metadata Importer
-version: 0.2.0
diff --git a/eddy/ui/iri.py b/eddy/ui/iri.py
index d8340a34..1b14945e 100644
--- a/eddy/ui/iri.py
+++ b/eddy/ui/iri.py
@@ -58,7 +58,7 @@
)
from eddy.core.common import HasWidgetSystem
from eddy.core.diagram import Diagram
-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.base import AbstractNode
from eddy.core.items.nodes.facet import FacetNode
@@ -643,13 +643,14 @@ def accept(self):
if self.iri:
if not str(self.iri) == inputIriString:
if len(self.project.iriOccurrences(iri=self.iri)) == 1:
- existIRI = self.project.existIRI(inputIriString)
- if existIRI:
- newIRI = self.project.getIRI(inputIriString,
- addLabelFromSimpleName=True,
- addLabelFromUserInput=True,
- userInput=userExplicitInput,
- labelLang=labelLang)
+ if self.project.existIRI(inputIriString):
+ newIRI = self.project.getIRI(
+ inputIriString,
+ addLabelFromSimpleName=True,
+ addLabelFromUserInput=True,
+ userInput=userExplicitInput,
+ labelLang=labelLang,
+ )
if newIRI is not self.iri:
oldIRI = self.iri
self.iri = newIRI
@@ -797,6 +798,15 @@ def __init__(self, iri, session, focusOnAnnotations=False):
connect(addBtn.clicked, self.addAnnotation)
connect(delBtn.clicked, self.removeAnnotation)
connect(editBtn.clicked, self.editAnnotation)
+ ##############################
+ # Disable edit - add - delete if entity has an external origin annotation
+ if any([str(a.assertionProperty) == 'urn:x-graphol:origin'
+ for a in self.iri.annotationAssertions]):
+ addBtn.setDisabled(True)
+ delBtn.setDisabled(True)
+ editBtn.setDisabled(True)
+ disconnect(table.cellDoubleClicked, self.editAnnotation)
+ ##############################
self.addWidget(addBtn)
self.addWidget(delBtn)
self.addWidget(editBtn)
diff --git a/eddy/ui/ontology.py b/eddy/ui/ontology.py
index 12807831..831915e3 100644
--- a/eddy/ui/ontology.py
+++ b/eddy/ui/ontology.py
@@ -32,8 +32,6 @@
# #
##########################################################################
-import textwrap
-
from PyQt5 import (
QtCore,
QtGui,
@@ -79,7 +77,6 @@
from eddy.core.functions.misc import first
from eddy.core.functions.path import expandPath
from eddy.core.functions.signals import connect
-from eddy.core.metadata import Repository
from eddy.core.ndc import (
ADMS,
NDCDataset,
@@ -1152,67 +1149,6 @@ def __init__(self, session):
self.setDistributionSuggestions()
self.setProjectSuggestions()
- #############################################
- # METADATA REPOSITORIES
- #################################
-
- table = QtWidgets.QTableWidget(0, 2, self, objectName='repository_table_widget')
- table.setHorizontalHeaderLabels(['Name', 'Endpoint'])
- table.horizontalHeader().setStretchLastSection(True)
- table.horizontalHeader().setSectionsClickable(False)
- table.horizontalHeader().setMinimumSectionSize(100)
- table.horizontalHeader().setSectionsClickable(False)
- table.verticalHeader().setVisible(False)
- table.verticalHeader().setSectionsClickable(False)
- table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
- self.addWidget(table)
-
- delBtn = QtWidgets.QPushButton('Remove', objectName='repository_del_button')
- delBtn.setEnabled(False)
- connect(delBtn.clicked, self.removeRepository)
- self.addWidget(delBtn)
-
- boxlayout = QtWidgets.QHBoxLayout()
- boxlayout.setAlignment(QtCore.Qt.AlignCenter)
- boxlayout.addWidget(delBtn)
- formlayout = QtWidgets.QFormLayout()
- formlayout.addRow(self.widget('repository_table_widget'))
- formlayout.addRow(boxlayout)
-
- groupbox = QtWidgets.QGroupBox('Repositories', self)
- groupbox.setObjectName('repository_list_groupbox')
- groupbox.setLayout(formlayout)
- self.addWidget(groupbox)
-
- nameField = StringField(self, objectName='repository_name_field')
- uriField = StringField(self, objectName='repository_uri_field')
- addBtn = QtWidgets.QPushButton('Add', objectName='repository_add_button')
- connect(addBtn.clicked, self.addRepository)
- self.addWidget(nameField)
- self.addWidget(uriField)
- self.addWidget(addBtn)
-
- boxlayout = QtWidgets.QHBoxLayout()
- boxlayout.setAlignment(QtCore.Qt.AlignCenter)
- boxlayout.addWidget(addBtn)
- formlayout = QtWidgets.QFormLayout()
- formlayout.addRow(QtWidgets.QLabel('Name'), nameField)
- formlayout.addRow(QtWidgets.QLabel('URI'), uriField)
- formlayout.addRow(boxlayout)
-
- groupbox = QtWidgets.QGroupBox('Add Repository', self)
- groupbox.setObjectName('repository_add_groupbox')
- groupbox.setLayout(formlayout)
- self.addWidget(groupbox)
-
- layout = QtWidgets.QVBoxLayout()
- layout.addWidget(self.widget('repository_list_groupbox'))
- layout.addWidget(self.widget('repository_add_groupbox'))
- widget = QtWidgets.QWidget()
- widget.setObjectName('repositories_widget')
- widget.setLayout(layout)
- self.addWidget(widget)
-
#############################################
# CONFIRMATION BOX
#################################
@@ -1235,7 +1171,6 @@ def __init__(self, session):
widget.addTab(self.widget('annotations_widget'), 'Annotations')
widget.addTab(self.widget('iri_widget'), 'Global IRIs')
widget.addTab(self.widget('assertions_widget'), 'Annotation Assertions')
- widget.addTab(self.widget('repositories_widget'), 'Metadata Repositories')
widget.addTab(self.widget('NDCmetadata_widget'), 'NDC Metadata')
self.addWidget(widget)
@@ -1439,25 +1374,6 @@ def redraw(self):
preField = self.widget('pre_input_field')
postField = self.widget('post_input_field')
- #############################################
- # METADATA REPOSITORIES
- #################################
-
- widget = self.widget('repository_table_widget') # type: QtWidgets.QTableWidget
- widget.clearContents()
- repos = Repository.load()
- widget.setRowCount(len(repos))
- for index, repo in enumerate(repos):
- nameItem = QtWidgets.QTableWidgetItem(repo.name)
- nameItem.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable)
- uriItem = QtWidgets.QTableWidgetItem(repo.uri)
- uriItem.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable)
- widget.setItem(index, 0, nameItem)
- widget.setItem(index, 1, uriItem)
- widget.resizeColumnsToContents()
- widget.sortItems(0)
- self.widget('repository_del_button').setEnabled(len(repos) > 0)
-
#############################################
# GENERAL TAB
#################################
@@ -2709,80 +2625,3 @@ def doAddMetadata(self):
self.session.undostack.endMacro()
# self.redraw()
self.session.addNotification('Metadata added to the current project!')
-
- #############################################
- # METADATA REPOSITORIES
- #################################
-
- @QtCore.pyqtSlot()
- def addRepository(self):
- """Shows a dialog to insert a add a new repository."""
- nameField = self.widget('repository_name_field') # type: StringField
- uriField = self.widget('repository_uri_field') # type: StringField
-
- # Validate user input
- if len(nameField.text()) == 0:
- msgBox = QtWidgets.QMessageBox( # noqa
- QtWidgets.QMessageBox.Warning,
- 'Invalid Repository Name',
- 'Please specify a repository name.',
- informativeText=textwrap.dedent("""
- The repository name can be any string that is used to easily
- reference the repository.
- """),
- parent=self,
- )
- msgBox.open()
- elif not QtCore.QUrl(uriField.text()).isValid():
- msgBox = QtWidgets.QMessageBox( # noqa
- QtWidgets.QMessageBox.Warning,
- 'Invalid Repository URI',
- 'Please specify a valid repository URI.',
- informativeText=textwrap.dedent("""
- The repository URI is the base path at which the repository API is accessible,
- and must include protocol, domain and port (if any).
-
- e.g.:
- https://example.com:5000/
- https://example.com/myrepo/
- """),
- parent=self,
- )
- msgBox.open()
- else:
- # Add new repository
- repos = Repository.load()
- if any(map(lambda r: r.name == nameField.text(), repos)):
- msgBox = QtWidgets.QMessageBox( # noqa
- QtWidgets.QMessageBox.Warning,
- 'Duplicate Repository Error',
- f'A repository named {nameField.text()} already exists.',
- informativeText=textwrap.dedent("""
- Repository names must be unique to avoid abiguity in the user interface.
- """),
- parent=self,
- )
- msgBox.open()
- else:
- repos.append(Repository(name=nameField.text(), uri=uriField.text()))
- Repository.save(repos)
- self.redraw()
-
- @QtCore.pyqtSlot()
- def removeRepository(self):
- """Remove selected repositories."""
- # Delete selected repositories
- widget = self.widget('repository_table_widget') # type: QtWidgets.QTableWidget
- selections = widget.selectedRanges()
- for sel in selections:
- for row in range(sel.bottomRow(), sel.topRow() + 1):
- widget.removeRow(row)
- # Save the current repositories list
- repos = []
- for row in range(widget.rowCount()):
- repos.append(Repository(
- name=widget.item(row, 0).text(),
- uri=widget.item(row, 1).text(),
- ))
- Repository.save(repos)
- self.redraw()