exm_scene.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
  1. from enum import Enum
  2. from PyQt5.QtWidgets import QGraphicsScene, QGraphicsItem, QGraphicsLineItem, QGraphicsRectItem, \
  3. QGraphicsEllipseItem, QInputDialog, QTableWidgetItem, QMessageBox
  4. from PyQt5.Qt import Qt, QPointF, QPen, QTransform, QApplication
  5. from sketchUI.graphics_edge_item import GraphicsEdgeItem
  6. from sketchUI.graphics_node_item import GraphicsNodeItem, IconType
  7. from sketchUI.graphics_sketch_group import SketchGroup
  8. from sketchUI.graphics_sketch_line import SketchedLineItem
  9. from sketchUI import mvops
  10. from evolution.node_ops import NodeAdd, NodeDelete, NodeRetype
  11. from evolution.edge_ops import EdgeDel, EdgeAdd
  12. from evolution.attribute_ops import AttributeAdd
  13. import commons
  14. class Mode(Enum):
  15. SELECT = 0
  16. CONNECT = 1
  17. LINE = 2
  18. RECT = 3
  19. CIRCLE = 4
  20. FREE = 5
  21. class SketchScene(QGraphicsScene):
  22. def __init__(self, model, parent):
  23. QGraphicsScene.__init__(self)
  24. self._cur_drawing = False
  25. self._mode = None # set from mainwindow on start
  26. self._connect_from_item = None # mouse pressed on this item if in connect mode
  27. self._cur_model = model
  28. self._orig_point = QPointF()
  29. self._free_draw_lines = []
  30. self._parent = parent
  31. def set_mode(self, mode):
  32. self._mode = mode
  33. def _in_draw_mode(self):
  34. # are we in one of the draw modes? (rect, line, free, ...)
  35. if self._mode == Mode.CONNECT or self._mode == Mode.SELECT:
  36. return False
  37. return True
  38. def draw_edge(self, from_item, to_item, edge_id=None):
  39. # type: (GraphicsNodeItem, GraphicsNodeItem, str) -> GraphicsEdgeItem
  40. # draw an edge between two items.
  41. line = GraphicsEdgeItem(from_item, to_item, edge_id)
  42. line.setFlag(QGraphicsItem.ItemIsMovable, False)
  43. line.setFlag(QGraphicsItem.ItemIsSelectable, False)
  44. self.addItem(line)
  45. line.redraw()
  46. return line
  47. def mousePressEvent(self, event):
  48. if event.button() == Qt.LeftButton and self._in_draw_mode():
  49. # start drawing, save click point
  50. self._orig_point = event.scenePos()
  51. self._cur_drawing = True
  52. elif event.button() == Qt.LeftButton and self._mode == Mode.CONNECT:
  53. # store clicked item to connect it later
  54. item = self.itemAt(event.scenePos(), QTransform())
  55. if not item or not isinstance(item, GraphicsNodeItem):
  56. return
  57. else:
  58. self._connect_from_item = item
  59. elif event.button() == Qt.LeftButton and self._mode == Mode.SELECT:
  60. item = self.itemAt(event.scenePos(), QTransform())
  61. if not item:
  62. return
  63. elif isinstance(item, GraphicsNodeItem):
  64. self._parent.plainTextEdit.appendPlainText("Selected node {}:{}".format(item.node_id, item.get_type()))
  65. self.highlight_node(item.node_id, Qt.blue)
  66. # load attributes for selected node
  67. self._parent.tableWidget.setRowCount(0)
  68. self._parent.tableWidget.blockSignals(True)
  69. attrs = commons.get_attributes_of_node(self._cur_model, item.node_id)
  70. for attr in attrs:
  71. table_item_key = QTableWidgetItem(attr.key)
  72. table_item_key.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
  73. table_item_val = QTableWidgetItem(attr.val)
  74. cur_row_cnt = self._parent.tableWidget.rowCount()
  75. self._parent.tableWidget.insertRow(cur_row_cnt)
  76. self._parent.tableWidget.setItem(cur_row_cnt, 0, table_item_key)
  77. self._parent.tableWidget.setItem(cur_row_cnt, 1, table_item_val)
  78. self._parent.tableWidget.blockSignals(False)
  79. elif isinstance(item, GraphicsEdgeItem):
  80. self._parent.plainTextEdit.appendPlainText("Selected edge ({},{}),id={}".format(item.from_item.get_type(),
  81. item.to_item.get_type(), item.edge_id))
  82. else:
  83. pass
  84. QGraphicsScene.mousePressEvent(self, event)
  85. def mouseMoveEvent(self, event):
  86. # if in freehand mode, draw lines from move movement
  87. if self._mode == Mode.FREE and self._cur_drawing:
  88. pt = event.scenePos()
  89. line = QGraphicsLineItem(self._orig_point.x(), self._orig_point.y(), pt.x(), pt.y())
  90. line.setPen(QPen(Qt.black, 2, Qt.SolidLine))
  91. self.addItem(line)
  92. self._orig_point = pt
  93. self._free_draw_lines.append(line)
  94. QGraphicsScene.mouseMoveEvent(self, event)
  95. def mouseReleaseEvent(self, event):
  96. if self._cur_drawing:
  97. end_point = event.scenePos()
  98. if self._mode == Mode.LINE:
  99. line = SketchedLineItem(self._orig_point.x(), self._orig_point.y(),
  100. end_point.x(), end_point.y())
  101. line.setPen(QPen(Qt.black, 2, Qt.SolidLine))
  102. self.addItem(line)
  103. elif self._mode == Mode.RECT:
  104. width = abs(end_point.x() - self._orig_point.x())
  105. height = abs(end_point.y() - self._orig_point.y())
  106. rect = QGraphicsRectItem(self._orig_point.x(), self._orig_point.y(),
  107. width, height)
  108. rect.setPen(QPen(Qt.black, 2, Qt.SolidLine))
  109. self.addItem(rect)
  110. elif self._mode == Mode.CIRCLE:
  111. width = abs(end_point.x() - self._orig_point.x())
  112. height = abs(end_point.y() - self._orig_point.y())
  113. ellipse = QGraphicsEllipseItem(self._orig_point.x(), self._orig_point.y(),
  114. width, height)
  115. ellipse.setPen(QPen(Qt.black, 2, Qt.SolidLine))
  116. self.addItem(ellipse)
  117. elif self._mode == Mode.FREE:
  118. line = QGraphicsLineItem(self._orig_point.x(), self._orig_point.y(),
  119. end_point.x(), end_point.y())
  120. line.setPen(QPen(Qt.black, 2, Qt.SolidLine))
  121. self.addItem(line)
  122. # group lines together
  123. self._free_draw_lines.append(line)
  124. group = self.createItemGroup(self._free_draw_lines)
  125. group.setFlag(QGraphicsItem.ItemIsSelectable, False)
  126. group.setFlag(QGraphicsItem.ItemIsMovable, False)
  127. del self._free_draw_lines[:]
  128. else:
  129. pass
  130. self._cur_drawing = False
  131. else:
  132. if self._mode == Mode.SELECT:
  133. item = self.itemAt(event.scenePos(), QTransform())
  134. if not item:
  135. return
  136. for item in self.items():
  137. if isinstance(item, GraphicsEdgeItem):
  138. item.redraw()
  139. elif self._mode == Mode.CONNECT:
  140. item = self.itemAt(event.scenePos(), QTransform())
  141. if not item or not isinstance(item, GraphicsNodeItem):
  142. return
  143. from_item = self._connect_from_item
  144. to_item = item
  145. # global or local edge add?
  146. scope, ok = QInputDialog.getItem(self._parent, "Select scope", "Scope", ["Local", "Global"])
  147. if not ok:
  148. return
  149. QApplication.setOverrideCursor(Qt.WaitCursor)
  150. add_handler = EdgeAdd()
  151. if scope == "Global":
  152. add_handler.execute(self._cur_model, from_item.node_id, to_item.node_id, local=False)
  153. else:
  154. add_handler.execute(self._cur_model, from_item.node_id, to_item.node_id, local=True)
  155. self.draw_edge(self._connect_from_item, item)
  156. # reload model because only then the ids of the edges get set anew
  157. self._parent._load_model()
  158. QApplication.restoreOverrideCursor()
  159. else:
  160. pass
  161. QGraphicsScene.mouseReleaseEvent(self, event)
  162. def keyPressEvent(self, event):
  163. if not self._mode == Mode.SELECT:
  164. return
  165. # "del" deletes all selected items
  166. if event.key() == Qt.Key_Delete:
  167. self._handle_keypress_delete(self.selectedItems())
  168. # "G" groups selected items
  169. elif event.key() == Qt.Key_G:
  170. selected = self.selectedItems()
  171. self.clearSelection()
  172. group = SketchGroup()
  173. for item in selected:
  174. group.addToGroup(item)
  175. bb_rect = QGraphicsRectItem(group.boundingRect())
  176. bb_rect.setData(0, "groupBBox") # identifier for "does not belong to the actual sketch"
  177. bb_rect.setPen(QPen(Qt.gray, 1, Qt.DashLine))
  178. group.addToGroup(bb_rect)
  179. self.addItem(group)
  180. group.setFlag(QGraphicsItem.ItemIsSelectable, True)
  181. group.setFlag(QGraphicsItem.ItemIsMovable, True)
  182. # "T" lets user type selected element
  183. elif event.key() == Qt.Key_T:
  184. # exactly one element that is a group must be selected
  185. selected = self.selectedItems()
  186. if len(selected) != 1:
  187. return
  188. item = selected[0]
  189. if isinstance(item, SketchGroup):
  190. self._handle_keypress_type_on_group(item)
  191. elif isinstance(item, GraphicsNodeItem):
  192. self._handle_keypress_type_on_node(item)
  193. else:
  194. self._parent.plainTextEdit.appendPlainText("Error: Cannot type element {}".format(item))
  195. # "A" attributes a node
  196. elif event.key() == Qt.Key_A:
  197. self._handle_keypress_attribute(self.selectedItems())
  198. else:
  199. QGraphicsScene.keyPressEvent(self, event)
  200. def _handle_keypress_type_on_node(self, item):
  201. # type: (GraphicsNodeItem) -> None
  202. """
  203. type an already typed node = retype it
  204. """
  205. old_type = item.get_type()
  206. new_type, ok = QInputDialog.getText(self._parent, "Retype node", "New type", text=item.get_type())
  207. if not ok or not new_type:
  208. # user canceled or node type empty
  209. return
  210. # local or global?
  211. scope, ok = QInputDialog.getItem(self._parent, "Select scope", "Scope", ["Local", "Global"])
  212. if not ok:
  213. return
  214. retype_handler = NodeRetype()
  215. QApplication.setOverrideCursor(Qt.WaitCursor)
  216. if scope == "Global":
  217. # global retype -> retype all existing nodes to new type
  218. if new_type in commons.get_available_types():
  219. self._parent.plainTextEdit.appendPlainText("Error: Already such a type: {}".format(new_type))
  220. return
  221. self._parent.plainTextEdit.appendPlainText("Performing global retype of node {}".format(new_type))
  222. retype_handler.execute(self._cur_model, item.node_id, new_type, local=False)
  223. # also rename the concrete syntax
  224. mvops.move_consyn_model("models/consyn/"+old_type, "models/consyn/"+new_type)
  225. # rename all types on canvas
  226. for node_item in self.items():
  227. if not isinstance(node_item, GraphicsNodeItem):
  228. continue
  229. if node_item.get_type() == old_type:
  230. node_item.set_type(new_type)
  231. # update list widget
  232. self._parent.populate_types()
  233. else:
  234. # local rename -> type must exist
  235. pass # TODO implement
  236. retype_handler.repair()
  237. QApplication.restoreOverrideCursor()
  238. def _handle_keypress_type_on_group(self, group):
  239. # type: (SketchGroup) -> None
  240. """
  241. type the selected group = make a real node out of it and store it in the model
  242. also capture its concrete syntax and store it in the modelverse
  243. """
  244. # get the type from the user
  245. node_type, ok = QInputDialog.getText(self._parent, "Type node", "Enter type")
  246. if not ok or not node_type:
  247. # user canceled or empty type string
  248. return
  249. reload = False
  250. if node_type in commons.get_available_types():
  251. # There is already such a type registered. Overwrite its conrete syntax?
  252. box = QMessageBox()
  253. box.setText("Type {} already exists".format(node_type))
  254. box.setInformativeText("Do you want to overwrite it?")
  255. box.setStandardButtons(QMessageBox.No | QMessageBox.Yes)
  256. box.setDefaultButton(QMessageBox.No)
  257. ret = box.exec_()
  258. if ret == QMessageBox.Yes:
  259. reload = True
  260. pass
  261. else:
  262. return
  263. self._parent.plainTextEdit.appendPlainText("Typing group to type {}".format(node_type))
  264. QApplication.setOverrideCursor(Qt.WaitCursor)
  265. # add the node to the model
  266. add_handler = NodeAdd()
  267. add_handler.execute(self._cur_model, node_type, local=True)
  268. # Get node id of newly added node in current model
  269. nodeid = commons.all_nodes_with_type(self._cur_model, node_type)[0]
  270. self._parent.plainTextEdit.appendPlainText("Capturing concrete syntax of group ...")
  271. self._parent.plainTextEdit.repaint()
  272. # create concrete syntax model for the sketched elements
  273. csm = mvops.new_concrete_syntax_model(node_type, IconType.PRIMITIVE, overwrite=True)
  274. if not csm:
  275. self._parent.plainTextEdit.appendPlainText("Error: Concrete syntax for type {} already exists".format(node_type))
  276. return
  277. # check if we need to scale the group items down to 100x100 first
  278. group_brect = group.boundingRect()
  279. need_scale = False
  280. scale_factor = 1.0
  281. if group_brect.width() > 100 or group_brect.height() > 100:
  282. need_scale = True
  283. scale_factor = 100.0 / max(group_brect.width(), group_brect.height())
  284. # populate CSM with sketched elements
  285. for item in group.childItems():
  286. if item.data(0) == "groupBBox":
  287. # just the bounding box from the group, ignore
  288. continue
  289. if isinstance(item, QGraphicsRectItem):
  290. rect = group.get_item_coord_relative(item)
  291. if need_scale:
  292. new_top_left = rect.topLeft() * scale_factor
  293. new_width = rect.width() * scale_factor
  294. new_height = rect.height() * scale_factor
  295. rect.setTopLeft(new_top_left)
  296. rect.setWidth(new_width)
  297. rect.setHeight(new_height)
  298. mvops.add_rect_to_cs(csm, rect)
  299. elif isinstance(item, QGraphicsEllipseItem):
  300. rect = group.get_item_coord_relative(item)
  301. if need_scale:
  302. new_top_left = rect.topLeft() * scale_factor
  303. new_width = rect.width() * scale_factor
  304. new_height = rect.height() * scale_factor
  305. rect.setTopLeft(new_top_left)
  306. rect.setWidth(new_width)
  307. rect.setHeight(new_height)
  308. mvops.add_ellipse_to_cs(csm, rect)
  309. elif isinstance(item, SketchedLineItem):
  310. p1, p2 = group.get_item_coord_relative(item)
  311. if need_scale:
  312. p1 *= scale_factor
  313. p2 *= scale_factor
  314. mvops.add_line_to_cs(csm, p1, p2)
  315. else:
  316. print("Dont know how to capture CS of item {}".format(item))
  317. if reload:
  318. # reload whole scene because concrete syntax of a type has changed
  319. self._parent._load_model()
  320. QApplication.restoreOverrideCursor()
  321. else:
  322. # update view: replace group by actual node item with newly populated CS
  323. csm_content = mvops.get_consyn_of(node_type)
  324. nodeitem = GraphicsNodeItem(nodeid, node_type, csm_content)
  325. nodeitem.setPos(group.scenePos())
  326. nodeitem.setFlag(QGraphicsItem.ItemIsSelectable, True)
  327. nodeitem.setFlag(QGraphicsItem.ItemIsMovable, True)
  328. self.removeItem(group)
  329. self.addItem(nodeitem)
  330. self._parent.populate_types()
  331. QApplication.restoreOverrideCursor()
  332. self._parent.plainTextEdit.appendPlainText("OK")
  333. def _handle_keypress_delete(self, selected):
  334. if len(selected) == 1 and isinstance(selected[0], GraphicsEdgeItem):
  335. # an edge is to be deleted
  336. edge = selected[0]
  337. edge_from_type = edge.from_item.get_type()
  338. edge_to_type = edge.to_item.get_type()
  339. self._parent.plainTextEdit.appendPlainText("Deleting edge ({},{}),id={}".format(edge_from_type, edge_to_type, edge.edge_id))
  340. # local or global?
  341. scope, ok = QInputDialog.getItem(self._parent, "Select scope", "Scope", ["Local", "Global"])
  342. if not ok:
  343. return
  344. del_handler = EdgeDel()
  345. QApplication.setOverrideCursor(Qt.WaitCursor)
  346. if scope == "Global":
  347. del_handler.execute(self._cur_model, edge.edge_id, local=False, check_if_last=False)
  348. # delete all edges with same typing in view
  349. for item in self.items():
  350. if not isinstance(item, GraphicsEdgeItem):
  351. continue
  352. if item.from_item.get_type() in [edge_from_type, edge_to_type] and item.to_item.get_type() in [edge_from_type, edge_to_type]:
  353. self.removeItem(item)
  354. else:
  355. del_handler.execute(self._cur_model, edge.edge_id, local=True, check_if_last=True)
  356. # delete from view
  357. self.removeItem(edge)
  358. QApplication.restoreOverrideCursor()
  359. elif len(selected) == 1 and isinstance(selected[0], GraphicsNodeItem):
  360. del_handler = NodeDelete()
  361. node = selected[0]
  362. # a node is to be deleted
  363. self._parent.plainTextEdit.appendPlainText("Deleting node of type {}".format(node.get_type()))
  364. # when deleting a node, local or global?
  365. scope, ok = QInputDialog.getItem(self._parent, "Select scope", "Scope", ["Local", "Global"])
  366. if not ok:
  367. return
  368. QApplication.setOverrideCursor(Qt.WaitCursor)
  369. if scope == "Global":
  370. # global language evolution, so delete node with same type everywhere
  371. del_handler.execute(self._cur_model, node.node_id, local=False)
  372. # also delete its associated CS model
  373. mvops.del_concrete_syntax_model(node.get_type())
  374. # delete all nodes of type from view
  375. for item in self.items():
  376. if not isinstance(item, GraphicsNodeItem):
  377. continue
  378. if item.get_type() == node.get_type():
  379. self.removeItem(item)
  380. else:
  381. # just local, delete from this model only
  382. del_handler.execute(self._cur_model, node.node_id, local=True)
  383. # delete this node from view
  384. self.removeItem(node)
  385. # repair any possibly invalidated instance models
  386. del_handler.repair()
  387. if scope == "Local" and del_handler.was_last:
  388. # local delete removed type completely, so remove the dangling concrete syntax model as well
  389. mvops.del_concrete_syntax_model(node.get_type())
  390. # in view, delete edges that were connected to this node as well
  391. # modelverse does this on its own so do not delete edges explicitly here
  392. if scope == "Local":
  393. for edge in self.items():
  394. if not isinstance(edge, GraphicsEdgeItem):
  395. continue
  396. if edge.from_item.node_id == node.node_id or edge.to_item.node_id == node.node_id:
  397. self.removeItem(edge)
  398. else:
  399. # have to remove all edges connected to this type in model
  400. for edge in self.items():
  401. if not isinstance(edge, GraphicsEdgeItem):
  402. continue
  403. if edge.from_item.get_type() == node.get_type() or edge.to_item.get_type() == node.get_type():
  404. self.removeItem(edge)
  405. # repopulate available types just in case
  406. self._parent.populate_types()
  407. else:
  408. if not any(isinstance(x, GraphicsNodeItem) for x in selected) and not any(isinstance(x, GraphicsEdgeItem) for x in selected):
  409. # neither NodeItem nor EdgeItem in selected -> untyped sketch item, simply remove
  410. for obj in selected:
  411. self.removeItem(obj)
  412. QApplication.restoreOverrideCursor()
  413. def _handle_keypress_attribute(self, selected):
  414. if not len(selected) == 1:
  415. return
  416. item = selected[0]
  417. if not isinstance(item, GraphicsNodeItem):
  418. return
  419. # ask user for key value
  420. key, ok = QInputDialog.getText(self._parent, "New attribute", "Key value")
  421. if not ok or not key:
  422. return
  423. # check if key value already used for this node
  424. attrs = commons.get_attributes_of_node(self._cur_model, item.node_id)
  425. for attr in attrs:
  426. if attr.key == key:
  427. self._parent.plainTextEdit.appendPlainText("Error: Already such a key for the node: {}".format(key))
  428. return
  429. # ask of global or local add attribute
  430. scope, ok = QInputDialog.getItem(self._parent, "Select scope", "Scope", ["Local", "Global"])
  431. if not ok:
  432. return
  433. add_handler = AttributeAdd()
  434. if scope == "Global":
  435. add_handler.execute(self._cur_model, item.node_id, key, "unknown", local=False)
  436. else:
  437. add_handler.execute(self._cur_model, item.node_id, key, "unknown", local=True)
  438. # add to view
  439. self._parent.add_new_attribute(key, "unknown")
  440. def highlight_node(self, node_id, color):
  441. for item in self.items():
  442. if not isinstance(item, GraphicsNodeItem):
  443. continue
  444. if item.node_id == node_id:
  445. item.set_highlighted(True, color)
  446. else:
  447. item.set_highlighted(False, color)
  448. item.update(item.boundingRect())