ソースを参照

added attribute editing of nodes in instance modeling view (add, delete, modify)

Lucas Heer 7 年 前
コミット
6987287ad5
8 ファイル変更228 行追加22 行削除
  1. 16 2
      commons.py
  2. 50 1
      sketchUI/im_mainwindow.py
  3. 53 3
      sketchUI/im_scene.py
  4. 43 0
      sketchUI/mvops.py
  5. 28 6
      sketchUI/ui.py
  6. 34 6
      sketchUI/ui.ui
  7. 3 3
      verifier.py
  8. 1 1
      wrappers/modelverse_SCCD.py

+ 16 - 2
commons.py

@@ -70,9 +70,9 @@ def get_associations_between(model, node1, node2):
     edges_n2.update(mv.read_incoming(model, node2, "Edge"))
     edges_n2.update(mv.read_incoming(model, node2, "Edge"))
     return list(edges_n1.intersection(edges_n2))
     return list(edges_n1.intersection(edges_n2))
 
 
-def get_attributes_of(model, node_type):
+def get_all_attributes_of_type(model, node_type):
     # type: (str, str) -> list(Attribute)
     # type: (str, str) -> list(Attribute)
-    """ returns a list of attributes of a node with type node_type in a model """
+    """ Returns a list of attributes of a node with type node_type in a model """
     ret = []
     ret = []
     node_attribute_links = mv.all_instances(model, "NodeAttribute")
     node_attribute_links = mv.all_instances(model, "NodeAttribute")
     for link in node_attribute_links:
     for link in node_attribute_links:
@@ -86,6 +86,20 @@ def get_attributes_of(model, node_type):
         ret.append(attr)
         ret.append(attr)
     return ret
     return ret
 
 
+def get_attributes_of_node(model, node_id):
+    # type: (str, str) -> list(Attribute)
+    """ Returns a list of attributes of a specific node with id node_id """
+    ret = []
+    outgoings = mv.read_outgoing(model, node_id, "NodeAttribute")
+    if not outgoings:
+        return []
+    for link in outgoings:
+        dest = mv.read_association_destination(model, link)[0]
+        attr_dict = mv.read_attrs(model, dest)
+        attr = Attribute(attr_dict["key"], attr_dict["value"])
+        ret.append(attr)
+    return ret
+
 def new_instance_model():
 def new_instance_model():
     """
     """
     Adds a new, empty instance model to the Modelverse.
     Adds a new, empty instance model to the Modelverse.

+ 50 - 1
sketchUI/im_mainwindow.py

@@ -1,4 +1,4 @@
-from PyQt5.QtWidgets import QMainWindow, QGraphicsItem, QAction, QActionGroup, QGraphicsView
+from PyQt5.QtWidgets import QMainWindow, QGraphicsItem, QAction, QActionGroup, QGraphicsView, QTableWidgetItem
 from PyQt5.QtGui import QIcon
 from PyQt5.QtGui import QIcon
 from PyQt5.QtCore import QStateMachine, QState
 from PyQt5.QtCore import QStateMachine, QState
 from PyQt5.Qt import QApplication, Qt
 from PyQt5.Qt import QApplication, Qt
@@ -35,6 +35,12 @@ class IMMainWindow(QMainWindow, Ui_MainWindow):
         # setup log viewer
         # setup log viewer
         self.plainTextEdit.setReadOnly(True)
         self.plainTextEdit.setReadOnly(True)
 
 
+        # setup table view for attributes
+        self.tableWidget.setColumnCount(2)
+        self.tableWidget.setHorizontalHeaderLabels(["Key", "Value"])
+        self.tableWidget.horizontalHeader().setStretchLastSection(True)
+        self.tableWidget.itemChanged.connect(self._on_attribute_edited)
+
         #lastly, start the state machine
         #lastly, start the state machine
         self._statemachine.start()
         self._statemachine.start()
 
 
@@ -127,6 +133,10 @@ class IMMainWindow(QMainWindow, Ui_MainWindow):
             self.graphicsView.setDragMode(QGraphicsView.NoDrag)
             self.graphicsView.setDragMode(QGraphicsView.NoDrag)
 
 
     def _add_node_to_scene(self, node_id, node_type, x=0, y=0):
     def _add_node_to_scene(self, node_id, node_type, x=0, y=0):
+        """
+        Render a node with id and type to the canvas by getting its concrete syntax
+        from the modelverse.
+        """
         consyn = mvops.get_consyn_of(node_type)
         consyn = mvops.get_consyn_of(node_type)
         item = GraphicsNodeItem(node_id, node_type, consyn)
         item = GraphicsNodeItem(node_id, node_type, consyn)
         item.setPos(x, y)
         item.setPos(x, y)
@@ -164,3 +174,42 @@ class IMMainWindow(QMainWindow, Ui_MainWindow):
         ret = verify(self._cur_model)
         ret = verify(self._cur_model)
         self.plainTextEdit.appendPlainText("Result: {}".format(str(ret)))
         self.plainTextEdit.appendPlainText("Result: {}".format(str(ret)))
         QApplication.restoreOverrideCursor()
         QApplication.restoreOverrideCursor()
+
+    def _on_attribute_edited(self, item):
+        # type: (QTableWidgetItem) -> None
+        """ An attribute was edited, change it in the model but do not check (too expensive so
+        checking is done by verify method on demand).
+        If the new entered value is empty, delete the attribute.
+        """
+        row = self.tableWidget.row(item)
+        attr_key = self.tableWidget.item(row, 0).text()
+        attr_val = self.tableWidget.item(row, 1).text()
+        node = self._scene.selectedItems()[0]
+
+        if not attr_val:
+            self.plainTextEdit.appendPlainText("Deleting attribute {}".format(attr_key))
+            mvops.delete_attribute_from_node(self._cur_model, node.node_id, attr_key)
+            self.tableWidget.removeRow(row)
+        else:
+            self.plainTextEdit.appendPlainText("Updating value of attribute {} to {}".format(attr_key, attr_val))
+            mvops.update_attribute_val(self._cur_model, node.node_id, attr_key, attr_val)
+
+    def add_new_attribute(self, key, val="unknown"):
+        """
+        Adds a new attribute to the view with key "key" and optional val. Also adds this attribute to the modelverse
+        model.
+        """
+        selected_node = self._scene.selectedItems()[0]
+        self.plainTextEdit.appendPlainText("Adding new attribute with key {} to node {}".format(key, selected_node.get_type()))
+        self.tableWidget.blockSignals(True)
+        table_item_key = QTableWidgetItem(key)
+        table_item_key.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
+        table_item_val = QTableWidgetItem(val)
+        cur_row_cnt = self.tableWidget.rowCount()
+        self.tableWidget.insertRow(cur_row_cnt)
+        self.tableWidget.setItem(cur_row_cnt, 0, table_item_key)
+        self.tableWidget.setItem(cur_row_cnt, 1, table_item_val)
+        self.tableWidget.blockSignals(False)
+
+        # add to modelverse
+        mvops.add_attribute(self._cur_model, selected_node.node_id, key, val)

+ 53 - 3
sketchUI/im_scene.py

@@ -1,10 +1,10 @@
 from enum import Enum
 from enum import Enum
-from PyQt5.QtWidgets import QGraphicsScene, QGraphicsItem
-from PyQt5.Qt import Qt, QTransform
+from PyQt5.QtWidgets import QGraphicsScene, QGraphicsItem, QTableWidgetItem, QInputDialog
+from PyQt5.Qt import Qt, QTransform, QApplication
 from sketchUI.graphics_edge_item import GraphicsEdgeItem
 from sketchUI.graphics_edge_item import GraphicsEdgeItem
 from sketchUI.graphics_node_item import GraphicsNodeItem
 from sketchUI.graphics_node_item import GraphicsNodeItem
 from sketchUI import mvops
 from sketchUI import mvops
-
+import commons
 
 
 class Mode(Enum):
 class Mode(Enum):
     SELECT = 0
     SELECT = 0
@@ -27,8 +27,29 @@ class CustomScene(QGraphicsScene):
             item = self.itemAt(event.scenePos(), QTransform())
             item = self.itemAt(event.scenePos(), QTransform())
             if not item:
             if not item:
                 return
                 return
+            # store the potential item to be connected from
             self.connect_from_item = item
             self.connect_from_item = item
 
 
+        if event.button() == Qt.LeftButton and self._mode == Mode.SELECT:
+            # load attributes for selected node
+            self._parent.tableWidget.setRowCount(0)
+            item = self.itemAt(event.scenePos(), QTransform())
+            if not item:
+                return
+            if isinstance(item, GraphicsNodeItem):
+                self._parent.tableWidget.blockSignals(True)
+                self._parent.plainTextEdit.appendPlainText("Selected item of type {}".format(item.get_type()))
+                attrs = commons.get_attributes_of_node(self._cur_model, item.node_id)
+                for attr in attrs:
+                    table_item_key = QTableWidgetItem(attr.key)
+                    table_item_key.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
+                    table_item_val = QTableWidgetItem(attr.val)
+                    cur_row_cnt = self._parent.tableWidget.rowCount()
+                    self._parent.tableWidget.insertRow(cur_row_cnt)
+                    self._parent.tableWidget.setItem(cur_row_cnt, 0, table_item_key)
+                    self._parent.tableWidget.setItem(cur_row_cnt, 1, table_item_val)
+                self._parent.tableWidget.blockSignals(False)
+
         QGraphicsScene.mousePressEvent(self, event)
         QGraphicsScene.mousePressEvent(self, event)
 
 
     def mouseReleaseEvent(self, event):
     def mouseReleaseEvent(self, event):
@@ -61,6 +82,12 @@ class CustomScene(QGraphicsScene):
         # del deletes elements
         # del deletes elements
         if event.key() == Qt.Key_Delete:
         if event.key() == Qt.Key_Delete:
             self._handle_keypress_delete(self.selectedItems())
             self._handle_keypress_delete(self.selectedItems())
+        elif event.key() == Qt.Key_A:
+            self._handle_keypress_attribute(self.selectedItems())
+        else:
+            pass
+
+        QGraphicsScene.keyPressEvent(self, event)
 
 
     def draw_edge(self, from_item, to_item, is_new):
     def draw_edge(self, from_item, to_item, is_new):
         # type: (GraphicsNodeItem, GraphicsNodeItem, bool) -> None
         # type: (GraphicsNodeItem, GraphicsNodeItem, bool) -> None
@@ -101,3 +128,26 @@ class CustomScene(QGraphicsScene):
                         self.removeItem(edge)
                         self.removeItem(edge)
 
 
                 self.removeItem(item)
                 self.removeItem(item)
+
+    def _handle_keypress_attribute(self, selected):
+        if not len(selected) == 1:
+            return
+        item = selected[0]
+        if not isinstance(item, GraphicsNodeItem):
+            return
+
+        # quickly check if attributing of such a node is allowed
+        QApplication.setOverrideCursor(Qt.WaitCursor)
+        self._parent.plainTextEdit.appendPlainText("Checking if attributing nodes of type {} is allowed ...".format(item.get_type()))
+        if not mvops.is_attributing_allowed(item.get_type()):
+            self._parent.plainTextEdit.appendPlainText("Error: Not allowed".format(item.get_type()))
+            QApplication.restoreOverrideCursor()
+            return
+        QApplication.restoreOverrideCursor()
+        self._parent.plainTextEdit.appendPlainText("Yes")
+
+        # ask user for key value
+        key, ok = QInputDialog.getText(self._parent, "New attribute", "Key value")
+        if not ok or not key:
+            return
+        self._parent.add_new_attribute(key)

+ 43 - 0
sketchUI/mvops.py

@@ -80,3 +80,46 @@ def new_concrete_syntax(type_id, icon_type):
         mv.attr_assign(csm, icon, "is_primitive", False)
         mv.attr_assign(csm, icon, "is_primitive", False)
 
 
     return csm
     return csm
+
+def add_attribute(model, node_id, key, val):
+    """
+    Adds an attribute to node_id in model with key and value
+    """
+    attr_id = mv.instantiate(model, "Attribute")
+    mv.attr_assign(model, attr_id, "key", key)
+    mv.attr_assign(model, attr_id, "value", val)
+    mv.instantiate(model, "NodeAttribute", (node_id, attr_id))
+
+def is_attributing_allowed(node_type):
+    """
+    Check if attributing is allowed for a node of type node_type.
+    Goes through all example models and checks if they have attributes.
+    """
+    for exm in commons.all_example_models():
+        if commons.get_all_attributes_of_type(exm, node_type):
+            return True
+    return False
+
+def update_attribute_val(model, node_id, key, new_val):
+    """
+    Update the attribute identified by its key of node node_id in model to new_val
+    """
+    outgoings = mv.read_outgoing(model, node_id, "NodeAttribute")
+    for link in outgoings:
+        attr = mv.read_association_destination(model, link)[0]
+        attr_key = mv.read_attrs(model, attr)["key"]
+        if attr_key == key:
+            mv.attr_assign(model, attr, "value", new_val)
+            break
+
+def delete_attribute_from_node(model, node_id, key):
+    """
+    Deletes the attribute identified by its key of node node_id in model
+    """
+    outgoings = mv.read_outgoing(model, node_id, "NodeAttribute")
+    for link in outgoings:
+        attr = mv.read_association_destination(model, link)[0]
+        attr_key = mv.read_attrs(model, attr)["key"]
+        if attr_key == key:
+            mv.delete_element(model, attr)
+            break

+ 28 - 6
sketchUI/ui.py

@@ -11,13 +11,13 @@ from PyQt5 import QtCore, QtGui, QtWidgets
 class Ui_MainWindow(object):
 class Ui_MainWindow(object):
     def setupUi(self, MainWindow):
     def setupUi(self, MainWindow):
         MainWindow.setObjectName("MainWindow")
         MainWindow.setObjectName("MainWindow")
-        MainWindow.resize(991, 678)
+        MainWindow.resize(1037, 726)
         self.centralwidget = QtWidgets.QWidget(MainWindow)
         self.centralwidget = QtWidgets.QWidget(MainWindow)
         self.centralwidget.setObjectName("centralwidget")
         self.centralwidget.setObjectName("centralwidget")
         self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
         self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
         self.verticalLayout.setObjectName("verticalLayout")
         self.verticalLayout.setObjectName("verticalLayout")
         self.frame = QtWidgets.QFrame(self.centralwidget)
         self.frame = QtWidgets.QFrame(self.centralwidget)
-        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding)
+        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
         sizePolicy.setHorizontalStretch(0)
         sizePolicy.setHorizontalStretch(0)
         sizePolicy.setVerticalStretch(0)
         sizePolicy.setVerticalStretch(0)
         sizePolicy.setHeightForWidth(self.frame.sizePolicy().hasHeightForWidth())
         sizePolicy.setHeightForWidth(self.frame.sizePolicy().hasHeightForWidth())
@@ -39,17 +39,39 @@ class Ui_MainWindow(object):
         self.listWidget.setObjectName("listWidget")
         self.listWidget.setObjectName("listWidget")
         self.horizontalLayout.addWidget(self.listWidget)
         self.horizontalLayout.addWidget(self.listWidget)
         self.verticalLayout.addWidget(self.frame)
         self.verticalLayout.addWidget(self.frame)
-        self.plainTextEdit = QtWidgets.QPlainTextEdit(self.centralwidget)
-        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
+        self.frame_2 = QtWidgets.QFrame(self.centralwidget)
+        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Maximum)
+        sizePolicy.setHorizontalStretch(0)
+        sizePolicy.setVerticalStretch(0)
+        sizePolicy.setHeightForWidth(self.frame_2.sizePolicy().hasHeightForWidth())
+        self.frame_2.setSizePolicy(sizePolicy)
+        self.frame_2.setFrameShape(QtWidgets.QFrame.StyledPanel)
+        self.frame_2.setFrameShadow(QtWidgets.QFrame.Raised)
+        self.frame_2.setObjectName("frame_2")
+        self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.frame_2)
+        self.horizontalLayout_2.setObjectName("horizontalLayout_2")
+        self.plainTextEdit = QtWidgets.QPlainTextEdit(self.frame_2)
+        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
         sizePolicy.setHorizontalStretch(0)
         sizePolicy.setHorizontalStretch(0)
         sizePolicy.setVerticalStretch(0)
         sizePolicy.setVerticalStretch(0)
         sizePolicy.setHeightForWidth(self.plainTextEdit.sizePolicy().hasHeightForWidth())
         sizePolicy.setHeightForWidth(self.plainTextEdit.sizePolicy().hasHeightForWidth())
         self.plainTextEdit.setSizePolicy(sizePolicy)
         self.plainTextEdit.setSizePolicy(sizePolicy)
         self.plainTextEdit.setObjectName("plainTextEdit")
         self.plainTextEdit.setObjectName("plainTextEdit")
-        self.verticalLayout.addWidget(self.plainTextEdit)
+        self.horizontalLayout_2.addWidget(self.plainTextEdit)
+        self.tableWidget = QtWidgets.QTableWidget(self.frame_2)
+        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
+        sizePolicy.setHorizontalStretch(0)
+        sizePolicy.setVerticalStretch(0)
+        sizePolicy.setHeightForWidth(self.tableWidget.sizePolicy().hasHeightForWidth())
+        self.tableWidget.setSizePolicy(sizePolicy)
+        self.tableWidget.setObjectName("tableWidget")
+        self.tableWidget.setColumnCount(0)
+        self.tableWidget.setRowCount(0)
+        self.horizontalLayout_2.addWidget(self.tableWidget)
+        self.verticalLayout.addWidget(self.frame_2)
         MainWindow.setCentralWidget(self.centralwidget)
         MainWindow.setCentralWidget(self.centralwidget)
         self.menubar = QtWidgets.QMenuBar(MainWindow)
         self.menubar = QtWidgets.QMenuBar(MainWindow)
-        self.menubar.setGeometry(QtCore.QRect(0, 0, 991, 26))
+        self.menubar.setGeometry(QtCore.QRect(0, 0, 1037, 26))
         self.menubar.setObjectName("menubar")
         self.menubar.setObjectName("menubar")
         self.menuFile = QtWidgets.QMenu(self.menubar)
         self.menuFile = QtWidgets.QMenu(self.menubar)
         self.menuFile.setObjectName("menuFile")
         self.menuFile.setObjectName("menuFile")

+ 34 - 6
sketchUI/ui.ui

@@ -6,8 +6,8 @@
    <rect>
    <rect>
     <x>0</x>
     <x>0</x>
     <y>0</y>
     <y>0</y>
-    <width>991</width>
-    <height>678</height>
+    <width>1037</width>
+    <height>726</height>
    </rect>
    </rect>
   </property>
   </property>
   <property name="windowTitle">
   <property name="windowTitle">
@@ -18,7 +18,7 @@
     <item>
     <item>
      <widget class="QFrame" name="frame">
      <widget class="QFrame" name="frame">
       <property name="sizePolicy">
       <property name="sizePolicy">
-       <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+       <sizepolicy hsizetype="Expanding" vsizetype="Minimum">
         <horstretch>0</horstretch>
         <horstretch>0</horstretch>
         <verstretch>0</verstretch>
         <verstretch>0</verstretch>
        </sizepolicy>
        </sizepolicy>
@@ -47,13 +47,41 @@
      </widget>
      </widget>
     </item>
     </item>
     <item>
     <item>
-     <widget class="QPlainTextEdit" name="plainTextEdit">
+     <widget class="QFrame" name="frame_2">
       <property name="sizePolicy">
       <property name="sizePolicy">
-       <sizepolicy hsizetype="Expanding" vsizetype="Minimum">
+       <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
         <horstretch>0</horstretch>
         <horstretch>0</horstretch>
         <verstretch>0</verstretch>
         <verstretch>0</verstretch>
        </sizepolicy>
        </sizepolicy>
       </property>
       </property>
+      <property name="frameShape">
+       <enum>QFrame::StyledPanel</enum>
+      </property>
+      <property name="frameShadow">
+       <enum>QFrame::Raised</enum>
+      </property>
+      <layout class="QHBoxLayout" name="horizontalLayout_2">
+       <item>
+        <widget class="QPlainTextEdit" name="plainTextEdit">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QTableWidget" name="tableWidget">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Minimum" vsizetype="Expanding">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+        </widget>
+       </item>
+      </layout>
      </widget>
      </widget>
     </item>
     </item>
    </layout>
    </layout>
@@ -63,7 +91,7 @@
     <rect>
     <rect>
      <x>0</x>
      <x>0</x>
      <y>0</y>
      <y>0</y>
-     <width>991</width>
+     <width>1037</width>
      <height>26</height>
      <height>26</height>
     </rect>
     </rect>
    </property>
    </property>

+ 3 - 3
verifier.py

@@ -1,5 +1,5 @@
 import wrappers.modelverse as mv
 import wrappers.modelverse as mv
-from commons import get_attributes_of
+from commons import get_all_attributes_of_type
 
 
 
 
 class Edge(object):
 class Edge(object):
@@ -83,7 +83,7 @@ def verify(instance_model_path):
     all_nodes = mv.all_instances(instance_model_path, "Node")
     all_nodes = mv.all_instances(instance_model_path, "Node")
     for node in all_nodes:
     for node in all_nodes:
         node_typ = mv.read_attrs(instance_model_path, node)["type"]
         node_typ = mv.read_attrs(instance_model_path, node)["type"]
-        attrs = get_attributes_of(instance_model_path, node_typ)
+        attrs = get_all_attributes_of_type(instance_model_path, node_typ)
         if len(attrs) == 0:
         if len(attrs) == 0:
             continue
             continue
         #print("Attributes of node {} of type {} are: {}".format(node, node_typ, attrs))
         #print("Attributes of node {} of type {} are: {}".format(node, node_typ, attrs))
@@ -93,7 +93,7 @@ def verify(instance_model_path):
             for exm in example_models_full:
             for exm in example_models_full:
                 if found:
                 if found:
                     break
                     break
-                exm_attrs = get_attributes_of(exm, node_typ)
+                exm_attrs = get_all_attributes_of_type(exm, node_typ)
                 for exm_attr in exm_attrs:
                 for exm_attr in exm_attrs:
                     if exm_attr.key == im_attribute.key:
                     if exm_attr.key == im_attribute.key:
                         #print("Found attribute {} in model {}".format(im_attribute, exm))
                         #print("Found attribute {} in model {}".format(im_attribute, exm))

+ 1 - 1
wrappers/modelverse_SCCD.py

@@ -1,7 +1,7 @@
 """
 """
 Generated by Statechart compiler by Glenn De Jonghe, Joeri Exelmans, Simon Van Mierlo, and Yentl Van Tendeloo (for the inspiration)
 Generated by Statechart compiler by Glenn De Jonghe, Joeri Exelmans, Simon Van Mierlo, and Yentl Van Tendeloo (for the inspiration)
 
 
-Date:   Mon Apr 23 19:39:02 2018
+Date:   Wed Apr 25 11:43:23 2018
 
 
 Model author: Yentl Van Tendeloo
 Model author: Yentl Van Tendeloo
 Model name:   MvK Server
 Model name:   MvK Server