Bläddra i källkod

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

Lucas Heer 7 år sedan
förälder
incheckning
6987287ad5
8 ändrade filer med 228 tillägg och 22 borttagningar
  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"))
     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)
-    """ 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 = []
     node_attribute_links = mv.all_instances(model, "NodeAttribute")
     for link in node_attribute_links:
@@ -86,6 +86,20 @@ def get_attributes_of(model, node_type):
         ret.append(attr)
     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():
     """
     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.QtCore import QStateMachine, QState
 from PyQt5.Qt import QApplication, Qt
@@ -35,6 +35,12 @@ class IMMainWindow(QMainWindow, Ui_MainWindow):
         # setup log viewer
         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
         self._statemachine.start()
 
@@ -127,6 +133,10 @@ class IMMainWindow(QMainWindow, Ui_MainWindow):
             self.graphicsView.setDragMode(QGraphicsView.NoDrag)
 
     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)
         item = GraphicsNodeItem(node_id, node_type, consyn)
         item.setPos(x, y)
@@ -164,3 +174,42 @@ class IMMainWindow(QMainWindow, Ui_MainWindow):
         ret = verify(self._cur_model)
         self.plainTextEdit.appendPlainText("Result: {}".format(str(ret)))
         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 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_node_item import GraphicsNodeItem
 from sketchUI import mvops
-
+import commons
 
 class Mode(Enum):
     SELECT = 0
@@ -27,8 +27,29 @@ class CustomScene(QGraphicsScene):
             item = self.itemAt(event.scenePos(), QTransform())
             if not item:
                 return
+            # store the potential item to be connected from
             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)
 
     def mouseReleaseEvent(self, event):
@@ -61,6 +82,12 @@ class CustomScene(QGraphicsScene):
         # del deletes elements
         if event.key() == Qt.Key_Delete:
             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):
         # type: (GraphicsNodeItem, GraphicsNodeItem, bool) -> None
@@ -101,3 +128,26 @@ class CustomScene(QGraphicsScene):
                         self.removeItem(edge)
 
                 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)
 
     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):
     def setupUi(self, MainWindow):
         MainWindow.setObjectName("MainWindow")
-        MainWindow.resize(991, 678)
+        MainWindow.resize(1037, 726)
         self.centralwidget = QtWidgets.QWidget(MainWindow)
         self.centralwidget.setObjectName("centralwidget")
         self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
         self.verticalLayout.setObjectName("verticalLayout")
         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.setVerticalStretch(0)
         sizePolicy.setHeightForWidth(self.frame.sizePolicy().hasHeightForWidth())
@@ -39,17 +39,39 @@ class Ui_MainWindow(object):
         self.listWidget.setObjectName("listWidget")
         self.horizontalLayout.addWidget(self.listWidget)
         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.setVerticalStretch(0)
         sizePolicy.setHeightForWidth(self.plainTextEdit.sizePolicy().hasHeightForWidth())
         self.plainTextEdit.setSizePolicy(sizePolicy)
         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)
         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.menuFile = QtWidgets.QMenu(self.menubar)
         self.menuFile.setObjectName("menuFile")

+ 34 - 6
sketchUI/ui.ui

@@ -6,8 +6,8 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>991</width>
-    <height>678</height>
+    <width>1037</width>
+    <height>726</height>
    </rect>
   </property>
   <property name="windowTitle">
@@ -18,7 +18,7 @@
     <item>
      <widget class="QFrame" name="frame">
       <property name="sizePolicy">
-       <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+       <sizepolicy hsizetype="Expanding" vsizetype="Minimum">
         <horstretch>0</horstretch>
         <verstretch>0</verstretch>
        </sizepolicy>
@@ -47,13 +47,41 @@
      </widget>
     </item>
     <item>
-     <widget class="QPlainTextEdit" name="plainTextEdit">
+     <widget class="QFrame" name="frame_2">
       <property name="sizePolicy">
-       <sizepolicy hsizetype="Expanding" vsizetype="Minimum">
+       <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
         <horstretch>0</horstretch>
         <verstretch>0</verstretch>
        </sizepolicy>
       </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>
     </item>
    </layout>
@@ -63,7 +91,7 @@
     <rect>
      <x>0</x>
      <y>0</y>
-     <width>991</width>
+     <width>1037</width>
      <height>26</height>
     </rect>
    </property>

+ 3 - 3
verifier.py

@@ -1,5 +1,5 @@
 import wrappers.modelverse as mv
-from commons import get_attributes_of
+from commons import get_all_attributes_of_type
 
 
 class Edge(object):
@@ -83,7 +83,7 @@ def verify(instance_model_path):
     all_nodes = mv.all_instances(instance_model_path, "Node")
     for node in all_nodes:
         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:
             continue
         #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:
                 if found:
                     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:
                     if exm_attr.key == im_attribute.key:
                         #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)
 
-Date:   Mon Apr 23 19:39:02 2018
+Date:   Wed Apr 25 11:43:23 2018
 
 Model author: Yentl Van Tendeloo
 Model name:   MvK Server