exm_mainwindow.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. from PyQt5.QtWidgets import QMainWindow, QAction, QActionGroup, QGraphicsItem, QGraphicsView,\
  2. QTableWidgetItem, QInputDialog
  3. from PyQt5.QtGui import QIcon
  4. from PyQt5.QtCore import QStateMachine, QState, Qt
  5. from PyQt5.Qt import QApplication
  6. from sketchUI.ui import Ui_MainWindow
  7. from sketchUI.exm_scene import SketchScene, Mode
  8. from sketchUI import mvops
  9. from sketchUI.graphics_node_item import GraphicsNodeItem
  10. from sketchUI.graphics_edge_item import GraphicsEdgeItem
  11. from wrappers.modelverse import element_list_nice
  12. import commons
  13. from evolution.node_ops import NodeDelete
  14. from evolution.attribute_ops import AttributeDelete
  15. class EXMMainWindow(QMainWindow, Ui_MainWindow):
  16. def __init__(self, model):
  17. QMainWindow.__init__(self)
  18. self.setupUi(self)
  19. self.setWindowTitle(model)
  20. self.menuActionVerify.setDisabled(True)
  21. self._cur_model = model
  22. self._scene = SketchScene(model, self)
  23. self._scene.setSceneRect(0, 0, 200, 200)
  24. self.graphicsView.setScene(self._scene)
  25. self._scene.set_mode(Mode.SELECT)
  26. self.populate_types()
  27. self.listWidget.itemDoubleClicked.connect(self._on_list_item_clicked)
  28. self._setup_toolbar()
  29. self._setup_statemachine()
  30. self.menuActionDeleteModel.triggered.connect(self._on_delete_model_clicked)
  31. # load model
  32. self._load_model()
  33. # setup log viewer
  34. self.plainTextEdit.setReadOnly(True)
  35. # setup table view for attributes
  36. self.tableWidget.setColumnCount(2)
  37. self.tableWidget.setHorizontalHeaderLabels(["Key", "Value"])
  38. self.tableWidget.horizontalHeader().setStretchLastSection(True)
  39. self.tableWidget.itemChanged.connect(self._on_attribute_edited)
  40. # lastly, run state machine
  41. self._statemachine.start()
  42. def populate_types(self):
  43. # sync example model types to list widget
  44. self.listWidget.clear()
  45. self.listWidget.addItems(commons.get_available_types())
  46. def _setup_toolbar(self):
  47. # create action buttons on toolbar
  48. self.select_action = QAction("Select", self)
  49. self.select_action.setIcon(QIcon("sketchUI/icons/select.png"))
  50. self.select_action.setCheckable(True)
  51. self.select_action.setChecked(True)
  52. self.connect_action = QAction("Connect", self)
  53. self.connect_action.setIcon(QIcon("sketchUI/icons/connect.png"))
  54. self.connect_action.setCheckable(True)
  55. self.line_action = QAction("Draw line", self)
  56. self.line_action.setIcon(QIcon("sketchUI/icons/line.png"))
  57. self.line_action.setCheckable(True)
  58. self.rect_action = QAction("Draw Rectangle", self)
  59. self.rect_action.setIcon(QIcon("sketchUI/icons/rect.png"))
  60. self.rect_action.setCheckable(True)
  61. self.circle_action = QAction("Draw Circle", self)
  62. self.circle_action.setIcon(QIcon("sketchUI/icons/circle.png"))
  63. self.circle_action.setCheckable(True)
  64. self.pen_action = QAction("Pen", self)
  65. self.pen_action.setIcon(QIcon("sketchUI/icons/pen.svg"))
  66. self.pen_action.setCheckable(True)
  67. self.pen_action.setEnabled(False)
  68. action_group = QActionGroup(self)
  69. action_group.setExclusive(True)
  70. action_group.addAction(self.select_action)
  71. action_group.addAction(self.connect_action)
  72. action_group.addAction(self.line_action)
  73. action_group.addAction(self.rect_action)
  74. action_group.addAction(self.circle_action)
  75. action_group.addAction(self.pen_action)
  76. for item in action_group.actions():
  77. self.toolBar.addAction(item)
  78. def _setup_statemachine(self):
  79. self._statemachine = QStateMachine()
  80. state_select = QState()
  81. state_connect = QState()
  82. state_draw_line = QState()
  83. state_draw_rect = QState()
  84. state_draw_circle = QState()
  85. state_draw_free = QState()
  86. # from select
  87. state_select.addTransition(self.select_action.triggered, state_select)
  88. state_select.addTransition(self.connect_action.triggered, state_connect)
  89. state_select.addTransition(self.line_action.triggered, state_draw_line)
  90. state_select.addTransition(self.rect_action.triggered, state_draw_rect)
  91. state_select.addTransition(self.circle_action.triggered, state_draw_circle)
  92. state_select.addTransition(self.pen_action.triggered, state_draw_free)
  93. state_select.entered.connect(self._state_select_entered)
  94. # from connect
  95. state_connect.addTransition(self.connect_action.triggered, state_connect)
  96. state_connect.addTransition(self.select_action.triggered, state_select)
  97. state_connect.addTransition(self.line_action.triggered, state_draw_line)
  98. state_connect.addTransition(self.rect_action.triggered, state_draw_rect)
  99. state_connect.addTransition(self.circle_action.triggered, state_draw_circle)
  100. state_connect.addTransition(self.pen_action.triggered, state_draw_free)
  101. state_connect.entered.connect(self._state_connect_entered)
  102. # from rectangle
  103. state_draw_rect.addTransition(self.rect_action.triggered, state_draw_rect)
  104. state_draw_rect.addTransition(self.connect_action.triggered, state_connect)
  105. state_draw_rect.addTransition(self.select_action.triggered, state_select)
  106. state_draw_rect.addTransition(self.line_action.triggered, state_draw_line)
  107. state_draw_rect.addTransition(self.circle_action.triggered, state_draw_circle)
  108. state_draw_rect.addTransition(self.pen_action.triggered, state_draw_free)
  109. state_draw_rect.entered.connect(self._state_draw_rect_entered)
  110. # from line
  111. state_draw_line.addTransition(self.line_action.triggered, state_draw_line)
  112. state_draw_line.addTransition(self.rect_action.triggered, state_draw_rect)
  113. state_draw_line.addTransition(self.connect_action.triggered, state_connect)
  114. state_draw_line.addTransition(self.select_action.triggered, state_select)
  115. state_draw_line.addTransition(self.circle_action.triggered, state_draw_circle)
  116. state_draw_line.addTransition(self.pen_action.triggered, state_draw_free)
  117. state_draw_line.entered.connect(self._state_draw_line_entered)
  118. # from circle
  119. state_draw_circle.addTransition(self.circle_action.triggered, state_draw_circle)
  120. state_draw_circle.addTransition(self.line_action.triggered, state_draw_line)
  121. state_draw_circle.addTransition(self.rect_action.triggered, state_draw_rect)
  122. state_draw_circle.addTransition(self.connect_action.triggered, state_connect)
  123. state_draw_circle.addTransition(self.select_action.triggered, state_select)
  124. state_draw_circle.addTransition(self.pen_action.triggered, state_draw_free)
  125. state_draw_circle.entered.connect(self._state_draw_circle_entered)
  126. # from freehand
  127. state_draw_free.addTransition(self.pen_action.triggered, state_draw_free)
  128. state_draw_free.addTransition(self.circle_action.triggered, state_draw_circle)
  129. state_draw_free.addTransition(self.line_action.triggered, state_draw_line)
  130. state_draw_free.addTransition(self.rect_action.triggered, state_draw_rect)
  131. state_draw_free.addTransition(self.connect_action.triggered, state_connect)
  132. state_draw_free.addTransition(self.select_action.triggered, state_select)
  133. state_draw_free.entered.connect(self._state_draw_free_entered)
  134. self._statemachine.addState(state_select)
  135. self._statemachine.addState(state_connect)
  136. self._statemachine.addState(state_draw_rect)
  137. self._statemachine.addState(state_draw_line)
  138. self._statemachine.addState(state_draw_circle)
  139. self._statemachine.addState(state_draw_free)
  140. self._statemachine.setInitialState(state_select)
  141. def _state_select_entered(self):
  142. self._scene.set_mode(Mode.SELECT)
  143. self._make_items_movable(True)
  144. self._enable_box_select(True)
  145. def _state_connect_entered(self):
  146. self._scene.set_mode(Mode.CONNECT)
  147. self._make_items_movable(False)
  148. self._enable_box_select(False)
  149. def _state_draw_rect_entered(self):
  150. self._scene.set_mode(Mode.RECT)
  151. self._make_items_movable(False)
  152. self._enable_box_select(False)
  153. def _state_draw_line_entered(self):
  154. self._scene.set_mode(Mode.LINE)
  155. self._make_items_movable(False)
  156. self._enable_box_select(False)
  157. def _state_draw_circle_entered(self):
  158. self._scene.set_mode(Mode.CIRCLE)
  159. self._make_items_movable(False)
  160. self._enable_box_select(False)
  161. def _state_draw_free_entered(self):
  162. self._scene.set_mode(Mode.FREE)
  163. self._make_items_movable(False)
  164. self._enable_box_select(False)
  165. def _make_items_movable(self, movable):
  166. for item in self._scene.items():
  167. if isinstance(item, GraphicsEdgeItem):
  168. # edges are never movable, but selectable
  169. item.setFlag(QGraphicsItem.ItemIsMovable, False)
  170. item.setFlag(QGraphicsItem.ItemIsSelectable, True)
  171. else:
  172. item.setFlag(QGraphicsItem.ItemIsMovable, movable)
  173. item.setFlag(QGraphicsItem.ItemIsSelectable, movable)
  174. def _enable_box_select(self, enable):
  175. if enable:
  176. self.graphicsView.setDragMode(QGraphicsView.RubberBandDrag)
  177. else:
  178. self.graphicsView.setDragMode(QGraphicsView.NoDrag)
  179. def _load_model(self):
  180. model = element_list_nice(self._cur_model)
  181. if not model:
  182. # empty
  183. return
  184. self._scene.clear()
  185. x_pos = -150
  186. y_pos = 70
  187. for item in model:
  188. typ = item["type"]
  189. if typ == "Node":
  190. # first, draw all nodes
  191. node_type = item["typeID"]
  192. self._add_node_to_scene(item["id"], node_type, x=x_pos, y=y_pos)
  193. x_pos += 80
  194. y_pos *= -1
  195. for item in model:
  196. typ = item["type"]
  197. if typ == "Edge":
  198. target = item["__target"]
  199. src = item["__source"]
  200. self._add_edge_to_scene(src, target, item["id"])
  201. def _add_node_to_scene(self, node_id, node_type, x=0, y=0):
  202. consyn = mvops.get_consyn_of(node_type)
  203. item = GraphicsNodeItem(node_id, node_type, consyn)
  204. item.setPos(x, y)
  205. item.setFlag(QGraphicsItem.ItemIsMovable, True)
  206. item.setFlag(QGraphicsItem.ItemIsSelectable, True)
  207. self._scene.addItem(item)
  208. def _add_edge_to_scene(self, from_id, to_id, edge_id):
  209. from_item = None
  210. to_item = None
  211. for item in self._scene.items():
  212. try:
  213. node_id = item.node_id
  214. except AttributeError:
  215. # no node item, continue
  216. continue
  217. if node_id == from_id:
  218. from_item = item
  219. continue
  220. if node_id == to_id:
  221. to_item = item
  222. self._scene.draw_edge(from_item, to_item, edge_id=edge_id)
  223. def _on_list_item_clicked(self, event):
  224. # add node to model
  225. node_id = mvops.add_node(self._cur_model, event.text())
  226. # render node
  227. self._add_node_to_scene(node_id, event.text())
  228. self.plainTextEdit.appendPlainText("Added node of type {} to model".format(event.text()))
  229. def _on_attribute_edited(self, item):
  230. # type: (QTableWidgetItem) -> None
  231. """
  232. An attribute value was edited.
  233. If the new entered value is empty, delete the attribute.
  234. """
  235. row = self.tableWidget.row(item)
  236. attr_key = self.tableWidget.item(row, 0).text()
  237. attr_val = self.tableWidget.item(row, 1).text()
  238. node = self._scene.selectedItems()[0]
  239. if not attr_val:
  240. # ask if global or local delete
  241. scope, ok = QInputDialog.getItem(self._parent, "Select scope", "Scope", ["Local", "Global"])
  242. if not ok:
  243. return
  244. del_handler = AttributeDelete()
  245. self.plainTextEdit.appendPlainText("Deleting attribute {}".format(attr_key))
  246. if scope == "Global":
  247. del_handler.execute(self._cur_model, node.node_id, attr_key, local=False)
  248. else:
  249. del_handler.execute(self._cur_model, node.node_id, attr_key, local=True)
  250. # update view
  251. self.tableWidget.removeRow(row)
  252. else:
  253. self.plainTextEdit.appendPlainText("Updating value of attribute {} to {}".format(attr_key, attr_val))
  254. mvops.update_attribute_val(self._cur_model, node.node_id, attr_key, attr_val)
  255. def add_new_attribute(self, key, val="unknown"):
  256. """
  257. Adds a new attribute to the view with key "key" and optional val.
  258. """
  259. selected_node = self._scene.selectedItems()[0]
  260. self.plainTextEdit.appendPlainText("Adding new attribute with key {} to node {}".format(key, selected_node.get_type()))
  261. self.tableWidget.blockSignals(True)
  262. table_item_key = QTableWidgetItem(key)
  263. table_item_key.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
  264. table_item_val = QTableWidgetItem(val)
  265. cur_row_cnt = self.tableWidget.rowCount()
  266. self.tableWidget.insertRow(cur_row_cnt)
  267. self.tableWidget.setItem(cur_row_cnt, 0, table_item_key)
  268. self.tableWidget.setItem(cur_row_cnt, 1, table_item_val)
  269. self.tableWidget.blockSignals(False)
  270. def _on_delete_model_clicked(self, event):
  271. # delete the open example model by iteratively deleting all nodes
  272. self.plainTextEdit.appendPlainText("Deleting model ...")
  273. self.plainTextEdit.repaint()
  274. QApplication.setOverrideCursor(Qt.WaitCursor)
  275. delhander = NodeDelete()
  276. for item in self._scene.items():
  277. if not isinstance(item, GraphicsNodeItem):
  278. continue
  279. self.plainTextEdit.appendPlainText("Deleting node {}".format(item.node_id))
  280. self.plainTextEdit.repaint()
  281. delhander.execute(self._cur_model, item.node_id, local=True, check_if_last=True)
  282. # delete the empty model from the Modelverse
  283. if mvops.delete_example_model(self._cur_model):
  284. self.close()
  285. else:
  286. self.plainTextEdit.appendPlainText("Error: Delete failed")
  287. QApplication.restoreOverrideCursor()