im_mainwindow.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. from PyQt5.QtWidgets import QMainWindow, QGraphicsItem, QAction, QActionGroup, QGraphicsView, QTableWidgetItem
  2. from PyQt5.QtGui import QIcon
  3. from PyQt5.QtCore import QStateMachine, QState
  4. from PyQt5.Qt import QApplication, Qt
  5. from sketchUI.ui import Ui_MainWindow
  6. from sketchUI.im_scene import CustomScene, Mode
  7. from sketchUI import mvops
  8. from sketchUI.graphics_node_item import GraphicsNodeItem
  9. from sketchUI.graphics_edge_item import GraphicsEdgeItem
  10. from wrappers.modelverse import element_list_nice
  11. from verifier import Verifier
  12. import commons
  13. class IMMainWindow(QMainWindow, Ui_MainWindow):
  14. def __init__(self, model):
  15. QMainWindow.__init__(self)
  16. self.setupUi(self)
  17. self.setWindowTitle(model)
  18. self._cur_model = model
  19. self._scene = CustomScene(model, self)
  20. self._scene.set_mode(Mode.SELECT)
  21. self._scene.setSceneRect(0, 0, 200, 200)
  22. self.graphicsView.setScene(self._scene)
  23. self.listWidget.addItems(commons.get_available_types())
  24. self.listWidget.itemDoubleClicked.connect(self._on_list_item_clicked)
  25. self.setup_toolbar()
  26. self.setup_state_machine()
  27. self.menuActionVerify.triggered.connect(self._on_verify_clicked)
  28. self.menuActionDeleteModel.triggered.connect(self._on_delete_model_clicked)
  29. # load the model
  30. self._load_model()
  31. # setup log viewer
  32. self.plainTextEdit.setReadOnly(True)
  33. # setup table view for attributes
  34. self.tableWidget.setColumnCount(2)
  35. self.tableWidget.setHorizontalHeaderLabels(["Key", "Value"])
  36. self.tableWidget.horizontalHeader().setStretchLastSection(True)
  37. self.tableWidget.itemChanged.connect(self._on_attribute_edited)
  38. #lastly, start the state machine
  39. self._statemachine.start()
  40. def setup_toolbar(self):
  41. self.select_action = QAction("Select", self)
  42. self.select_action.setIcon(QIcon("sketchUI/icons/select.png"))
  43. self.select_action.setCheckable(True)
  44. self.select_action.setChecked(True)
  45. self.connect_action = QAction("Connect", self)
  46. self.connect_action.setIcon(QIcon("sketchUI/icons/connect.png"))
  47. self.connect_action.setCheckable(True)
  48. action_group = QActionGroup(self)
  49. action_group.setExclusive(True)
  50. action_group.addAction(self.select_action)
  51. action_group.addAction(self.connect_action)
  52. for item in action_group.actions():
  53. self.toolBar.addAction(item)
  54. def setup_state_machine(self):
  55. self._statemachine = QStateMachine()
  56. state_select = QState()
  57. state_connect = QState()
  58. state_select.addTransition(self.connect_action.triggered, state_connect)
  59. state_connect.addTransition(self.select_action.triggered, state_select)
  60. state_connect.entered.connect(self._state_connect_entered)
  61. state_select.entered.connect(self._state_select_entered)
  62. self._statemachine.addState(state_select)
  63. self._statemachine.addState(state_connect)
  64. self._statemachine.setInitialState(state_select)
  65. def _load_model(self):
  66. """
  67. Load the model from the mv and render to screen using the concrete syntaxes stored in the mv
  68. """
  69. model = element_list_nice(self._cur_model)
  70. if not model:
  71. # empty model
  72. return
  73. x_pos = -150
  74. y_pos = 70
  75. for item in model:
  76. typ = item["type"]
  77. if typ == "Node":
  78. # first, draw all nodes
  79. node_type = item["typeID"]
  80. self._add_node_to_scene(item["id"], node_type, x=x_pos, y=y_pos)
  81. x_pos += 80
  82. y_pos *= -1
  83. for item in model:
  84. # now the edges
  85. typ = item["type"]
  86. if typ == "Edge":
  87. target = item["__target"]
  88. src = item["__source"]
  89. self._add_edge_to_scene(src, target, item["id"])
  90. def _state_connect_entered(self):
  91. self._scene.set_mode(Mode.CONNECT)
  92. self._make_items_movable(False)
  93. self._enable_box_select(False)
  94. self._enable_list_widget(False)
  95. def _state_select_entered(self):
  96. self._scene.set_mode(Mode.SELECT)
  97. self._make_items_movable(True)
  98. self._enable_box_select(True)
  99. self._enable_list_widget(True)
  100. def _enable_list_widget(self, enabled):
  101. self.listWidget.setEnabled(enabled)
  102. def _make_items_movable(self, movable):
  103. for item in self._scene.items():
  104. if isinstance(item, GraphicsEdgeItem):
  105. # edges are selectable (for delete) but never movable
  106. item.setFlag(QGraphicsItem.ItemIsMovable, False)
  107. item.setFlag(QGraphicsItem.ItemIsSelectable, True)
  108. else:
  109. item.setFlag(QGraphicsItem.ItemIsMovable, movable)
  110. item.setFlag(QGraphicsItem.ItemIsSelectable, movable)
  111. def _enable_box_select(self, enable):
  112. if enable:
  113. self.graphicsView.setDragMode(QGraphicsView.RubberBandDrag)
  114. else:
  115. self.graphicsView.setDragMode(QGraphicsView.NoDrag)
  116. def _add_node_to_scene(self, node_id, node_type, x=0, y=0):
  117. """
  118. Render a node with id and type to the canvas by getting its concrete syntax
  119. from the modelverse.
  120. """
  121. consyn = mvops.get_consyn_of(node_type)
  122. item = GraphicsNodeItem(node_id, node_type, consyn)
  123. item.setPos(x, y)
  124. item.setFlag(QGraphicsItem.ItemIsMovable, True)
  125. item.setFlag(QGraphicsItem.ItemIsSelectable, True)
  126. self._scene.addItem(item)
  127. def _add_edge_to_scene(self, from_id, to_id, edge_id):
  128. from_item = None
  129. to_item = None
  130. for item in self._scene.items():
  131. try:
  132. node_id = item.node_id
  133. except AttributeError:
  134. # no node item, continue
  135. continue
  136. if node_id == from_id:
  137. from_item = item
  138. continue
  139. if node_id == to_id:
  140. to_item = item
  141. self._scene.draw_edge(from_item, to_item, is_new=False, edge_id=edge_id)
  142. def _on_list_item_clicked(self, event):
  143. # add new node to model in mv
  144. node_id = mvops.add_node(self._cur_model, event.text())
  145. # render to scene
  146. self._add_node_to_scene(node_id, event.text())
  147. self.plainTextEdit.appendPlainText("Added node of type {} to model".format(event.text()))
  148. def _on_verify_clicked(self, event):
  149. self.plainTextEdit.appendPlainText("Verifying instance model against example models ...")
  150. self.plainTextEdit.repaint()
  151. QApplication.setOverrideCursor(Qt.WaitCursor)
  152. verify = Verifier(self._cur_model)
  153. verify.init()
  154. self.plainTextEdit.appendPlainText("Verify: Checking node typing ...")
  155. self.plainTextEdit.repaint()
  156. ret = verify.verify_node_typing()
  157. if not ret["OK"]:
  158. self.plainTextEdit.appendPlainText("Error: {}".format(str(ret["error"])))
  159. QApplication.restoreOverrideCursor()
  160. return
  161. else:
  162. self.plainTextEdit.appendPlainText("OK")
  163. self.plainTextEdit.repaint()
  164. self.plainTextEdit.appendPlainText("Verify: Checking node multiplicity ...")
  165. self.plainTextEdit.repaint()
  166. ret = verify.verify_node_multiplicity()
  167. if not ret["OK"]:
  168. self.plainTextEdit.appendPlainText("Error: {}".format(str(ret["error"])))
  169. QApplication.restoreOverrideCursor()
  170. return
  171. else:
  172. self.plainTextEdit.appendPlainText("OK")
  173. self.plainTextEdit.repaint()
  174. self.plainTextEdit.appendPlainText("Verify: Checking attributes ...")
  175. self.plainTextEdit.repaint()
  176. ret = verify.verify_attributes()
  177. if not ret["OK"]:
  178. self.plainTextEdit.appendPlainText("Error: {}".format(str(ret["error"])))
  179. self._scene.highlight_node(ret["affected"][0], Qt.red)
  180. QApplication.restoreOverrideCursor()
  181. return
  182. else:
  183. self.plainTextEdit.appendPlainText("OK")
  184. self.plainTextEdit.repaint()
  185. self.plainTextEdit.appendPlainText("Verify: Checking edges ...")
  186. self.plainTextEdit.repaint()
  187. ret = verify.verify_associations()
  188. if not ret["OK"]:
  189. self.plainTextEdit.appendPlainText("Error: {}".format(str(ret["error"])))
  190. QApplication.restoreOverrideCursor()
  191. return
  192. else:
  193. self.plainTextEdit.appendPlainText("OK")
  194. self.plainTextEdit.repaint()
  195. self.plainTextEdit.appendPlainText("Verify OK")
  196. QApplication.restoreOverrideCursor()
  197. def _on_attribute_edited(self, item):
  198. # type: (QTableWidgetItem) -> None
  199. """ An attribute was edited, change it in the model if supported.
  200. If the new entered value is empty, delete the attribute.
  201. """
  202. row = self.tableWidget.row(item)
  203. attr_key = self.tableWidget.item(row, 0).text()
  204. attr_val = self.tableWidget.item(row, 1).text()
  205. node = self._scene.selectedItems()[0]
  206. if not attr_val:
  207. if commons.is_attribute_mandatory(node.get_type(), attr_key):
  208. self.plainTextEdit.appendPlainText("Error: attribute {} mandatory".format(attr_key))
  209. return
  210. self.plainTextEdit.appendPlainText("Deleting attribute {}".format(attr_key))
  211. mvops.delete_attribute_from_node(self._cur_model, node.node_id, attr_key)
  212. self.tableWidget.removeRow(row)
  213. else:
  214. self.plainTextEdit.appendPlainText("Updating value of attribute {} to {}".format(attr_key, attr_val))
  215. mvops.update_attribute_val(self._cur_model, node.node_id, attr_key, attr_val)
  216. def add_new_attribute(self, key, val="unknown"):
  217. """
  218. Adds a new attribute to the view with key "key" and optional val. Also adds this attribute to the modelverse
  219. model.
  220. """
  221. selected_node = self._scene.selectedItems()[0]
  222. self.plainTextEdit.appendPlainText("Adding new attribute with key {} to node {}".format(key, selected_node.get_type()))
  223. self.tableWidget.blockSignals(True)
  224. table_item_key = QTableWidgetItem(key)
  225. table_item_key.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
  226. table_item_val = QTableWidgetItem(val)
  227. cur_row_cnt = self.tableWidget.rowCount()
  228. self.tableWidget.insertRow(cur_row_cnt)
  229. self.tableWidget.setItem(cur_row_cnt, 0, table_item_key)
  230. self.tableWidget.setItem(cur_row_cnt, 1, table_item_val)
  231. self.tableWidget.blockSignals(False)
  232. # add to modelverse
  233. mvops.add_attribute(self._cur_model, selected_node.node_id, key, val)
  234. def _on_delete_model_clicked(self, event):
  235. self.plainTextEdit.appendPlainText("Deleting model ...")
  236. self.plainTextEdit.repaint()
  237. if mvops.delete_instance_model(self._cur_model):
  238. self.close()
  239. else:
  240. self.plainTextEdit.appendPlainText("Error: Delete failed")