exm_scene.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  1. from enum import Enum
  2. from PyQt5.QtWidgets import QGraphicsScene, QGraphicsItem, QGraphicsLineItem, QGraphicsRectItem, \
  3. QGraphicsEllipseItem, QInputDialog, QTableWidgetItem
  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. import commons
  12. class Mode(Enum):
  13. SELECT = 0
  14. CONNECT = 1
  15. LINE = 2
  16. RECT = 3
  17. CIRCLE = 4
  18. FREE = 5
  19. class SketchScene(QGraphicsScene):
  20. def __init__(self, model, parent):
  21. QGraphicsScene.__init__(self)
  22. self._cur_drawing = False
  23. self._mode = None # set from mainwindow on start
  24. self._connect_from_item = None # mouse pressed on this item if in connect mode
  25. self._cur_model = model
  26. self._orig_point = QPointF()
  27. self._free_draw_lines = []
  28. self._parent = parent
  29. def set_mode(self, mode):
  30. self._mode = mode
  31. def _in_draw_mode(self):
  32. # are we in one of the draw modes? (rect, line, free, ...)
  33. if self._mode == Mode.CONNECT or self._mode == Mode.SELECT:
  34. return False
  35. return True
  36. def draw_edge(self, from_item, to_item, is_new, edge_id=None):
  37. # type: (GraphicsNodeItem, GraphicsNodeItem, bool, str) -> None
  38. # draw an edge between two items. If is_new, also add it to the model
  39. line = GraphicsEdgeItem(from_item, to_item, edge_id)
  40. line.setFlag(QGraphicsItem.ItemIsMovable, False)
  41. line.setFlag(QGraphicsItem.ItemIsSelectable, False)
  42. self.addItem(line)
  43. line.redraw()
  44. if is_new:
  45. edge_id = mvops.add_edge(self._cur_model, from_item.node_id, to_item.node_id)
  46. line.edge_id = edge_id
  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. # load attributes for selected node
  65. self._parent.tableWidget.setRowCount(0)
  66. self._parent.tableWidget.blockSignals(True)
  67. self._parent.plainTextEdit.appendPlainText("Selected node of type {}".format(item.get_type()))
  68. attrs = commons.get_attributes_of_node(self._cur_model, item.node_id)
  69. for attr in attrs:
  70. table_item_key = QTableWidgetItem(attr.key)
  71. table_item_key.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
  72. table_item_val = QTableWidgetItem(attr.val)
  73. cur_row_cnt = self._parent.tableWidget.rowCount()
  74. self._parent.tableWidget.insertRow(cur_row_cnt)
  75. self._parent.tableWidget.setItem(cur_row_cnt, 0, table_item_key)
  76. self._parent.tableWidget.setItem(cur_row_cnt, 1, table_item_val)
  77. self._parent.tableWidget.blockSignals(False)
  78. elif isinstance(item, GraphicsEdgeItem):
  79. self._parent.plainTextEdit.appendPlainText("Selected edge")
  80. else:
  81. pass
  82. QGraphicsScene.mousePressEvent(self, event)
  83. def mouseMoveEvent(self, event):
  84. # if in freehand mode, draw lines from move movement
  85. if self._mode == Mode.FREE and self._cur_drawing:
  86. pt = event.scenePos()
  87. line = QGraphicsLineItem(self._orig_point.x(), self._orig_point.y(), pt.x(), pt.y())
  88. line.setPen(QPen(Qt.black, 2, Qt.SolidLine))
  89. self.addItem(line)
  90. self._orig_point = pt
  91. self._free_draw_lines.append(line)
  92. QGraphicsScene.mouseMoveEvent(self, event)
  93. def mouseReleaseEvent(self, event):
  94. if self._cur_drawing:
  95. end_point = event.scenePos()
  96. if self._mode == Mode.LINE:
  97. line = SketchedLineItem(self._orig_point.x(), self._orig_point.y(),
  98. end_point.x(), end_point.y())
  99. line.setPen(QPen(Qt.black, 2, Qt.SolidLine))
  100. self.addItem(line)
  101. elif self._mode == Mode.RECT:
  102. width = abs(end_point.x() - self._orig_point.x())
  103. height = abs(end_point.y() - self._orig_point.y())
  104. rect = QGraphicsRectItem(self._orig_point.x(), self._orig_point.y(),
  105. width, height)
  106. rect.setPen(QPen(Qt.black, 2, Qt.SolidLine))
  107. self.addItem(rect)
  108. elif self._mode == Mode.CIRCLE:
  109. width = abs(end_point.x() - self._orig_point.x())
  110. height = abs(end_point.y() - self._orig_point.y())
  111. ellipse = QGraphicsEllipseItem(self._orig_point.x(), self._orig_point.y(),
  112. width, height)
  113. ellipse.setPen(QPen(Qt.black, 2, Qt.SolidLine))
  114. self.addItem(ellipse)
  115. elif self._mode == Mode.FREE:
  116. line = QGraphicsLineItem(self._orig_point.x(), self._orig_point.y(),
  117. end_point.x(), end_point.y())
  118. line.setPen(QPen(Qt.black, 2, Qt.SolidLine))
  119. self.addItem(line)
  120. # group lines together
  121. self._free_draw_lines.append(line)
  122. group = self.createItemGroup(self._free_draw_lines)
  123. group.setFlag(QGraphicsItem.ItemIsSelectable, False)
  124. group.setFlag(QGraphicsItem.ItemIsMovable, False)
  125. del self._free_draw_lines[:]
  126. else:
  127. pass
  128. self._cur_drawing = False
  129. else:
  130. if self._mode == Mode.SELECT:
  131. item = self.itemAt(event.scenePos(), QTransform())
  132. if not item:
  133. return
  134. for item in self.items():
  135. if isinstance(item, GraphicsEdgeItem):
  136. item.redraw()
  137. elif self._mode == Mode.CONNECT:
  138. item = self.itemAt(event.scenePos(), QTransform())
  139. if not item or not isinstance(item, GraphicsNodeItem):
  140. return
  141. self.draw_edge(self._connect_from_item, item, is_new=True)
  142. else:
  143. pass
  144. QGraphicsScene.mouseReleaseEvent(self, event)
  145. def keyPressEvent(self, event):
  146. if not self._mode == Mode.SELECT:
  147. return
  148. # "del" deletes all selected items
  149. if event.key() == Qt.Key_Delete:
  150. self._handle_keypress_delete(self.selectedItems())
  151. # "G" groups selected items
  152. elif event.key() == Qt.Key_G:
  153. selected = self.selectedItems()
  154. self.clearSelection()
  155. group = SketchGroup()
  156. for item in selected:
  157. group.addToGroup(item)
  158. bb_rect = QGraphicsRectItem(group.boundingRect())
  159. bb_rect.setData(0, "groupBBox") # identifier for "does not belong to the actual sketch"
  160. bb_rect.setPen(QPen(Qt.gray, 1, Qt.DashLine))
  161. group.addToGroup(bb_rect)
  162. self.addItem(group)
  163. group.setFlag(QGraphicsItem.ItemIsSelectable, True)
  164. group.setFlag(QGraphicsItem.ItemIsMovable, True)
  165. # "T" lets user type selected element
  166. elif event.key() == Qt.Key_T:
  167. # exactly one element that is a group must be selected
  168. selected = self.selectedItems()
  169. if len(selected) != 1:
  170. return
  171. item = selected[0]
  172. if isinstance(item, SketchGroup):
  173. self._handle_keypress_type_on_group(item)
  174. elif isinstance(item, GraphicsNodeItem):
  175. self._handle_keypress_type_on_node(item)
  176. else:
  177. self._parent.plainTextEdit.appendPlainText("Error: Cannot type element {}".format(item))
  178. # "A" attributes a node
  179. elif event.key() == Qt.Key_A:
  180. self._handle_keypress_attribute(self.selectedItems())
  181. else:
  182. QGraphicsScene.keyPressEvent(self, event)
  183. def _handle_keypress_type_on_node(self, item):
  184. # type: (GraphicsNodeItem) -> None
  185. """
  186. type an already typed node = retype it
  187. """
  188. old_type = item.get_type()
  189. node_type, ok = QInputDialog.getText(self._parent, "Retype node", "New type", text=item.get_type())
  190. if not ok or not node_type:
  191. # user canceled or node type empty
  192. return
  193. if node_type in commons.get_available_types():
  194. self._parent.plainTextEdit.appendPlainText("Error: Already such a type: {}".format(node_type))
  195. return
  196. # local or global retype?
  197. scope, ok = QInputDialog.getItem(self._parent, "Select scope", "Scope", ["Local", "Global"])
  198. if not ok:
  199. return
  200. self._parent.plainTextEdit.appendPlainText("Performing retype of node {}".format(node_type))
  201. QApplication.setOverrideCursor(Qt.WaitCursor)
  202. retype_handler = NodeRetype()
  203. if scope == "Global":
  204. retype_handler.execute(self._cur_model, item.node_id, node_type, local=False)
  205. else:
  206. retype_handler.execute(self._cur_model, item.node_id, node_type, local=True)
  207. # rename on screen
  208. if scope == "Global":
  209. for node_item in self.items():
  210. if not isinstance(node_item, GraphicsNodeItem):
  211. continue
  212. if node_item.get_type() == old_type:
  213. node_item.set_type(node_type)
  214. else:
  215. item.set_type(node_type)
  216. # update list widget
  217. self._parent.populate_types()
  218. QApplication.restoreOverrideCursor()
  219. def _handle_keypress_type_on_group(self, group):
  220. # type: (SketchGroup) -> None
  221. """
  222. type the selected group = make a real node out of it and store it in the model
  223. also capture its concrete syntax and store it in the modelverse
  224. """
  225. # get the type from the user
  226. node_type, ok = QInputDialog.getText(self._parent, "Type node", "Enter type")
  227. if not ok or not node_type:
  228. # user canceled or empty type string
  229. return
  230. if node_type in commons.get_available_types():
  231. self._parent.plainTextEdit.appendPlainText("Error: Already such a type: {}".format(node_type))
  232. return
  233. # perform add local or global?
  234. scope, ok = QInputDialog.getItem(self._parent, "Select scope", "Scope", ["Local", "Global"])
  235. if not ok:
  236. return
  237. self._parent.plainTextEdit.appendPlainText("Typing group to type {}".format(node_type))
  238. QApplication.setOverrideCursor(Qt.WaitCursor)
  239. # add the node to the model
  240. add_handler = NodeAdd()
  241. if scope == "Global":
  242. add_handler.execute(self._cur_model, node_type, local=False)
  243. else:
  244. add_handler.execute(self._cur_model, node_type, local=True)
  245. # Get node id of newly added node in current model
  246. nodeid = commons.all_nodes_with_type(self._cur_model, node_type)[0]
  247. self._parent.plainTextEdit.appendPlainText("Capturing concrete syntax of group ...")
  248. self._parent.plainTextEdit.repaint()
  249. # create concrete syntax model for the sketched elements
  250. csm = mvops.new_concrete_syntax_model(node_type, IconType.PRIMITIVE)
  251. if not csm:
  252. self._parent.plainTextEdit.appendPlainText("Error: Concrete syntax for type {} already exists".format(node_type))
  253. return
  254. # check if we need to scale the group items down first to 100x100
  255. group_brect = group.boundingRect()
  256. need_scale = False
  257. scale_factor = 1.0
  258. if group_brect.width() > 100 or group_brect.height() > 100:
  259. need_scale = True
  260. scale_factor = 100.0 / max(group_brect.width(), group_brect.height())
  261. # populate CSM with sketched elements
  262. for item in group.childItems():
  263. if item.data(0) == "groupBBox":
  264. # just the bounding box from the group, ignore
  265. continue
  266. if isinstance(item, QGraphicsRectItem):
  267. rect = group.get_item_coord_relative(item)
  268. if need_scale:
  269. new_top_left = rect.topLeft() * scale_factor
  270. new_width = rect.width() * scale_factor
  271. new_height = rect.height() * scale_factor
  272. rect.setTopLeft(new_top_left)
  273. rect.setWidth(new_width)
  274. rect.setHeight(new_height)
  275. mvops.add_rect_to_cs(csm, rect)
  276. elif isinstance(item, QGraphicsEllipseItem):
  277. rect = group.get_item_coord_relative(item)
  278. if need_scale:
  279. new_top_left = rect.topLeft() * scale_factor
  280. new_width = rect.width() * scale_factor
  281. new_height = rect.height() * scale_factor
  282. rect.setTopLeft(new_top_left)
  283. rect.setWidth(new_width)
  284. rect.setHeight(new_height)
  285. mvops.add_rect_to_cs(csm, rect)
  286. elif isinstance(item, SketchedLineItem):
  287. p1, p2 = group.get_item_coord_relative(item)
  288. if need_scale:
  289. p1 *= scale_factor
  290. p2 *= scale_factor
  291. mvops.add_line_to_cs(csm, p1, p2)
  292. else:
  293. print("Dont know how to capture CS of item {}".format(item))
  294. # update view: replace group by actual node item with newly populated CS
  295. csm_content = mvops.get_consyn_of(node_type)
  296. nodeitem = GraphicsNodeItem(nodeid, node_type, csm_content)
  297. nodeitem.setPos(group.scenePos())
  298. nodeitem.setFlag(QGraphicsItem.ItemIsSelectable, True)
  299. nodeitem.setFlag(QGraphicsItem.ItemIsMovable, True)
  300. self.removeItem(group)
  301. self.addItem(nodeitem)
  302. self._parent.populate_types()
  303. QApplication.restoreOverrideCursor()
  304. self._parent.plainTextEdit.appendPlainText("OK")
  305. def _handle_keypress_delete(self, selected):
  306. del_hander = NodeDelete()
  307. if len(selected) == 1 and isinstance(selected[0], GraphicsEdgeItem):
  308. self._parent.plainTextEdit.appendPlainText("Deleting edge")
  309. edge = selected[0]
  310. mvops.delete_node(self._cur_model, edge.edge_id)
  311. self.removeItem(edge)
  312. return
  313. for item in selected:
  314. # only delete nodes, edges are taken care of later
  315. if isinstance(item, GraphicsNodeItem):
  316. self._parent.plainTextEdit.appendPlainText("Deleting node of type {}".format(item.get_type()))
  317. # when deleting a node, local or global?
  318. scope, ok = QInputDialog.getItem(self._parent, "Select scope", "Scope", ["Local", "Global"])
  319. if not ok:
  320. return
  321. QApplication.setOverrideCursor(Qt.WaitCursor)
  322. if scope == "Global":
  323. # global language evolution, so delete node with same type everywhere
  324. del_hander.execute(self._cur_model, item.node_id, local=False, check_if_last=False)
  325. # also delete its associated CS model
  326. mvops.del_concrete_syntax_model(item.get_type())
  327. else:
  328. # just local, delete from this model only
  329. del_hander.execute(self._cur_model, item.node_id, local=True, check_if_last=True)
  330. if del_hander.was_last():
  331. # it was the last node, so delete its CS model as well
  332. mvops.del_concrete_syntax_model(item.get_type())
  333. # in view, delete edges that were connected to this node as well
  334. # modelverse does this on its own so do not delete edges explicitly here
  335. for edge in self.items():
  336. if not isinstance(edge, GraphicsEdgeItem):
  337. continue
  338. if edge.from_item.node_id == item.node_id or edge.to_item.node_id == item.node_id:
  339. self.removeItem(edge)
  340. self.removeItem(item)
  341. elif isinstance(item, GraphicsEdgeItem):
  342. continue
  343. else:
  344. # no NodeItem -> untyped sketch, simply remove
  345. for obj in selected:
  346. self.removeItem(obj)
  347. # if any node was deleted, repopulate list of available items
  348. if any(isinstance(item, GraphicsNodeItem) for item in selected):
  349. self._parent.populate_types()
  350. QApplication.restoreOverrideCursor()
  351. def _handle_keypress_attribute(self, selected):
  352. if not len(selected) == 1:
  353. return
  354. item = selected[0]
  355. if not isinstance(item, GraphicsNodeItem):
  356. return
  357. # ask user for key value
  358. key, ok = QInputDialog.getText(self._parent, "New attribute", "Key value")
  359. if not ok or not key:
  360. return
  361. # check if key value already used for this node
  362. attrs = commons.get_attributes_of_node(self._cur_model, item.node_id)
  363. for attr in attrs:
  364. if attr.key == key:
  365. self._parent.plainTextEdit.appendPlainText("Error: Already such a key for the node: {}".format(key))
  366. return
  367. self._parent.add_new_attribute(key)