exm_scene.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. from enum import Enum
  2. from PyQt5.QtWidgets import QGraphicsScene, QGraphicsItem, QGraphicsLineItem, QGraphicsRectItem, \
  3. QGraphicsEllipseItem, QInputDialog, QGraphicsItemGroup
  4. from PyQt5.Qt import Qt, QPointF, QPen, QTransform
  5. from sketchUI.graphics_edge_item import GraphicsEdgeItem
  6. from sketchUI.graphics_node_item import GraphicsNodeItem
  7. from sketchUI import mvops
  8. from evolution.node_ops import NodeAdd, NodeDelete, NodeRetype
  9. from commons import all_nodes_with_type
  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._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):
  35. line = GraphicsEdgeItem(from_item, to_item)
  36. line.setFlag(QGraphicsItem.ItemIsMovable, False)
  37. line.setFlag(QGraphicsItem.ItemIsSelectable, False)
  38. self.addItem(line)
  39. line.redraw()
  40. if is_new:
  41. mvops.add_edge(self._cur_model, from_item.node_id, to_item.node_id)
  42. def mousePressEvent(self, event):
  43. if event.button() == Qt.LeftButton and self._in_draw_mode():
  44. # start drawing, save click point
  45. self._orig_point = event.scenePos()
  46. self._cur_drawing = True
  47. elif event.button() == Qt.LeftButton and self._mode == Mode.CONNECT:
  48. item = self.itemAt(event.scenePos(), QTransform())
  49. if not item:
  50. return
  51. self._from_item = item
  52. else:
  53. pass
  54. QGraphicsScene.mousePressEvent(self, event)
  55. def mouseMoveEvent(self, event):
  56. # if in freehand mode, draw lines from move movement
  57. if self._mode == Mode.FREE and self._cur_drawing:
  58. pt = event.scenePos()
  59. line = QGraphicsLineItem(self._orig_point.x(), self._orig_point.y(), pt.x(), pt.y())
  60. line.setPen(QPen(Qt.black, 2, Qt.SolidLine))
  61. self.addItem(line)
  62. self._orig_point = pt
  63. self._free_draw_lines.append(line)
  64. QGraphicsScene.mouseMoveEvent(self, event)
  65. def mouseReleaseEvent(self, event):
  66. if self._cur_drawing:
  67. end_point = event.scenePos()
  68. if self._mode == Mode.LINE:
  69. line = QGraphicsLineItem(self._orig_point.x(), self._orig_point.y(),
  70. end_point.x(), end_point.y())
  71. line.setPen(QPen(Qt.black, 2, Qt.SolidLine))
  72. self.addItem(line)
  73. elif self._mode == Mode.RECT:
  74. width = abs(end_point.x() - self._orig_point.x())
  75. height = abs(end_point.y() - self._orig_point.y())
  76. rect = QGraphicsRectItem(self._orig_point.x(), self._orig_point.y(),
  77. width, height)
  78. rect.setPen(QPen(Qt.black, 2, Qt.SolidLine))
  79. self.addItem(rect)
  80. elif self._mode == Mode.CIRCLE:
  81. width = abs(end_point.x() - self._orig_point.x())
  82. height = abs(end_point.y() - self._orig_point.y())
  83. ellipse = QGraphicsEllipseItem(self._orig_point.x(), self._orig_point.y(),
  84. width, height)
  85. ellipse.setPen(QPen(Qt.black, 2, Qt.SolidLine))
  86. self.addItem(ellipse)
  87. elif self._mode == Mode.FREE:
  88. line = QGraphicsLineItem(self._orig_point.x(), self._orig_point.y(),
  89. end_point.x(), end_point.y())
  90. line.setPen(QPen(Qt.black, 2, Qt.SolidLine))
  91. self.addItem(line)
  92. # group lines together
  93. self._free_draw_lines.append(line)
  94. group = self.createItemGroup(self._free_draw_lines)
  95. group.setFlag(QGraphicsItem.ItemIsSelectable, False)
  96. group.setFlag(QGraphicsItem.ItemIsMovable, False)
  97. del self._free_draw_lines[:]
  98. else:
  99. pass
  100. self._cur_drawing = False
  101. else:
  102. if self._mode == Mode.SELECT:
  103. item = self.itemAt(event.scenePos(), QTransform())
  104. if not item:
  105. return
  106. for item in self.items():
  107. try:
  108. item.__hack__() # hack to check if item is of edge type
  109. item.redraw()
  110. except AttributeError:
  111. continue
  112. elif self._mode == Mode.CONNECT:
  113. item = self.itemAt(event.scenePos(), QTransform())
  114. if not item:
  115. return
  116. self.draw_edge(self._from_item, item, is_new=True)
  117. else:
  118. pass
  119. QGraphicsScene.mouseReleaseEvent(self, event)
  120. def keyPressEvent(self, event):
  121. if not self._mode == Mode.SELECT:
  122. return
  123. # "del" deletes all selected items
  124. if event.key() == Qt.Key_Delete:
  125. self._handle_keypress_delete(self.selectedItems())
  126. # "G" groups selected items
  127. elif event.key() == Qt.Key_G:
  128. selected = self.selectedItems()
  129. self.clearSelection()
  130. group = self.createItemGroup(selected)
  131. bb_rect = QGraphicsRectItem(group.boundingRect())
  132. bb_rect.setPen(QPen(Qt.gray, 1, Qt.DashLine))
  133. group.addToGroup(bb_rect)
  134. group.setFlag(QGraphicsItem.ItemIsSelectable, True)
  135. group.setFlag(QGraphicsItem.ItemIsMovable, True)
  136. # "T" lets user type selected element
  137. elif event.key() == Qt.Key_T:
  138. # exactly one element that is a group must be selected
  139. selected = self.selectedItems()
  140. if len(selected) != 1:
  141. return
  142. item = selected[0]
  143. if isinstance(item, QGraphicsItemGroup):
  144. self._handle_keypress_type_on_group(item)
  145. elif isinstance(item, GraphicsNodeItem):
  146. self._handle_keypress_type_on_node(item)
  147. else:
  148. print("Cannot type element {}".format(item))
  149. else:
  150. QGraphicsScene.keyPressEvent(self, event)
  151. def _handle_keypress_type_on_node(self, item):
  152. # type a node = retype it
  153. node_type, ok = QInputDialog.getText(self._parent, "Retype node", "New type", text=item.text())
  154. if not ok:
  155. # user canceled
  156. return
  157. if node_type:
  158. print("Reyping item {} to type {}".format(item, node_type))
  159. if node_type in mvops.get_available_types():
  160. print("Error: Already such a type: {}".format(node_type))
  161. return
  162. # local or global retype?
  163. scope, ok = QInputDialog.getItem(self._parent, "Select scope", "Scope", ["Local", "Global"])
  164. if not ok:
  165. return
  166. retype_handler = NodeRetype()
  167. if scope == "Global":
  168. retype_handler.execute(self._cur_model, item.node_id, node_type, local=False)
  169. else:
  170. retype_handler.execute(self._cur_model, item.node_id, node_type, local=True)
  171. # rename on screen
  172. for node_item in self.items():
  173. if not isinstance(node_item, GraphicsNodeItem):
  174. continue
  175. if item.text() == node_item.text():
  176. node_item.setText(node_type)
  177. # update list widget
  178. self._parent.populate_types()
  179. def _handle_keypress_type_on_group(self, group):
  180. # type the selected group
  181. # get the type from the user
  182. node_type, ok = QInputDialog.getText(self._parent, "Type node", "Enter type")
  183. if not ok:
  184. # user canceled
  185. return
  186. if node_type:
  187. print("Typing item {} to type {}".format(group, node_type))
  188. if node_type in mvops.get_available_types():
  189. print("Error: Already such a type: {}".format(node_type))
  190. return
  191. # perform add local or global?
  192. scope, ok = QInputDialog.getItem(self._parent, "Select scope", "Scope", ["Local", "Global"])
  193. if not ok:
  194. return
  195. # add the node to the model
  196. add_handler = NodeAdd()
  197. if scope == "Global":
  198. add_handler.execute(self._cur_model, node_type, local=False)
  199. # Hack: Get node id of newly added node in current model
  200. nodeid = all_nodes_with_type(self._cur_model, node_type)[0]
  201. else:
  202. add_handler.execute(self._cur_model, node_type, local=True)
  203. nodeid = add_handler.get_node_id()
  204. # update view
  205. nodeitem = GraphicsNodeItem(nodeid)
  206. nodeitem.setText(node_type)
  207. nodeitem.setPos(group.x(), group.y())
  208. nodeitem.setFlag(QGraphicsItem.ItemIsSelectable, True)
  209. nodeitem.setFlag(QGraphicsItem.ItemIsMovable, True)
  210. self.removeItem(group)
  211. self.addItem(nodeitem)
  212. self._parent.populate_types()
  213. def _handle_keypress_delete(self, selected):
  214. del_hander = NodeDelete()
  215. for item in selected:
  216. # only delete nodes, edges are taken care of
  217. if not isinstance(item, GraphicsNodeItem):
  218. continue
  219. # when deleting a node, local or global?
  220. scope, ok = QInputDialog.getItem(self._parent, "Select scope", "Scope", ["Local", "Global"])
  221. if not ok:
  222. return
  223. if scope == "Global":
  224. # global language evolution, so delete node with same type everywhere
  225. del_hander.execute(self._cur_model, item.node_id, local=False, check_if_last=False)
  226. else:
  227. # just local, delete from this model only
  228. del_hander.execute(self._cur_model, item.node_id, local=True, check_if_last=True)
  229. # in view, delete edges that were connected to this node as well
  230. for edge in self.items():
  231. if not isinstance(edge, GraphicsEdgeItem):
  232. continue
  233. if edge.from_item.node_id == item.node_id or edge.to_item.node_id == item.node_id:
  234. self.removeItem(edge)
  235. self.removeItem(item)
  236. # repopulate available types in view since they might have changed
  237. self._parent.populate_types()