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 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) # 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 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) 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) 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(): try: # hacky hack because of pythons isinstance fails due to import chaos # edges are not selectable or movable item.__hack__() item.setFlag(QGraphicsItem.ItemIsMovable, False) item.setFlag(QGraphicsItem.ItemIsSelectable, False) continue except AttributeError: pass 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): 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) 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"]))) 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 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)