class OntologyExplorerWidget(QtWidgets.QWidget): """ This class implements the ontology explorer used to list ontology predicates. """ sgnItemActivated = QtCore.pyqtSignal('QGraphicsItem') sgnItemClicked = QtCore.pyqtSignal('QGraphicsItem') sgnItemDoubleClicked = QtCore.pyqtSignal('QGraphicsItem') sgnItemRightClicked = QtCore.pyqtSignal('QGraphicsItem') sgnIRIItemActivated = QtCore.pyqtSignal(IRI) sgnIRIItemClicked = QtCore.pyqtSignal(IRI) sgnIRIItemDoubleClicked = QtCore.pyqtSignal(IRI) sgnIRIItemRightClicked = QtCore.pyqtSignal(IRI) def __init__(self, plugin): """ Initialize the ontology explorer widget. :type plugin: Session """ super().__init__(plugin.session) self.plugin = plugin self.items = [ Item.ConceptIRINode, Item.RoleIRINode, Item.AttributeIRINode, Item.IndividualIRINode, Item.ValueDomainIRINode ] self.unsatisfiableItems = list() self.unsatisfiableClasses = list() self.unsatisfiableObjProps = list() self.unsatisfiableDataProps = list() 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.searchShortcut = QtWidgets.QShortcut(QtGui.QKeySequence('Ctrl+f'), self.session) self.search = StringField(self) self.search.setAcceptDrops(False) self.search.setClearButtonEnabled(True) self.search.setPlaceholderText('Search...') self.search.setToolTip('Search ({})'.format( self.searchShortcut.key().toString(QtGui.QKeySequence.NativeText))) self.search.setFixedHeight(30) self.model = QtGui.QStandardItemModel(self) self.proxy = OntologyExplorerFilterProxyModel(self) self.proxy.setDynamicSortFilter(False) self.proxy.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive) self.proxy.setSortCaseSensitivity(QtCore.Qt.CaseSensitive) self.proxy.setSourceModel(self.model) self.ontoview = OntologyExplorerView(self) self.ontoview.setModel(self.proxy) self.mainLayout = QtWidgets.QVBoxLayout(self) self.mainLayout.setContentsMargins(0, 0, 0, 0) self.mainLayout.addWidget(self.search) self.mainLayout.addWidget(self.ontoview) self.setTabOrder(self.search, self.ontoview) 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; } """) header = self.ontoview.header() header.setStretchLastSection(False) header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents) connect(self.ontoview.activated, self.onItemActivated) connect(self.ontoview.doubleClicked, self.onItemDoubleClicked) connect(self.ontoview.pressed, self.onItemPressed) connect(self.search.textChanged, self.doFilterItem) connect(self.search.returnPressed, self.onReturnPressed) connect(self.searchShortcut.activated, self.doFocusSearch) connect(self.sgnItemActivated, self.session.doFocusItem) connect(self.sgnItemDoubleClicked, self.session.doFocusItem) connect(self.sgnItemRightClicked, self.session.doFocusItem) connect(self.session.sgnPrefixAdded, self.onPrefixAdded) connect(self.session.sgnPrefixRemoved, self.onPrefixRemoved) connect(self.session.sgnPrefixModified, self.onPrefixModified) connect(self.session.sgnRenderingModified, self.onRenderingModified) connect(self.session.sgnIRIRemovedFromAllDiagrams, self.onIRIRemovedFromAllDiagrams) connect(self.session.sgnSingleNodeSwitchIRI, self.onSingleNodeIRISwitched) ############################################# # 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 onRenderingModified(self, rendering): # RESET MODEL DISPLAY DATA # Here we force te explorer view to become invisible while we update # its model in order to avoid unnecessary UI updates that could # otherwise cause the entire application to become unresponsive. # This is kind of a hack but it works for the moment. self.ontoview.setVisible(False) for index in range(self.model.rowCount()): item = self.model.item(index) data = item.data(OntologyExplorerView.IRIRole) if isinstance(data, IRI): item.setText(self.parentKeyForIRI(data)) self.model.dataChanged.emit( self.model.index(0, 0), self.model.index(self.model.rowCount() - 1, 0)) self.ontoview.setVisible(True) # APPLY FILTERS AND SORT self.proxy.invalidateFilter() self.proxy.sort(0, QtCore.Qt.AscendingOrder) @QtCore.pyqtSlot(str, str) def onPrefixAdded(self, _prefix: str, _ns: str): settings = QtCore.QSettings() rendering = settings.value('ontology/iri/render', IRIRender.PREFIX.value, str) if rendering == IRIRender.PREFIX.value or rendering == IRIRender.LABEL.value: self.redrawIRIItem() @QtCore.pyqtSlot(str) def onPrefixRemoved(self, _: str): settings = QtCore.QSettings() rendering = settings.value('ontology/iri/render', IRIRender.PREFIX.value, str) if rendering == IRIRender.PREFIX.value or rendering == IRIRender.LABEL.value: self.redrawIRIItem() @QtCore.pyqtSlot(str) def onPrefixModified(self, _: str): settings = QtCore.QSettings() rendering = settings.value('ontology/iri/render', IRIRender.PREFIX.value, str) if rendering == IRIRender.PREFIX.value or rendering == IRIRender.LABEL.value: self.redrawIRIItem() @QtCore.pyqtSlot(str) def onIRIModified(self, _: str): iri = self.sender() self.redrawIRIItem(iri) @QtCore.pyqtSlot(AnnotationAssertion) def onIRIAnnotationAssertionAdded(self, _): iri = self.sender() settings = QtCore.QSettings() rendering = settings.value('ontology/iri/render', IRIRender.PREFIX.value, str) if rendering == IRIRender.PREFIX.value or rendering == IRIRender.LABEL.value: self.redrawIRIItem(iri) @QtCore.pyqtSlot(AnnotationAssertion) def onIRIAnnotationAssertionRemoved(self, _): iri = self.sender() settings = QtCore.QSettings() rendering = settings.value('ontology/iri/render', IRIRender.PREFIX.value, str) if rendering == IRIRender.PREFIX.value or rendering == IRIRender.LABEL.value: self.redrawIRIItem(iri) @QtCore.pyqtSlot(AnnotationAssertion) def onIRIAnnotationAssertionModified(self, _): iri = self.sender() settings = QtCore.QSettings() rendering = settings.value('ontology/iri/render', IRIRender.PREFIX.value, str) if rendering == IRIRender.PREFIX.value or rendering == IRIRender.LABEL.value: self.redrawIRIItem(iri) @QtCore.pyqtSlot() def onNodeIRISwitched(self): node = self.sender() self.doAddNode(node.diagram, node) @QtCore.pyqtSlot(AbstractNode, IRI) def onSingleNodeIRISwitched(self, node, oldIRI): oldParentK = self.parentKeyForIRI(oldIRI) for parent in self.model.findItems(oldParentK, QtCore.Qt.MatchExactly): rowCount = parent.rowCount() for i in range(rowCount): child = parent.child(i) if child.data(OntologyExplorerView.IRIRole) is node: parent.removeRow(i) break if not parent.rowCount(): if isinstance(node, ( OntologyEntityNode, OntologyEntityResizableNode, )): self.disconnectIRISignals( parent.data(OntologyExplorerView.IRIRole)) self.model.removeRow(parent.index().row()) @QtCore.pyqtSlot(IRI) def onIRIRemovedFromAllDiagrams(self, iri): parentK = self.parentKeyForIRI(iri) for parent in self.model.findItems(parentK, QtCore.Qt.MatchExactly): ''' removeParent = True rowCount = parent.rowCount() for i in range(rowCount): childData = parent.child(i).data(QtCore.Qt.UserRole) if isinstance(childData,OntologyEntityNode) or isinstance(childData, OntologyEntityResizableNode): parent.removeRow(i) else: removeParent = False if removeParent: self.model.removeRow(parent.index().row()) ''' self.model.removeRow(parent.index().row()) @QtCore.pyqtSlot(IRI) def onUnsatisfiableClass(self, iri): parent = self.parentForIRI(iri) if parent: parent.setData(OntologyExplorerView.UnsatisfiableBrush, QtCore.Qt.ForegroundRole) self.unsatisfiableItems.append(parent) self.unsatisfiableClasses.append(parent) @QtCore.pyqtSlot(IRI) def onUnsatisfiableObjectProperty(self, iri): parent = self.parentForIRI(iri) if parent: parent.setData(OntologyExplorerView.UnsatisfiableBrush, QtCore.Qt.ForegroundRole) self.unsatisfiableItems.append(parent) self.unsatisfiableObjProps.append(parent) @QtCore.pyqtSlot(IRI) def onUnsatisfiableDataProperty(self, iri): parent = self.parentForIRI(iri) if parent: parent.setData(OntologyExplorerView.UnsatisfiableBrush, QtCore.Qt.ForegroundRole) self.unsatisfiableItems.append(parent) self.unsatisfiableDataProps.append(parent) @QtCore.pyqtSlot() def doResetReasonerHighlight(self): for item in self.unsatisfiableItems: item.setData(None, QtCore.Qt.ForegroundRole) self.unsatisfiableItems = list() self.unsatisfiableClasses = list() self.unsatisfiableObjProps = list() self.unsatisfiableDataProps = list() @QtCore.pyqtSlot(ImportedOntology) def onImportedOntologyAdded(self, impOnt): """ :param impOnt:ImportedOntology :return: """ for classIRI in impOnt.classes: parent = self.parentForIRI(classIRI) if not parent: parent = QtGui.QStandardItem(self.parentKeyForIRI(classIRI)) parent.setData(classIRI, OntologyExplorerView.IRIRole) self.connectIRISignals(classIRI) self.model.appendRow(parent) child = QtGui.QStandardItem( self.childKeyForImported(impOnt, classIRI)) # CHECK FOR DUPLICATE NODES children = [parent.child(i) for i in range(parent.rowCount())] if not any([(child.text() == c.text() and c.icon() is self.iconConcept) for c in children]): child.setIcon(self.iconConcept) childData = [classIRI, Item.ConceptIRINode.value] child.setData(childData, OntologyExplorerView.IRIRole) parent.appendRow(child) for objPropIRI in impOnt.objectProperties: parent = self.parentForIRI(objPropIRI) if not parent: parent = QtGui.QStandardItem(self.parentKeyForIRI(objPropIRI)) parent.setData(objPropIRI, OntologyExplorerView.IRIRole) self.connectIRISignals(objPropIRI) self.model.appendRow(parent) child = QtGui.QStandardItem( self.childKeyForImported(impOnt, objPropIRI)) # CHECK FOR DUPLICATE NODES children = [parent.child(i) for i in range(parent.rowCount())] if not any([(child.text() == c.text() and c.icon() is self.iconRole) for c in children]): child.setIcon(self.iconRole) childData = [objPropIRI, Item.RoleIRINode.value] child.setData(childData, OntologyExplorerView.IRIRole) parent.appendRow(child) for dataPropIRI in impOnt.dataProperties: parent = self.parentForIRI(dataPropIRI) if not parent: parent = QtGui.QStandardItem(self.parentKeyForIRI(dataPropIRI)) parent.setData(dataPropIRI, OntologyExplorerView.IRIRole) self.connectIRISignals(dataPropIRI) self.model.appendRow(parent) child = QtGui.QStandardItem( self.childKeyForImported(impOnt, dataPropIRI)) # CHECK FOR DUPLICATE NODES children = [parent.child(i) for i in range(parent.rowCount())] if not any([(child.text() == c.text() and c.icon() is self.iconAttribute) for c in children]): child.setIcon(self.iconAttribute) childData = [dataPropIRI, Item.AttributeIRINode.value] child.setData(childData, OntologyExplorerView.IRIRole) parent.appendRow(child) for indIRI in impOnt.individuals: parent = self.parentForIRI(indIRI) if not parent: parent = QtGui.QStandardItem(self.parentKeyForIRI(indIRI)) parent.setData(indIRI, OntologyExplorerView.IRIRole) self.connectIRISignals(indIRI) self.model.appendRow(parent) child = QtGui.QStandardItem( self.childKeyForImported(impOnt, indIRI)) # CHECK FOR DUPLICATE NODES children = [parent.child(i) for i in range(parent.rowCount())] if not any([(child.text() == c.text() and c.icon() is self.iconInstance) for c in children]): child.setIcon(self.iconInstance) childData = [indIRI, Item.IndividualIRINode.value] child.setData(childData, OntologyExplorerView.IRIRole) parent.appendRow(child) # APPLY FILTERS AND SORT if self.sender() != self.plugin: self.proxy.invalidateFilter() self.proxy.sort(0, QtCore.Qt.AscendingOrder) @QtCore.pyqtSlot(ImportedOntology) def onImportedOntologyRemoved(self, impOnt): """ :param impOnt:ImportedOntology :return: """ for classIRI in impOnt.classes: parent = self.parentForIRI(classIRI) if parent: child = self.childForImported(parent, impOnt, classIRI) if child: parent.removeRow((child.index().row())) if not parent.rowCount(): self.disconnectIRISignals(classIRI) self.model.removeRow(parent.index().row()) for objPropIRI in impOnt.objectProperties: parent = self.parentForIRI(objPropIRI) if parent: child = self.childForImported(parent, impOnt, objPropIRI) if child: parent.removeRow((child.index().row())) if not parent.rowCount(): self.disconnectIRISignals(objPropIRI) self.model.removeRow(parent.index().row()) for dataPropIRI in impOnt.dataProperties: parent = self.parentForIRI(dataPropIRI) if parent: child = self.childForImported(parent, impOnt, dataPropIRI) if child: parent.removeRow((child.index().row())) if not parent.rowCount(): self.disconnectIRISignals(dataPropIRI) self.model.removeRow(parent.index().row()) for indIRI in impOnt.individuals: parent = self.parentForIRI(indIRI) if parent: child = self.childForImported(parent, impOnt, indIRI) if child: parent.removeRow((child.index().row())) if not parent.rowCount(): self.disconnectIRISignals(indIRI) self.model.removeRow(parent.index().row()) # APPLY FILTERS AND SORT if self.sender() != self.plugin: self.proxy.invalidateFilter() self.proxy.sort(0, QtCore.Qt.AscendingOrder) @QtCore.pyqtSlot('QGraphicsScene', 'QGraphicsItem') def doAddNode(self, diagram, node): """ Add a node in the tree view. :type diagram: QGraphicsScene :type node: AbstractItem """ if node.type() in self.items: parent = self.parentFor(node) if not parent: if not isinstance(node, ( OntologyEntityNode, OntologyEntityResizableNode, )): parent = QtGui.QStandardItem(self.parentKey(node)) parent.setIcon(self.iconFor(node)) else: parent = QtGui.QStandardItem(self.parentKeyForIRI( node.iri)) parent.setData(node.iri, OntologyExplorerView.IRIRole) self.connectIRISignals(node.iri) self.model.appendRow(parent) child = QtGui.QStandardItem(self.childKey(diagram, node)) if isinstance(node, ( OntologyEntityNode, OntologyEntityResizableNode, )): child.setIcon(self.iconFor(node)) connect(node.sgnIRISwitched, self.onNodeIRISwitched) child.setData(node, OntologyExplorerView.IRIRole) parent.appendRow(child) # APPLY FILTERS AND SORT if self.sender() != self.plugin: self.proxy.invalidateFilter() self.proxy.sort(0, QtCore.Qt.AscendingOrder) @QtCore.pyqtSlot('QGraphicsScene', 'QGraphicsItem') def doRemoveNode(self, diagram, node): """ Remove a node from the tree view. :type diagram: QGraphicsScene :type node: AbstractItem """ if node.type() in self.items: parent = self.parentFor(node) if parent: child = self.childFor(parent, diagram, node) if child: parent.removeRow(child.index().row()) if not parent.rowCount(): if isinstance(node, ( OntologyEntityNode, OntologyEntityResizableNode, )): self.disconnectIRISignals( parent.data(OntologyExplorerView.IRIRole)) self.model.removeRow(parent.index().row()) @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 doFocusSearch(self): """ Focus the search bar. """ # RAISE THE ENTIRE WIDGET TREE IF IT IS NOT VISIBLE if not self.isVisible(): widget = self while widget != self.session: widget.show() widget.raise_() widget = widget.parent() self.search.setFocus() self.search.selectAll() @QtCore.pyqtSlot('QModelIndex') def onItemActivated(self, index): """ Executed when an item in the treeview 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 and item.data(OntologyExplorerView.IRIRole): if isinstance(item.data(OntologyExplorerView.IRIRole), IRI): self.sgnIRIItemActivated.emit( item.data(OntologyExplorerView.IRIRole)) else: self.sgnItemActivated.emit( item.data(OntologyExplorerView.IRIRole)) # KEEP FOCUS ON THE TREE VIEW UNLESS SHIFT IS PRESSED if QtWidgets.QApplication.queryKeyboardModifiers( ) & QtCore.Qt.SHIFT: return self.ontoview.setFocus() elif item: # EXPAND/COLLAPSE PARENT ITEM if self.ontoview.isExpanded(index): self.ontoview.collapse(index) else: self.ontoview.expand(index) @QtCore.pyqtSlot('QModelIndex') def onItemDoubleClicked(self, index): """ Executed when an item in the treeview 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 and item.data(OntologyExplorerView.IRIRole): if isinstance(item.data(OntologyExplorerView.IRIRole), IRI): self.sgnIRIItemDoubleClicked.emit( item.data(OntologyExplorerView.IRIRole)) elif isinstance(item.data(OntologyExplorerView.IRIRole), AbstractNode): self.sgnItemDoubleClicked.emit( item.data(OntologyExplorerView.IRIRole)) @QtCore.pyqtSlot('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 and item.data(OntologyExplorerView.IRIRole): if isinstance(item.data(OntologyExplorerView.IRIRole), IRI): self.sgnIRIItemClicked.emit( item.data(OntologyExplorerView.IRIRole)) elif isinstance(item.data(OntologyExplorerView.IRIRole), AbstractNode): self.sgnItemClicked.emit( item.data(OntologyExplorerView.IRIRole)) @QtCore.pyqtSlot() def onReturnPressed(self): """ Executed when the Return or Enter key is pressed in the search field. """ self.focusNextChild() ############################################# # INTERFACE ################################# def connectNodeSignals(self, node): """ :type node: OntologyEntityNode | OntologyEntityResizableNode """ connect(node.sgnIRISwitched, self.onNodeIRISwitched) def connectIRISignals(self, iri): """ :type iri: IRI """ connect(iri.sgnAnnotationAdded, self.onIRIAnnotationAssertionAdded) connect(iri.sgnAnnotationRemoved, self.onIRIAnnotationAssertionRemoved) connect(iri.sgnAnnotationModified, self.onIRIAnnotationAssertionModified) connect(iri.sgnIRIModified, self.onIRIModified) def disconnectIRISignals(self, iri): """ :type iri: IRI """ disconnect(iri.sgnAnnotationAdded, self.onIRIAnnotationAssertionAdded) disconnect(iri.sgnAnnotationRemoved, self.onIRIAnnotationAssertionRemoved) disconnect(iri.sgnAnnotationModified, self.onIRIAnnotationAssertionModified) disconnect(iri.sgnIRIModified, self.onIRIModified) def redrawIRIItem(self, iri=None): self.ontoview.setSortingEnabled(False) for row in range(self.model.rowCount()): currItem = self.model.item(row) if iri: currIRI = currItem.data(OntologyExplorerView.IRIRole) if currIRI is iri: currItem.setText(self.parentKeyForIRI(iri)) break else: if isinstance(currItem.data(OntologyExplorerView.IRIRole), IRI): currItem.setText( self.parentKeyForIRI( currItem.data(OntologyExplorerView.IRIRole))) self.ontoview.setSortingEnabled(True) if self.sender() != self.plugin: self.proxy.invalidateFilter() self.proxy.sort(0, QtCore.Qt.AscendingOrder) def childFor(self, parent, diagram, node): """ Search the item representing this node among parent children. :type parent: QtGui.QStandardItem :type diagram: Diagram :type node: AbstractNode """ key = self.childKey(diagram, node) for i in range(parent.rowCount()): child = parent.child(i) if child.text() == key: return child return None def childForImported(self, parent, impOnt, iri): """ Search the item representing this node among parent children. :type parent: QtGui.QStandardItem :type impOnt: ImportedOntology :type iri: IRI """ key = self.childKeyForImported(impOnt, iri) for i in range(parent.rowCount()): child = parent.child(i) if child.text() == key: return child return None @staticmethod def childKey(diagram, node): """ Returns the child key (text) used to place the given node in the treeview. :type diagram: Diagram :type node: AbstractNode :rtype: str """ diagram = rstrip(diagram.name, File.Graphol.extension) if isinstance(node, (OntologyEntityNode, OntologyEntityResizableNode)): return '{0} - {1}'.format(diagram, node.id) else: predicate = node.text().replace('\n', '') return '{0} ({1} - {2})'.format(predicate, diagram, node.id) @staticmethod def childKeyForImported(impOnt, iri): """ Returns the child key (text) used to place the given node in the treeview. :type impOnt: ImportedOntology :type iri: IRI :rtype: str """ return 'Imported from {}'.format(impOnt.docLocation) def iconFor(self, node): """ Returns the icon for the given node. :type node: """ if node.type() is Item.AttributeIRINode: return self.iconAttribute if node.type() is Item.ConceptIRINode: return self.iconConcept if node.type() is Item.IndividualIRINode: return self.iconInstance if node.type() is Item.RoleIRINode: return self.iconRole if node.type() is Item.ValueDomainIRINode: return self.iconValue def parentFor(self, node): """ Search the parent element of the given node. :type node: AbstractNode :rtype: QtGui.QStandardItem """ if isinstance(node, ( OntologyEntityNode, OntologyEntityResizableNode, )): parentK = self.parentKeyForIRI(node.iri) for i in self.model.findItems(parentK, QtCore.Qt.MatchExactly): parentIRI = i.data(OntologyExplorerView.IRIRole) if node.iri is parentIRI: return i else: parentK = self.parentKey(node) for i in self.model.findItems(parentK, QtCore.Qt.MatchExactly): n = i.child(0).data(OntologyExplorerView.IRIRole) if node.type() is n.type(): return i return None def parentForIRI(self, iri): """ Search the parent element of the given iri. :type node: IRI :rtype: QtGui.QStandardItem """ parentK = self.parentKeyForIRI(iri) for i in self.model.findItems(parentK, QtCore.Qt.MatchExactly): parentIRI = i.data(OntologyExplorerView.IRIRole) if iri is parentIRI: return i return None @staticmethod def parentKeyForIRI(iri): return IRIRender.iriLabelString(iri).replace('\n', '') @staticmethod def parentKey(node): """ Returns the parent key (text) used to place the given node in the treeview. :type node: AbstractNode :type project Project :rtype: str """ return node.text().replace('\n', '') def sizeHint(self): """ Returns the recommended size for this widget. :rtype: QtCore.QSize """ return QtCore.QSize(216, 266)
class TableExplorerWidget(QtWidgets.QWidget): """ This class implements the schema explorer used to list schema tables. """ sgnGraphicalNodeItemClicked = QtCore.pyqtSignal('QGraphicsItem') sgnGraphicalNodeItemActivated = QtCore.pyqtSignal('QGraphicsItem') sgnGraphicalNodeItemDoubleClicked = QtCore.pyqtSignal('QGraphicsItem') sgnGraphicalNodeItemRightClicked = QtCore.pyqtSignal('QGraphicsItem') sgnRelationalTableItemClicked = QtCore.pyqtSignal(RelationalTable) sgnRelationalTableItemActivated = QtCore.pyqtSignal(RelationalTable) sgnRelationalTableItemDoubleClicked = QtCore.pyqtSignal(RelationalTable) sgnRelationalTableItemRightClicked = QtCore.pyqtSignal(RelationalTable) def __init__(self, plugin): super().__init__(plugin.session) self.plugin = plugin self.items = [ EntityType.Class, EntityType.ObjectProperty, EntityType.DataProperty ] self.classIcon = QtGui.QIcon(':/icons/18/ic_treeview_concept') self.objPropIcon = QtGui.QIcon(':/icons/18/ic_treeview_role') self.dataPropIcon = QtGui.QIcon(':/icons/18/ic_treeview_attribute') self.searchShortcut = QtWidgets.QShortcut( QtGui.QKeySequence('Ctrl+f+t'), plugin.session) self.search = StringField(self) self.search.setAcceptDrops(False) self.search.setClearButtonEnabled(True) self.search.setPlaceholderText('Search...') self.search.setToolTip('Search ({})'.format( self.searchShortcut.key().toString(QtGui.QKeySequence.NativeText))) self.search.setFixedHeight(30) self.model = QtGui.QStandardItemModel(self) self.proxy = TableExplorerFilterProxyModel(self) self.proxy.setDynamicSortFilter(False) self.proxy.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive) self.proxy.setSortCaseSensitivity(QtCore.Qt.CaseSensitive) self.proxy.setSourceModel(self.model) self.tableview = TableExplorerView(self) self.tableview.setModel(self.proxy) self.mainLayout = QtWidgets.QVBoxLayout(self) self.mainLayout.setContentsMargins(0, 0, 0, 0) self.mainLayout.addWidget(self.search) self.mainLayout.addWidget(self.tableview) self.setTabOrder(self.search, self.tableview) 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; } """) header = self.tableview.header() header.setStretchLastSection(False) header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents) connect(plugin.sgnSchemaChanged, self.onSchemaChanged) connect(plugin.sgnNodeAdded, self.doAddNode) connect(self.tableview.pressed, self.onItemPressed) connect(self.tableview.doubleClicked, self.onItemDoubleClicked) connect(self.search.textChanged, self.doFilterItem) connect(self.search.returnPressed, self.onReturnPressed) connect(self.searchShortcut.activated, self.doFocusSearch) connect(self.sgnGraphicalNodeItemActivated, self.plugin.doFocusItem) connect(self.sgnGraphicalNodeItemClicked, self.plugin.doFocusItem) connect(self.sgnGraphicalNodeItemDoubleClicked, self.plugin.doFocusItem) connect(self.sgnGraphicalNodeItemRightClicked, self.plugin.doFocusItem) connect(self.sgnRelationalTableItemActivated, self.plugin.doFocusTable) connect(self.sgnRelationalTableItemClicked, self.plugin.doFocusTable) connect(self.sgnRelationalTableItemDoubleClicked, self.plugin.doFocusTable) connect(self.sgnRelationalTableItemRightClicked, self.plugin.doFocusTable) ############################################# # SLOTS ################################# @QtCore.pyqtSlot(RelationalSchema) def onSchemaChanged(self, schema): """ Add a node in the tree view. :type schema: RelationalSchema """ self.model.clear() @QtCore.pyqtSlot(BlackBirdDiagram, TableNode) def doAddNode(self, diagram, node): """ Add a node in the tree view. :type diagram: QGraphicsScene :type node: TableNode """ parent = self.parentFor(node) if not parent: parent = QtGui.QStandardItem(self.parentKey(node)) parent.setIcon(self.iconFor(node.relationalTable)) parent.setData(node.relationalTable) self.model.appendRow(parent) child = QtGui.QStandardItem(self.childKey(diagram, node)) child.setData(node) parent.appendRow(child) # APPLY FILTERS AND SORT if self.sender() != self.plugin: self.proxy.invalidateFilter() self.proxy.sort(0, QtCore.Qt.AscendingOrder) else: self.doFilterItem('') @QtCore.pyqtSlot('QGraphicsScene', 'QGraphicsItem') def doRemoveNode(self, diagram, node): """ Remove a node from the tree view. :type diagram: QGraphicsScene :type node: AbstractItem """ pass @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 doFocusSearch(self): """ Focus the search bar. """ # RAISE THE ENTIRE WIDGET TREE IF IT IS NOT VISIBLE if not self.isVisible(): widget = self while widget != self.session: widget.show() widget.raise_() widget = widget.parent() self.search.setFocus() self.search.selectAll() @QtCore.pyqtSlot() def onReturnPressed(self): """ Executed when the Return or Enter key is pressed in the search field. """ self.focusNextChild() @QtCore.pyqtSlot('QModelIndex') def onItemActivated(self, index): """ Executed when an item in the treeview 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 and item.data(): if isinstance(item.data(), RelationalTable): self.sgnRelationalTableItemActivated.emit(item.data()) elif isinstance(item.data(), TableNode): # self.sgnGraphicalNodeItemActivated.emit(item.data()) self.sgnRelationalTableItemActivated.emit( item.data().relationalTable) # KEEP FOCUS ON THE TREE VIEW UNLESS SHIFT IS PRESSED if QtWidgets.QApplication.queryKeyboardModifiers( ) & QtCore.Qt.SHIFT: return self.tableview.setFocus() elif item: # EXPAND/COLLAPSE PARENT ITEM if self.tableview.isExpanded(index): self.tableview.collapse(index) else: self.tableview.expand(index) @QtCore.pyqtSlot('QModelIndex') def onItemDoubleClicked(self, index): """ Executed when an item in the treeview 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 and item.data(): if isinstance(item.data(), RelationalTable): self.sgnRelationalTableItemDoubleClicked.emit(item.data()) elif isinstance(item.data(), TableNode): self.sgnGraphicalNodeItemDoubleClicked.emit(item.data()) self.sgnRelationalTableItemDoubleClicked.emit( item.data().relationalTable) @QtCore.pyqtSlot('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 and item.data(): if isinstance(item.data(), RelationalTable): self.sgnRelationalTableItemClicked.emit(item.data()) elif isinstance(item.data(), TableNode): # self.sgnGraphicalNodeItemClicked.emit(item.data()) self.sgnRelationalTableItemClicked.emit( item.data().relationalTable) ############################################# # INTERFACE ################################# def iconFor(self, table): """ Returns the icon for the given node. :type table:RelationalTable """ entity = table.entity entityType = entity.entityType if entityType is EntityType.Class: return self.classIcon if entityType is EntityType.ObjectProperty: return self.objPropIcon if entityType is EntityType.DataProperty: return self.dataPropIcon def parentFor(self, node): """ Search the parent element of the given node. :type node: TableNode :rtype: QtGui.QStandardItem """ for i in self.model.findItems(self.parentKey(node), QtCore.Qt.MatchExactly): if i.child(0): n = i.child(0).data() if node.type() is n.type(): return i return None @staticmethod def childKey(diagram, node): """ Returns the child key (text) used to place the given node in the treeview. :type diagram: Diagram :type node: TableNode :rtype: str """ diagram = rstrip(diagram.name, File.Graphol.extension) return '[{0} - {1}] ({2})'.format(diagram, node.id, node.relationalTable.name) @staticmethod def parentKey(node): """ Returns the parent key (text) used to place the given node in the treeview. :type node: Union[TableNode,RelationalTable] :rtype: str """ if isinstance(node, RelationalTable): return node.name if isinstance(node, TableNode): return node.relationalTable.name def sizeHint(self): """ Returns the recommended size for this widget. :rtype: QtCore.QSize """ return QtCore.QSize(216, 266)
class OntologyExplorerWidget(QtWidgets.QWidget): """ This class implements the ontology explorer used to list ontology predicates. """ sgnItemActivated = QtCore.pyqtSignal('QGraphicsItem') sgnItemClicked = QtCore.pyqtSignal('QGraphicsItem') sgnItemDoubleClicked = QtCore.pyqtSignal('QGraphicsItem') sgnItemRightClicked = QtCore.pyqtSignal('QGraphicsItem') def __init__(self, plugin): """ Initialize the ontology explorer widget. :type plugin: Session """ super().__init__(plugin.session) self.plugin = plugin self.items = [ Item.ConceptNode, Item.RoleNode, Item.AttributeNode, Item.IndividualNode ] self.status = [Status.DEFAULT, Status.DRAFT, Status.FINAL] 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.searchShortcut = QtWidgets.QShortcut(QtGui.QKeySequence('Ctrl+f'), self.session) self.search = StringField(self) self.search.setAcceptDrops(False) self.search.setClearButtonEnabled(True) self.search.setPlaceholderText('Search...') self.search.setToolTip('Search ({})'.format( self.searchShortcut.key().toString(QtGui.QKeySequence.NativeText))) self.search.setFixedHeight(30) self.model = QtGui.QStandardItemModel(self) self.proxy = OntologyExplorerFilterProxyModel(self) self.proxy.setDynamicSortFilter(False) self.proxy.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive) self.proxy.setSortCaseSensitivity(QtCore.Qt.CaseSensitive) self.proxy.setSourceModel(self.model) self.ontoview = OntologyExplorerView(self) self.ontoview.setModel(self.proxy) self.mainLayout = QtWidgets.QVBoxLayout(self) self.mainLayout.setContentsMargins(0, 0, 0, 0) self.mainLayout.addWidget(self.search) self.mainLayout.addWidget(self.ontoview) self.setTabOrder(self.search, self.ontoview) 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; } """) header = self.ontoview.header() header.setStretchLastSection(False) header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents) connect(self.ontoview.activated, self.onItemActivated) connect(self.ontoview.doubleClicked, self.onItemDoubleClicked) connect(self.ontoview.pressed, self.onItemPressed) connect(self.search.textChanged, self.doFilterItem) connect(self.search.returnPressed, self.onReturnPressed) connect(self.searchShortcut.activated, self.doFocusSearch) 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('QGraphicsScene', 'QGraphicsItem') def doAddNode(self, diagram, node): """ Add a node in the tree view. :type diagram: QGraphicsScene :type node: AbstractItem """ if node.type() in self.items: parent = self.parentFor(node) if not parent: parent = QtGui.QStandardItem(self.parentKey(node)) parent.setIcon(self.iconFor(node)) self.model.appendRow(parent) child = QtGui.QStandardItem(self.childKey(diagram, node)) child.setData(node) # CHECK FOR DUPLICATE NODES children = [parent.child(i) for i in range(parent.rowCount())] if not any([child.text() == c.text() for c in children]): parent.appendRow(child) # APPLY FILTERS AND SORT if self.sender() != self.plugin: self.proxy.invalidateFilter() self.proxy.sort(0, QtCore.Qt.AscendingOrder) @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 doFocusSearch(self): """ Focus the search bar. """ # RAISE THE ENTIRE WIDGET TREE IF IT IS NOT VISIBLE if not self.isVisible(): widget = self while widget != self.session: widget.show() widget.raise_() widget = widget.parent() self.search.setFocus() self.search.selectAll() @QtCore.pyqtSlot('QGraphicsScene', 'QGraphicsItem') def doRemoveNode(self, diagram, node): """ Remove a node from the tree view. :type diagram: QGraphicsScene :type node: AbstractItem """ if node.type() in self.items: parent = self.parentFor(node) if parent: child = self.childFor(parent, diagram, node) if child: parent.removeRow(child.index().row()) if not parent.rowCount(): self.model.removeRow(parent.index().row()) @QtCore.pyqtSlot('QModelIndex') def onItemActivated(self, index): """ Executed when an item in the treeview 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 and item.data(): self.sgnItemActivated.emit(item.data()) # KEEP FOCUS ON THE TREE VIEW UNLESS SHIFT IS PRESSED if QtWidgets.QApplication.queryKeyboardModifiers( ) & QtCore.Qt.SHIFT: return self.ontoview.setFocus() elif item: # EXPAND/COLLAPSE PARENT ITEM if self.ontoview.isExpanded(index): self.ontoview.collapse(index) else: self.ontoview.expand(index) @QtCore.pyqtSlot('QModelIndex') def onItemDoubleClicked(self, index): """ Executed when an item in the treeview 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 and item.data(): self.sgnItemDoubleClicked.emit(item.data()) @QtCore.pyqtSlot('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 and item.data(): self.sgnItemClicked.emit(item.data()) @QtCore.pyqtSlot(bool) def onMenuButtonClicked(self, checked=False): """ Executed when a button in the widget menu is clicked. """ # UPDATE THE PALETTE LAYOUT data = self.sender().data() elems = self.proxy.items if isinstance(data, Item) else self.proxy.status if checked: elems.add(data) else: elems.discard(data) self.proxy.invalidateFilter() self.proxy.sort(0, QtCore.Qt.AscendingOrder) @QtCore.pyqtSlot(Item, str) def onMetaUpdated(self, item, name): """ Executed when metadata of the predicate for the given item/name combination is updated :type item: Item :type name: str """ self.proxy.invalidateFilter() self.proxy.sort(0, QtCore.Qt.AscendingOrder) @QtCore.pyqtSlot() def onReturnPressed(self): """ Executed when the Return or Enter key is pressed in the search field. """ self.focusNextChild() ############################################# # INTERFACE ################################# def childFor(self, parent, diagram, node): """ Search the item representing this node among parent children. :type parent: QtGui.QStandardItem :type diagram: Diagram :type node: AbstractNode """ key = self.childKey(diagram, node) for i in range(parent.rowCount()): child = parent.child(i) if child.text() == key: return child return None @staticmethod def childKey(diagram, node): """ Returns the child key (text) used to place the given node in the treeview. :type diagram: Diagram :type node: AbstractNode :rtype: str """ predicate = node.text().replace('\n', '') diagram = rstrip(diagram.name, File.Graphol.extension) return '{0} ({1} - {2})'.format(predicate, diagram, node.id) def iconFor(self, node): """ Returns the icon for the given node. :type node: """ if node.type() is Item.AttributeNode: return self.iconAttribute if node.type() is Item.ConceptNode: return self.iconConcept if node.type() is Item.IndividualNode: if node.identity() is Identity.Individual: return self.iconInstance if node.identity() is Identity.Value: return self.iconValue if node.type() is Item.RoleNode: return self.iconRole def parentFor(self, node): """ Search the parent element of the given node. :type node: AbstractNode :rtype: QtGui.QStandardItem """ for i in self.model.findItems(self.parentKey(node), QtCore.Qt.MatchExactly): n = i.child(0).data() if node.type() is n.type(): return i return None @staticmethod def parentKey(node): """ Returns the parent key (text) used to place the given node in the treeview. :type node: AbstractNode :rtype: str """ return node.text().replace('\n', '') def sizeHint(self): """ Returns the recommended size for this widget. :rtype: QtCore.QSize """ return QtCore.QSize(216, 266)