exm_scene.py 16 KB

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