123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282 |
- 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
- from sketchUI.ui import Ui_MainWindow
- from sketchUI.im_scene import CustomScene, Mode
- from sketchUI import mvops
- from sketchUI.graphics_node_item import GraphicsNodeItem
- from sketchUI.graphics_edge_item import GraphicsEdgeItem
- from wrappers.modelverse import element_list_nice
- from verifier import Verifier
- import commons
- class IMMainWindow(QMainWindow, Ui_MainWindow):
- def __init__(self, model):
- QMainWindow.__init__(self)
- self.setupUi(self)
- self.setWindowTitle(model)
- self._cur_model = model
- self._scene = CustomScene(model, self)
- self._scene.set_mode(Mode.SELECT)
- self._scene.setSceneRect(0, 0, 200, 200)
- self.graphicsView.setScene(self._scene)
- self.listWidget.addItems(commons.get_available_types())
- self.listWidget.itemDoubleClicked.connect(self._on_list_item_clicked)
- self.setup_toolbar()
- self.setup_state_machine()
- self.menuActionVerify.triggered.connect(self._on_verify_clicked)
- self.menuActionDeleteModel.triggered.connect(self._on_delete_model_clicked)
- # load the model
- self._load_model()
- # 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()
- def setup_toolbar(self):
- self.select_action = QAction("Select", self)
- self.select_action.setIcon(QIcon("sketchUI/icons/select.png"))
- self.select_action.setCheckable(True)
- self.select_action.setChecked(True)
- self.connect_action = QAction("Connect", self)
- self.connect_action.setIcon(QIcon("sketchUI/icons/connect.png"))
- self.connect_action.setCheckable(True)
- action_group = QActionGroup(self)
- action_group.setExclusive(True)
- action_group.addAction(self.select_action)
- action_group.addAction(self.connect_action)
- for item in action_group.actions():
- self.toolBar.addAction(item)
- def setup_state_machine(self):
- self._statemachine = QStateMachine()
- state_select = QState()
- state_connect = QState()
- state_select.addTransition(self.connect_action.triggered, state_connect)
- state_connect.addTransition(self.select_action.triggered, state_select)
- state_connect.entered.connect(self._state_connect_entered)
- state_select.entered.connect(self._state_select_entered)
- self._statemachine.addState(state_select)
- self._statemachine.addState(state_connect)
- self._statemachine.setInitialState(state_select)
- def _load_model(self):
- """
- Load the model from the mv and render to screen using the concrete syntaxes stored in the mv
- """
- model = element_list_nice(self._cur_model)
- if not model:
- # empty model
- return
- x_pos = -150
- y_pos = 70
- for item in model:
- typ = item["type"]
- if typ == "Node":
- # first, draw all nodes
- node_type = item["typeID"]
- self._add_node_to_scene(item["id"], node_type, x=x_pos, y=y_pos)
- x_pos += 80
- y_pos *= -1
- for item in model:
- # now the edges
- typ = item["type"]
- if typ == "Edge":
- target = item["__target"]
- src = item["__source"]
- self._add_edge_to_scene(src, target, item["id"])
- def _state_connect_entered(self):
- self._scene.set_mode(Mode.CONNECT)
- self._make_items_movable(False)
- self._enable_box_select(False)
- self._enable_list_widget(False)
- def _state_select_entered(self):
- self._scene.set_mode(Mode.SELECT)
- self._make_items_movable(True)
- self._enable_box_select(True)
- self._enable_list_widget(True)
- def _enable_list_widget(self, enabled):
- self.listWidget.setEnabled(enabled)
- def _make_items_movable(self, movable):
- for item in self._scene.items():
- if isinstance(item, GraphicsEdgeItem):
- # edges are selectable (for delete) but never movable
- item.setFlag(QGraphicsItem.ItemIsMovable, False)
- item.setFlag(QGraphicsItem.ItemIsSelectable, True)
- else:
- item.setFlag(QGraphicsItem.ItemIsMovable, movable)
- item.setFlag(QGraphicsItem.ItemIsSelectable, movable)
- def _enable_box_select(self, enable):
- if enable:
- self.graphicsView.setDragMode(QGraphicsView.RubberBandDrag)
- else:
- 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)
- item.setFlag(QGraphicsItem.ItemIsMovable, True)
- item.setFlag(QGraphicsItem.ItemIsSelectable, True)
- self._scene.addItem(item)
- def _add_edge_to_scene(self, from_id, to_id, edge_id):
- from_item = None
- to_item = None
- for item in self._scene.items():
- try:
- node_id = item.node_id
- except AttributeError:
- # no node item, continue
- continue
- if node_id == from_id:
- from_item = item
- continue
- if node_id == to_id:
- to_item = item
- self._scene.draw_edge(from_item, to_item, is_new=False, edge_id=edge_id)
- def _on_list_item_clicked(self, event):
- # add new node to model in mv
- node_id = mvops.add_node(self._cur_model, event.text())
- # render to scene
- self._add_node_to_scene(node_id, event.text())
- self.plainTextEdit.appendPlainText("Added node of type {} to model".format(event.text()))
- def _on_verify_clicked(self, event):
- self.plainTextEdit.appendPlainText("Verifying instance model against example models ...")
- self.plainTextEdit.repaint()
- QApplication.setOverrideCursor(Qt.WaitCursor)
- verify = Verifier(self._cur_model)
- verify.init()
- self.plainTextEdit.appendPlainText("Verify: Checking node typing ...")
- self.plainTextEdit.repaint()
- ret = verify.verify_node_typing()
- if not ret["OK"]:
- self.plainTextEdit.appendPlainText("Error: {}".format(str(ret["error"])))
- QApplication.restoreOverrideCursor()
- return
- else:
- self.plainTextEdit.appendPlainText("OK")
- self.plainTextEdit.repaint()
- self.plainTextEdit.appendPlainText("Verify: Checking node multiplicity ...")
- self.plainTextEdit.repaint()
- ret = verify.verify_node_multiplicity()
- if not ret["OK"]:
- self.plainTextEdit.appendPlainText("Error: {}".format(str(ret["error"])))
- QApplication.restoreOverrideCursor()
- return
- else:
- self.plainTextEdit.appendPlainText("OK")
- self.plainTextEdit.repaint()
- self.plainTextEdit.appendPlainText("Verify: Checking attributes ...")
- self.plainTextEdit.repaint()
- ret = verify.verify_attributes()
- if not ret["OK"]:
- self.plainTextEdit.appendPlainText("Error: {}".format(str(ret["error"])))
- self._scene.highlight_node(ret["affected"][0], Qt.red)
- QApplication.restoreOverrideCursor()
- return
- else:
- self.plainTextEdit.appendPlainText("OK")
- self.plainTextEdit.repaint()
- self.plainTextEdit.appendPlainText("Verify: Checking edges ...")
- self.plainTextEdit.repaint()
- ret = verify.verify_associations()
- if not ret["OK"]:
- self.plainTextEdit.appendPlainText("Error: {}".format(str(ret["error"])))
- QApplication.restoreOverrideCursor()
- return
- else:
- self.plainTextEdit.appendPlainText("OK")
- self.plainTextEdit.repaint()
- self.plainTextEdit.appendPlainText("Verify OK")
- QApplication.restoreOverrideCursor()
- def _on_attribute_edited(self, item):
- # type: (QTableWidgetItem) -> None
- """ An attribute was edited, change it in the model if supported.
- 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:
- if commons.is_attribute_mandatory(node.get_type(), attr_key):
- self.plainTextEdit.appendPlainText("Error: attribute {} mandatory".format(attr_key))
- return
- 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)
- def _on_delete_model_clicked(self, event):
- self.plainTextEdit.appendPlainText("Deleting model ...")
- self.plainTextEdit.repaint()
- if mvops.delete_instance_model(self._cur_model):
- self.close()
- else:
- self.plainTextEdit.appendPlainText("Error: Delete failed")
|