# -*- coding: utf-8 -*- ########################################################################## # # # Eddy: a graphical editor for the specification of Graphol ontologies # # Copyright (C) 2015 Daniele Pantaleone <danielepantaleone@me.com> # # # # 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 <http://www.gnu.org/licenses/>. # # # # ##################### ##################### # # # # 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 <lembo@dis.uniroma1.it> # # - Valerio Santarelli <santarelli@dis.uniroma1.it> # # - Domenico Fabio Savo <savo@dis.uniroma1.it> # # - Daniele Pantaleone <pantaleone@dis.uniroma1.it> # # - Marco Console <console@dis.uniroma1.it> # # # ########################################################################## from abc import ABCMeta, abstractmethod from operator import attrgetter from PyQt5 import QtCore from PyQt5 import QtGui from PyQt5 import QtWidgets from eddy.core.commands.labels import CommandLabelChange from eddy.core.commands.nodes import CommandNodeSetMeta from eddy.core.commands.project import CommandProjectSetIRI from eddy.core.commands.project import CommandProjectSetPrefix from eddy.core.commands.project import CommandProjectSetProfile from eddy.core.commands.project import CommandProjectSetVersion from eddy.core.datatypes.graphol import Item from eddy.core.datatypes.owl import Facet, Datatype, OWLProfile from eddy.core.datatypes.qt import BrushIcon, Font from eddy.core.functions.misc import first, clamp, isEmpty from eddy.core.functions.signals import connect, disconnect from eddy.core.plugin import AbstractPlugin from eddy.core.project import K_FUNCTIONAL, K_INVERSE_FUNCTIONAL from eddy.core.project import K_ASYMMETRIC, K_IRREFLEXIVE, K_REFLEXIVE from eddy.core.project import K_SYMMETRIC, K_TRANSITIVE from eddy.core.regex import RE_CAMEL_SPACE from eddy.ui.dock import DockWidget from eddy.ui.fields import IntegerField, StringField from eddy.ui.fields import CheckBox, ComboBox class InfoPlugin(AbstractPlugin): """ This plugin provides the Information widget. """ ############################################# # EVENTS ################################# def eventFilter(self, source, event): """ Filters events if this object has been installed as an event filter for the watched object. :type source: QObject :type event: QtCore.QEvent :rtype: bool """ if event.type() == QtCore.QEvent.Resize: widget = source.widget() widget.redraw() return super().eventFilter(source, event) ############################################# # SLOTS ################################# @QtCore.pyqtSlot('QGraphicsScene') def onDiagramAdded(self, diagram): """ Executed whenever a diagram is added to the active project. """ self.widget('info').stack() @QtCore.pyqtSlot('QGraphicsScene') def onDiagramRemoved(self, diagram): """ Executed whenever a diagram is removed from the active project. """ self.widget('info').stack() @QtCore.pyqtSlot() def onDiagramSelectionChanged(self): """ Executed whenever the selection of the active diagram changes. """ self.widget('info').stack() @QtCore.pyqtSlot() def onDiagramUpdated(self): """ Executed whenever the active diagram is updated. """ self.widget('info').stack() @QtCore.pyqtSlot('QGraphicsScene', 'QGraphicsItem') def onProjectItemAdded(self, diagram, item): """ Executed whenever a new element is added to the active project. """ self.widget('info').stack() @QtCore.pyqtSlot('QGraphicsScene', 'QGraphicsItem') def onProjectItemRemoved(self, diagram, item): """ Executed whenever a new element is removed from the active project. """ self.widget('info').stack() @QtCore.pyqtSlot() def onProjectUpdated(self): """ Executed whenever the current project is updated. """ self.widget('info').stack() @QtCore.pyqtSlot() def onSessionReady(self): """ Executed whenever the main session completes the startup sequence. """ self.debug('Connecting to project: %s', self.project.name) connect(self.project.sgnUpdated, self.onProjectUpdated) connect(self.project.sgnDiagramAdded, self.onDiagramAdded) connect(self.project.sgnDiagramRemoved, self.onDiagramRemoved) connect(self.project.sgnItemAdded, self.onProjectItemAdded) connect(self.project.sgnItemRemoved, self.onProjectItemRemoved) self.widget('info').stack() @QtCore.pyqtSlot(QtWidgets.QMdiSubWindow) def onSubWindowActivated(self, subwindow): """ Executed when the active subwindow changes. :type subwindow: MdiSubWindow """ if subwindow: # If we have an active subwindow, we change the info # widget to browse the diagram within such subwindow. widget = self.widget('info') if widget.diagram: # If the info widget is currently inspecting a # diagram, detach signals from the subwindow which # is going out of focus, before connecting new ones. self.debug('Disconnecting from diagram: %s', widget.diagram.name) disconnect(widget.diagram.selectionChanged, self.onDiagramSelectionChanged) disconnect(widget.diagram.sgnUpdated, self.onDiagramUpdated) # Attach the new view/diagram to the info widget. self.debug('Connecting to diagram: %s', subwindow.diagram.name) connect(subwindow.diagram.selectionChanged, self.onDiagramSelectionChanged) connect(subwindow.diagram.sgnUpdated, self.onDiagramUpdated) widget.setDiagram(subwindow.diagram) widget.stack() else: if not self.session.mdi.subWindowList(): # If we don't have any active subwindow (which means that # they have been all closed and not just out of focus) we # detach the widget from the last inspected diagram. widget = self.widget('info') if widget.diagram: self.debug('Disconnecting from diagram: %s', widget.diagram.name) disconnect(widget.diagram.selectionChanged, self.onDiagramSelectionChanged) disconnect(widget.diagram.sgnUpdated, self.onDiagramUpdated) widget.setDiagram(None) widget.stack() ############################################# # HOOKS ################################# def dispose(self): """ Executed whenever the plugin is going to be destroyed. """ # DISCONNECT FROM CURRENT PROJECT self.debug('Disconnecting from project: %s', self.project.name) disconnect(self.project.sgnUpdated, self.onProjectUpdated) disconnect(self.project.sgnDiagramAdded, self.onDiagramAdded) disconnect(self.project.sgnDiagramRemoved, self.onDiagramRemoved) disconnect(self.project.sgnItemAdded, self.onProjectItemAdded) disconnect(self.project.sgnItemRemoved, self.onProjectItemRemoved) # DISCONNECT FROM ACTIVE SESSION self.debug('Disconnecting from active session') disconnect(self.session.sgnReady, self.onSessionReady) disconnect(self.session.mdi.subWindowActivated, self.onSubWindowActivated) # REMOVE DOCKING AREA WIDGET MENU ENTRY self.debug('Removing docking area widget toggle from "view" menu') menu = self.session.menu('view') menu.removeAction(self.widget('info_dock').toggleViewAction()) # UNINSTALL THE PALETTE DOCK WIDGET self.debug('Uninstalling docking area widget') self.session.removeDockWidget(self.widget('info_dock')) def start(self): """ Perform initialization tasks for the plugin. """ # INITIALIZE THE WIDGET self.debug('Creating info widget') widget = InfoWidget(self) widget.setObjectName('info') self.addWidget(widget) # CREATE DOCKING AREA WIDGET self.debug('Creating docking area widget') widget = DockWidget('Info', QtGui.QIcon(':/icons/18/ic_info_outline_black'), self.session) widget.installEventFilter(self) widget.setAllowedAreas(QtCore.Qt.LeftDockWidgetArea | QtCore.Qt.RightDockWidgetArea) widget.setObjectName('info_dock') widget.setWidget(self.widget('info')) self.addWidget(widget) # CREATE ENTRY IN VIEW MENU self.debug('Creating docking area widget toggle in "view" menu') menu = self.session.menu('view') menu.addAction(self.widget('info_dock').toggleViewAction()) # INSTALL DOCKING AREA WIDGET self.debug('Installing docking area widget') self.session.addDockWidget(QtCore.Qt.RightDockWidgetArea, self.widget('info_dock')) # CONFIGURE SIGNAL/SLOTS self.debug('Connecting to active session') connect(self.session.sgnReady, self.onSessionReady) connect(self.session.mdi.subWindowActivated, self.onSubWindowActivated) class InfoWidget(QtWidgets.QScrollArea): """ This class implements the information box widget. """ def __init__(self, plugin): """ Initialize the info box. :type plugin: Info """ super().__init__(plugin.session) self.diagram = None self.plugin = plugin self.stacked = QtWidgets.QStackedWidget(self) self.stacked.setContentsMargins(0, 0, 0, 0) self.infoEmpty = QtWidgets.QWidget(self.stacked) self.infoProject = ProjectInfo(self.session, self.stacked) self.infoEdge = EdgeInfo(self.session, self.stacked) self.infoNode = NodeInfo(self.session, self.stacked) self.infoPredicateNode = PredicateNodeInfo(self.session, self.stacked) self.infoAttributeNode = AttributeNodeInfo(self.session, self.stacked) self.infoRoleNode = RoleNodeInfo(self.session, self.stacked) self.infoValueNode = ValueNodeInfo(self.session, self.stacked) self.infoValueDomainNode = ValueDomainNodeInfo(self.session, self.stacked) self.infoFacet = FacetNodeInfo(self.session, self.stacked) self.stacked.addWidget(self.infoProject) self.stacked.addWidget(self.infoEdge) self.stacked.addWidget(self.infoNode) self.stacked.addWidget(self.infoPredicateNode) self.stacked.addWidget(self.infoAttributeNode) self.stacked.addWidget(self.infoRoleNode) self.stacked.addWidget(self.infoValueNode) self.stacked.addWidget(self.infoValueDomainNode) self.stacked.addWidget(self.infoFacet) 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(""" InfoWidget { background: #FFFFFF; } InfoWidget Header { background: #5A5050; padding-left: 4px; color: #FFFFFF; } InfoWidget Key { background: #BBDEFB; border-top: none; border-right: none; border-bottom: 1px solid #BBDEFB; border-left: none; padding: 0 0 0 4px; } InfoWidget Button, InfoWidget Button:focus, InfoWidget Button:hover, InfoWidget Button:hover:focus, InfoWidget Button:pressed, InfoWidget Button:pressed:focus, InfoWidget Integer, InfoWidget String, InfoWidget Select, InfoWidget 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; } InfoWidget Button::menu-indicator { image: none; } InfoWidget Select:!editable, InfoWidget Select::drop-down:editable { background: #FFFFFF; } InfoWidget Select:!editable:on, InfoWidget Select::drop-down:editable:on { background: #FFFFFF; } InfoWidget QCheckBox { background: #FFFFFF; spacing: 0; margin-left: 4px; margin-top: 2px; } InfoWidget QCheckBox::indicator:disabled { background-color: #BABABA; } """) scrollbar = self.verticalScrollBar() scrollbar.installEventFilter(self) ############################################# # 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 eventFilter(self, source, event): """ Filter incoming events. :type source: QObject :type event: QtCore.QEvent """ 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): """ 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 setDiagram(self, diagram): """ Sets the widget to inspect the given diagram. :type diagram: diagram """ self.diagram = diagram def stack(self): """ Set the current stacked widget. """ if self.diagram: selected = self.diagram.selectedItems() if not selected or len(selected) > 1: show = self.infoProject show.updateData(self.project) else: item = first(selected) if item.isNode(): if item.isPredicate(): if item.type() is Item.ValueDomainNode: show = self.infoValueDomainNode elif item.type() is Item.RoleNode: show = self.infoRoleNode elif item.type() is Item.AttributeNode: show = self.infoAttributeNode else: show = self.infoPredicateNode else: if item.type() is Item.FacetNode: show = self.infoFacet else: show = self.infoNode else: show = self.infoEdge show.updateData(item) elif self.project: show = self.infoProject show.updateData(self.project) 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): """ 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): """ 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): """ Initialize the button. """ super().__init__(*args) class Integer(IntegerField): """ This class implements the integer value of an info field. """ def __init__(self, *args): """ 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): """ Initialize the field. """ super().__init__(*args) self.setFixedHeight(20) class Select(ComboBox): """ This class implements the selection box of an info field. """ def __init__(self, *args): """ 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): """ Initialize the field. """ super().__init__(*args) self.setFixedHeight(20) 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) ############################################# # INFO WIDGETS ################################# class AbstractInfo(QtWidgets.QWidget): """ This class implements the base information box. """ __metaclass__ = ABCMeta def __init__(self, session, parent=None): """ Initialize the base information box. :type session: Session :type parent: QtWidgets.QWidget """ super().__init__(parent) self.session = session self.setContentsMargins(0, 0, 0, 0) ############################################# # PROPERTIES ################################# @property def project(self): """ Returns the project loaded in the current session. :rtype: Project """ return self.session.project ############################################# # INTERFACE ################################# @abstractmethod def updateData(self, **kwargs): """ Fetch new information and fill the widget with data. """ pass class ProjectInfo(AbstractInfo): """ This class implements the project information box. """ def __init__(self, session, parent=None): """ Initialize the project information box. :type session: Session :type parent: QtWidgets.QWidget """ super().__init__(session, parent) self.versionKey = Key('Version', self) self.versionKey.setFont(Font('Roboto', 12)) self.versionField = String(self) self.versionField.setFont(Font('Roboto', 12)) self.versionField.setReadOnly(False) connect(self.versionField.editingFinished, self.versionEditingFinished) self.prefixKey = Key('Prefix', self) self.prefixKey.setFont(Font('Roboto', 12)) self.prefixField = String(self) self.prefixField.setFont(Font('Roboto', 12)) self.prefixField.setReadOnly(False) connect(self.prefixField.editingFinished, self.prefixEditingFinished) self.iriKey = Key('IRI', self) self.iriKey.setFont(Font('Roboto', 12)) self.iriField = String(self) self.iriField.setFont(Font('Roboto', 12)) self.iriField.setReadOnly(False) connect(self.iriField.editingFinished, self.iriEditingFinished) self.profileKey = Key('Profile', self) self.profileKey.setFont(Font('Roboto', 12)) self.profileField = Select(self) self.profileField.setFont(Font('Roboto', 12)) self.profileField.addItems(self.session.profileNames()) connect(self.profileField.activated, self.profileChanged) self.ontologyPropHeader = Header('Ontology properties', self) self.ontologyPropHeader.setFont(Font('Roboto', 12)) self.ontologyPropLayout = QtWidgets.QFormLayout() self.ontologyPropLayout.setSpacing(0) self.ontologyPropLayout.addRow(self.versionKey, self.versionField) self.ontologyPropLayout.addRow(self.prefixKey, self.prefixField) self.ontologyPropLayout.addRow(self.iriKey, self.iriField) self.ontologyPropLayout.addRow(self.profileKey, self.profileField) self.conceptsKey = Key('Concept', self) self.conceptsKey.setFont(Font('Roboto', 12)) self.conceptsField = Integer(self) self.conceptsField.setFont(Font('Roboto', 12)) self.conceptsField.setFocusPolicy(QtCore.Qt.NoFocus) self.conceptsField.setReadOnly(True) self.rolesKey = Key('Role', self) self.rolesKey.setFont(Font('Roboto', 12)) self.rolesField = Integer(self) self.rolesField.setFont(Font('Roboto', 12)) self.rolesField.setFocusPolicy(QtCore.Qt.NoFocus) self.rolesField.setReadOnly(True) self.attributesKey = Key('Attribute', self) self.attributesKey.setFont(Font('Roboto', 12)) self.attributesField = Integer(self) self.attributesField.setFont(Font('Roboto', 12)) self.attributesField.setFocusPolicy(QtCore.Qt.NoFocus) self.attributesField.setReadOnly(True) self.inclusionsKey = Key('Inclusion', self) self.inclusionsKey.setFont(Font('Roboto', 12)) self.inclusionsField = Integer(self) self.inclusionsField.setFont(Font('Roboto', 12)) self.inclusionsField.setFocusPolicy(QtCore.Qt.NoFocus) self.inclusionsField.setReadOnly(True) self.membershipKey = Key('Membership', self) self.membershipKey.setFont(Font('Roboto', 12)) self.membershipField = Integer(self) self.membershipField.setFont(Font('Roboto', 12)) self.membershipField.setFocusPolicy(QtCore.Qt.NoFocus) self.membershipField.setReadOnly(True) self.atomicPredHeader = Header('Atomic predicates', self) self.atomicPredHeader.setFont(Font('Roboto', 12)) self.atomicPredLayout = QtWidgets.QFormLayout() self.atomicPredLayout.setSpacing(0) self.atomicPredLayout.addRow(self.conceptsKey, self.conceptsField) self.atomicPredLayout.addRow(self.rolesKey, self.rolesField) self.atomicPredLayout.addRow(self.attributesKey, self.attributesField) self.assertionsHeader = Header('Assertions', self) self.assertionsHeader.setFont(Font('Roboto', 12)) self.assertionsLayout = QtWidgets.QFormLayout() self.assertionsLayout.setSpacing(0) self.assertionsLayout.addRow(self.inclusionsKey, self.inclusionsField) self.assertionsLayout.addRow(self.membershipKey, self.membershipField) 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.ontologyPropHeader) self.mainLayout.addLayout(self.ontologyPropLayout) self.mainLayout.addWidget(self.atomicPredHeader) self.mainLayout.addLayout(self.atomicPredLayout) self.mainLayout.addWidget(self.assertionsHeader) self.mainLayout.addLayout(self.assertionsLayout) ############################################# # SLOTS ################################# @QtCore.pyqtSlot() def iriEditingFinished(self): """ Executed whenever we finish to edit the ontology prefix """ iri = self.iriField.value() if self.project.iri != iri: self.session.undostack.push(CommandProjectSetIRI(self.project, self.project.iri, iri)) self.iriField.clearFocus() @QtCore.pyqtSlot() def prefixEditingFinished(self): """ Executed whenever we finish to edit the ontology prefix """ prefix = self.prefixField.value() if self.project.prefix != prefix: self.session.undostack.push(CommandProjectSetPrefix(self.project, self.project.prefix, prefix)) self.prefixField.clearFocus() @QtCore.pyqtSlot() def profileChanged(self): """ Executed when we need to change the project profile. """ profile = self.profileField.currentText() if self.project.profile.name() != profile: self.session.undostack.push(CommandProjectSetProfile(self.project, self.project.profile.name(), profile)) self.profileField.clearFocus() @QtCore.pyqtSlot() def versionEditingFinished(self): """ Executed whenever we finish to edit the ontology prefix """ version = self.versionField.value() if self.project.version != version: self.session.undostack.push(CommandProjectSetVersion(self.project, self.project.version, version)) self.iriField.clearFocus() ############################################# # INTERFACE ################################# def updateData(self, project): """ Fetch new information and fill the widget with data. :type project: Project """ self.prefixField.setValue(project.prefix) self.prefixField.home(True) self.prefixField.clearFocus() self.prefixField.deselect() self.iriField.setValue(project.iri) self.iriField.home(True) self.iriField.clearFocus() self.iriField.deselect() self.versionField.setValue(project.version) self.versionField.home(True) self.versionField.clearFocus() self.versionField.deselect() for i in range(self.profileField.count()): if self.profileField.itemText(i) == self.project.profile.name(): self.profileField.setCurrentIndex(i) break self.attributesField.setValue(project.predicateNum(Item.AttributeNode)) self.conceptsField.setValue(project.predicateNum(Item.ConceptNode)) self.rolesField.setValue(project.predicateNum(Item.RoleNode)) self.inclusionsField.setValue(project.itemNum(Item.InclusionEdge)) self.membershipField.setValue(project.itemNum(Item.MembershipEdge)) class EdgeInfo(AbstractInfo): """ This class implements the information box for generic edges. """ def __init__(self, session, parent=None): """ Initialize the generic edge information box. :type session: Session :type parent: QtWidgets.QWidget """ super().__init__(session, parent) self.h1 = Header('General', self) self.h1.setFont(Font('Roboto', 12)) self.typeKey = Key('Type', self) self.typeKey.setFont(Font('Roboto', 12)) self.typeField = String(self) self.typeField.setFont(Font('Roboto', 12)) self.typeField.setReadOnly(True) self.sourceKey = Key('Source', self) self.sourceKey.setFont(Font('Roboto', 12)) self.sourceField = String(self) self.sourceField.setFont(Font('Roboto', 12)) self.sourceField.setReadOnly(True) self.targetKey = Key('Target', self) self.targetKey.setFont(Font('Roboto', 12)) self.targetField = String(self) self.targetField.setFont(Font('Roboto', 12)) self.targetField.setReadOnly(True) self.generalLayout = QtWidgets.QFormLayout() self.generalLayout.setSpacing(0) self.generalLayout.addRow(self.typeKey, self.typeField) self.generalLayout.addRow(self.sourceKey, self.sourceField) self.generalLayout.addRow(self.targetKey, self.targetField) 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.h1) self.mainLayout.addLayout(self.generalLayout) ############################################# # INTERFACE ################################# def updateData(self, edge): """ Fetch new information and fill the widget with data. :type edge: AbstractEdge """ self.sourceField.setValue(edge.source.id) self.targetField.setValue(edge.target.id) self.typeField.setValue(edge.shortName.capitalize()) self.typeField.home(True) self.typeField.deselect() class NodeInfo(AbstractInfo): """ This class implements the information box for generic nodes. """ def __init__(self, session, parent=None): """ Initialize the generic node information box. :type session: Session :type parent: QtWidgets.QWidget """ super().__init__(session, parent) self.node = None self.idKey = Key('ID', self) self.idKey.setFont(Font('Roboto', 12)) self.idField = String(self) self.idField.setFont(Font('Roboto', 12)) self.idField.setReadOnly(True) self.identityKey = Key('Identity', self) self.identityKey.setFont(Font('Roboto', 12)) self.identityField = String(self) self.identityField.setFont(Font('Roboto', 12)) self.identityField.setReadOnly(True) self.nodePropHeader = Header('Node properties', self) self.nodePropHeader.setFont(Font('Roboto', 12)) self.nodePropLayout = QtWidgets.QFormLayout() self.nodePropLayout.setSpacing(0) self.nodePropLayout.addRow(self.idKey, self.idField) self.nodePropLayout.addRow(self.identityKey, self.identityField) 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) ############################################# # INTERFACE ################################# def updateData(self, node): """ Fetch new information and fill the widget with data. :type node: AbstractNode """ self.idField.setValue(node.id) self.identityField.setValue(node.identityName) self.node = node class PredicateNodeInfo(NodeInfo): """ This class implements the information box for predicate nodes. """ def __init__(self, session, parent=None): """ Initialize the predicate node information box. :type session: Session :type parent: QtWidgets.QWidget """ super().__init__(session, parent) self.textKey = Key('Label', self) self.textKey.setFont(Font('Roboto', 12)) self.textField = String(self) self.textField.setFont(Font('Roboto', 12)) self.textField.setReadOnly(False) connect(self.textField.editingFinished, self.editingFinished) self.brushKey = Key('Color', self) self.brushKey.setFont(Font('Roboto', 12)) self.brushMenu = QtWidgets.QMenu(self) self.brushButton = Button() self.brushButton.setFont(Font('Roboto', 12)) self.brushButton.setMenu(self.brushMenu) self.brushButton.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred) self.nodePropLayout.addRow(self.brushKey, self.brushButton) self.nodePropLayout.addRow(self.textKey, self.textField) self.nameKey = Key('Name', self) self.nameKey.setFont(Font('Roboto', 12)) self.nameField = String(self) self.nameField.setFont(Font('Roboto', 12)) self.nameField.setReadOnly(False) connect(self.nameField.editingFinished, self.editingFinished) self.predPropHeader = Header('Predicate properties', self) self.predPropHeader.setFont(Font('Roboto', 12)) self.predPropLayout = QtWidgets.QFormLayout() self.predPropLayout.setSpacing(0) self.predPropLayout.addRow(self.nameKey, self.nameField) self.mainLayout.insertWidget(0, self.predPropHeader) self.mainLayout.insertLayout(1, self.predPropLayout) ############################################# # SLOTS ################################# @QtCore.pyqtSlot() def editingFinished(self): """ Executed whenever we finish to edit the predicate/node name. """ if self.node: try: sender = self.sender() node = self.node data = sender.value() data = data if not isEmpty(data) else node.label.template if data != node.text(): diagram = node.diagram project = node.project if sender is self.nameField: self.session.undostack.beginMacro('change predicate "{0}" to "{1}"'.format(node.text(), data)) for n in project.predicates(node.type(), node.text()): self.session.undostack.push(CommandLabelChange(n.diagram, n, n.text(), data, refactor=True)) self.session.undostack.endMacro() else: self.session.undostack.push(CommandLabelChange(diagram, node, node.text(), data)) except RuntimeError: pass self.nameField.clearFocus() self.textField.clearFocus() ############################################# # INTERFACE ################################# def updateData(self, node): """ Fetch new information and fill the widget with data. :type node: AbstractNode """ super().updateData(node) ############################################# # BRUSH FIELD ################################# if self.brushMenu.isEmpty(): self.brushMenu.addActions(self.session.action('brush').actions()) for action in self.session.action('brush').actions(): color = action.data() brush = QtGui.QBrush(QtGui.QColor(color.value)) if node.brush() == brush: self.brushButton.setIcon(BrushIcon(12, 12, color.value, '#000000')) self.brushButton.setText(color.value) break ############################################# # NAME / TEXT FIELDS ################################# self.nameField.setValue(node.text()) self.nameField.home(True) self.nameField.deselect() self.textField.setValue(node.text()) self.textField.home(True) self.textField.deselect() ############################################# # ENABLE / DISABLE REFACTORING ################################# refactor = True if node.type() in {Item.AttributeNode, Item.ConceptNode, Item.RoleNode}: if node.special() is not None: refactor = False self.nameField.setReadOnly(not refactor) class AttributeNodeInfo(PredicateNodeInfo): """ This class implements the information box for the Attribute node. """ def __init__(self, session, parent=None): """ Initialize the Attribute node information box. :type session: Session :type parent: QtWidgets.QWidget """ super().__init__(session, parent) self.functKey = Key('Funct.', self) self.functKey.setFont(Font('Roboto', 12)) functParent = Parent(self) self.functBox = CheckBox(functParent) self.functBox.setCheckable(True) self.functBox.setFont(Font('Roboto', 12)) self.functBox.setProperty('key', K_FUNCTIONAL) connect(self.functBox.clicked, self.flagChanged) self.predPropLayout.addRow(self.functKey, functParent) ############################################# # SLOTS ################################# @QtCore.pyqtSlot() def flagChanged(self): """ Executed whenever one of the property fields changes. """ sender = self.sender() checked = sender.isChecked() key = sender.property('key') undo = self.project.meta(self.node.type(), self.node.text()) redo = undo.copy() redo[key] = checked if redo != undo: prop = RE_CAMEL_SPACE.sub('\g<1> \g<2>', key).lower() name = "{0}set '{1}' {2} property".format('' if checked else 'un', self.node.text(), prop) self.session.undostack.push( CommandNodeSetMeta( self.project, self.node.type(), self.node.text(), undo, redo, name)) ############################################# # INTERFACE ################################# def updateData(self, node): """ Fetch new information and fill the widget with data. :type node: AbstractNode """ super().updateData(node) self.functBox.setChecked(node.isFunctional()) self.functBox.setEnabled(self.project.profile.type() is not OWLProfile.OWL2QL) self.functKey.setEnabled(self.project.profile.type() is not OWLProfile.OWL2QL) class RoleNodeInfo(PredicateNodeInfo): """ This class implements the information box for the Role node. """ def __init__(self, session, parent=None): """ Initialize the Role node information box. :type session: Session :type parent: QtWidgets.QWidget """ super().__init__(session, parent) self.functKey = Key('Funct.', self) self.functKey.setFont(Font('Roboto', 12)) functParent = Parent(self) self.functBox = CheckBox(functParent) self.functBox.setCheckable(True) self.functBox.setFont(Font('Roboto', 12)) self.functBox.setProperty('key', K_FUNCTIONAL) connect(self.functBox.clicked, self.flagChanged) self.invFunctKey = Key('Inv. Funct.', self) self.invFunctKey.setFont(Font('Roboto', 12)) invFunctParent = Parent(self) self.invFunctBox = CheckBox(invFunctParent) self.invFunctBox.setCheckable(True) self.invFunctBox.setFont(Font('Roboto', 12)) self.invFunctBox.setProperty('key', K_INVERSE_FUNCTIONAL) connect(self.invFunctBox.clicked, self.flagChanged) self.asymmetricKey = Key('Asymmetric', self) self.asymmetricKey.setFont(Font('Roboto', 12)) asymmetricParent = Parent(self) self.asymmetricBox = CheckBox(asymmetricParent) self.asymmetricBox.setCheckable(True) self.asymmetricBox.setFont(Font('Roboto', 12)) self.asymmetricBox.setProperty('key', K_ASYMMETRIC) connect(self.asymmetricBox.clicked, self.flagChanged) self.irreflexiveKey = Key('Irreflexive', self) self.irreflexiveKey.setFont(Font('Roboto', 12)) irreflexiveParent = Parent(self) self.irreflexiveBox = CheckBox(irreflexiveParent) self.irreflexiveBox.setCheckable(True) self.irreflexiveBox.setFont(Font('Roboto', 12)) self.irreflexiveBox.setProperty('key', K_IRREFLEXIVE) connect(self.irreflexiveBox.clicked, self.flagChanged) self.reflexiveKey = Key('Reflexive', self) self.reflexiveKey.setFont(Font('Roboto', 12)) reflexiveParent = Parent(self) self.reflexiveBox = CheckBox(reflexiveParent) self.reflexiveBox.setCheckable(True) self.reflexiveBox.setFont(Font('Roboto', 12)) self.reflexiveBox.setProperty('key', K_REFLEXIVE) connect(self.reflexiveBox.clicked, self.flagChanged) self.symmetricKey = Key('Symmetric', self) self.symmetricKey.setFont(Font('Roboto', 12)) symmetricParent = Parent(self) self.symmetricBox = CheckBox(symmetricParent) self.symmetricBox.setCheckable(True) self.symmetricBox.setFont(Font('Roboto', 12)) self.symmetricBox.setProperty('key', K_SYMMETRIC) connect(self.symmetricBox.clicked, self.flagChanged) self.transitiveKey = Key('Transitive', self) self.transitiveKey.setFont(Font('Roboto', 12)) transitiveParent = Parent(self) self.transitiveBox = CheckBox(transitiveParent) self.transitiveBox.setCheckable(True) self.transitiveBox.setFont(Font('Roboto', 12)) self.transitiveBox.setProperty('key', K_TRANSITIVE) connect(self.transitiveBox.clicked, self.flagChanged) self.predPropLayout.addRow(self.functKey, functParent) self.predPropLayout.addRow(self.invFunctKey, invFunctParent) self.predPropLayout.addRow(self.asymmetricKey, asymmetricParent) self.predPropLayout.addRow(self.irreflexiveKey, irreflexiveParent) self.predPropLayout.addRow(self.reflexiveKey, reflexiveParent) self.predPropLayout.addRow(self.symmetricKey, symmetricParent) self.predPropLayout.addRow(self.transitiveKey, transitiveParent) ############################################# # SLOTS ################################# @QtCore.pyqtSlot() def flagChanged(self): """ Executed whenever one of the property fields changes. """ sender = self.sender() checked = sender.isChecked() key = sender.property('key') undo = self.project.meta(self.node.type(), self.node.text()) redo = undo.copy() redo[key] = checked if redo != undo: prop = RE_CAMEL_SPACE.sub('\g<1> \g<2>', key).lower() name = "{0}set '{1}' {2} property".format('' if checked else 'un', self.node.text(), prop) self.session.undostack.push( CommandNodeSetMeta( self.project, self.node.type(), self.node.text(), undo, redo, name)) ############################################# # INTERFACE ################################# def updateData(self, node): """ Fetch new information and fill the widget with data. :type node: AbstractNode """ super().updateData(node) self.asymmetricBox.setChecked(node.isAsymmetric()) self.functBox.setChecked(node.isFunctional()) self.functBox.setEnabled(self.project.profile.type() is not OWLProfile.OWL2QL) self.functKey.setEnabled(self.project.profile.type() is not OWLProfile.OWL2QL) self.invFunctBox.setChecked(node.isInverseFunctional()) self.invFunctBox.setEnabled(self.project.profile.type() is not OWLProfile.OWL2QL) self.invFunctKey.setEnabled(self.project.profile.type() is not OWLProfile.OWL2QL) self.irreflexiveBox.setChecked(node.isIrreflexive()) self.reflexiveBox.setChecked(node.isReflexive()) self.reflexiveBox.setEnabled(self.project.profile.type() is not OWLProfile.OWL2RL) self.reflexiveKey.setEnabled(self.project.profile.type() is not OWLProfile.OWL2RL) self.symmetricBox.setChecked(node.isSymmetric()) self.transitiveBox.setChecked(node.isTransitive()) self.transitiveBox.setEnabled(self.project.profile.type() is not OWLProfile.OWL2QL) self.transitiveKey.setEnabled(self.project.profile.type() is not OWLProfile.OWL2QL) class ValueDomainNodeInfo(NodeInfo): """ This class implements the information box for the Value Domain node. """ def __init__(self, session, parent=None): """ Initialize the Value Domain node information box. :type session: Session :type parent: QtWidgets.QWidget """ super().__init__(session, parent) self.datatypeKey = Key('Datatype', self) self.datatypeKey.setFont(Font('Roboto', 12)) self.datatypeField = Select(self) self.datatypeField.setFont(Font('Roboto', 12)) connect(self.datatypeField.activated, self.datatypeChanged) self.nodePropLayout.addRow(self.datatypeKey, self.datatypeField) ############################################# # SLOTS ################################# @QtCore.pyqtSlot() def datatypeChanged(self): """ Executed when we need to change the datatype. """ if self.node: node = self.node diagram = node.diagram datatype = self.datatypeField.currentData() data = datatype.value if node.text() != data: name = 'change {0} to {1}'.format(node.shortName, data) self.session.undostack.push(CommandLabelChange(diagram, node, node.text(), data, name=name)) self.datatypeField.clearFocus() ############################################# # INTERFACE ################################# def updateData(self, node): """ Fetch new information and fill the widget with data. :type node: AbstractNode """ super().updateData(node) self.datatypeField.clear() for datatype in sorted(Datatype.forProfile(self.project.profile.type()), key=attrgetter('value')): self.datatypeField.addItem(datatype.value, datatype) self.datatypeField.setCurrentText(node.datatype.value) class ValueNodeInfo(NodeInfo): """ This class implements the information box for the Individual node with identity 'Value'. """ def __init__(self, session, parent=None): """ Initialize the Value node information box. :type session: Session :type parent: QtWidgets.QWidget """ super().__init__(session, parent) self.datatypeKey = Key('Datatype', self) self.datatypeKey.setFont(Font('Roboto', 12)) self.datatypeField = Select(self) self.datatypeField.setFont(Font('Roboto', 12)) connect(self.datatypeField.activated, self.valueChanged) self.valueKey = Key('Value', self) self.valueKey.setFont(Font('Roboto', 12)) self.valueField = String(self) self.valueField.setFont(Font('Roboto', 12)) self.valueField.setReadOnly(False) connect(self.valueField.editingFinished, self.valueChanged) self.nodePropLayout.addRow(self.datatypeKey, self.datatypeField) self.nodePropLayout.addRow(self.valueKey, self.valueField) ############################################# # SLOTS ################################# @QtCore.pyqtSlot() def valueChanged(self): """ Executed when we need to recompute the Value. """ if self.node: try: node = self.node diagram = node.diagram datatype = self.datatypeField.currentData() value = self.valueField.value() data = node.compose(value, datatype) if node.text() != data: name = 'change value to {0}'.format(data) self.session.undostack.push(CommandLabelChange(diagram, node, node.text(), data, name=name)) except RuntimeError: pass self.datatypeField.clearFocus() self.valueField.clearFocus() ############################################# # INTERFACE ################################# def updateData(self, node): """ Fetch new information and fill the widget with data. :type node: AbstractNode """ super().updateData(node) ############################################# # DATATYPE FIELD ################################# self.datatypeField.clear() for datatype in sorted(Datatype.forProfile(self.project.profile.type()), key=attrgetter('value')): self.datatypeField.addItem(datatype.value, datatype) self.datatypeField.setCurrentText(node.datatype.value) ############################################# # VALUE FIELD ################################# self.valueField.setValue(node.value) class FacetNodeInfo(NodeInfo): """ This class implements the information box for the Facet node. """ def __init__(self, session, parent=None): """ Initialize the Value Restriction node information box. :type session: Session :type parent: QtWidgets.QWidget """ super().__init__(session, parent) self.facetKey = Key('Facet', self) self.facetKey.setFont(Font('Roboto', 12)) self.facetField = Select(self) self.facetField.setFont(Font('Roboto', 12)) connect(self.facetField.activated, self.facetChanged) self.valueKey = Key('Value', self) self.valueKey.setFont(Font('Roboto', 12)) self.valueField = String(self) self.valueField.setFont(Font('Roboto', 12)) self.valueField.setReadOnly(False) connect(self.valueField.editingFinished, self.facetChanged) self.nodePropLayout.addRow(self.facetKey, self.facetField) self.nodePropLayout.addRow(self.valueKey, self.valueField) ############################################# # SLOTS ################################# @QtCore.pyqtSlot() def facetChanged(self): """ Executed when we need to recompute the value of the node. """ if self.node: node = self.node diagram = node.diagram data = node.compose(self.facetField.currentData(), self.valueField.value()) if node.text() != data: name = 'change {0} to {1}'.format(node.text(), data) self.session.undostack.push(CommandLabelChange(diagram, node, node.text(), data, name=name)) self.facetField.clearFocus() self.valueField.clearFocus() ############################################# # INTERFACE ################################# def updateData(self, node): """ Fetch new information and fill the widget with data. :type node: AbstractNode """ super().updateData(node) ############################################# # FACET FIELD ################################# f1 = lambda x: x.type() is Item.InputEdge f2 = lambda x: x.type() is Item.DatatypeRestrictionNode f3 = lambda x: x.type() is Item.ValueDomainNode admissible = [x for x in Facet] restriction = first(node.outgoingNodes(filter_on_edges=f1, filter_on_nodes=f2)) if restriction: valuedomain = first(restriction.incomingNodes(filter_on_edges=f1, filter_on_nodes=f3)) if valuedomain: admissible = Facet.forDatatype(valuedomain.datatype) self.facetField.clear() for facet in admissible: self.facetField.addItem(facet.value, facet) facet = node.facet for i in range(self.facetField.count()): if self.facetField.itemData(i) is facet: self.facetField.setCurrentIndex(i) break else: self.facetField.setCurrentIndex(0) ############################################# # VALUE FIELD ################################# self.valueField.setValue(node.value)