123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473 |
- from enum import Enum
- from PyQt5.QtWidgets import QGraphicsScene, QGraphicsItem, QGraphicsLineItem, QGraphicsRectItem, \
- QGraphicsEllipseItem, QInputDialog, QTableWidgetItem
- from PyQt5.Qt import Qt, QPointF, QPen, QTransform, QApplication
- from sketchUI.graphics_edge_item import GraphicsEdgeItem
- from sketchUI.graphics_node_item import GraphicsNodeItem, IconType
- from sketchUI.graphics_sketch_group import SketchGroup
- from sketchUI.graphics_sketch_line import SketchedLineItem
- from sketchUI import mvops
- from evolution.node_ops import NodeAdd, NodeDelete, NodeRetype
- from evolution.attribute_ops import AttributeAdd
- import commons
- class Mode(Enum):
- SELECT = 0
- CONNECT = 1
- LINE = 2
- RECT = 3
- CIRCLE = 4
- FREE = 5
- class SketchScene(QGraphicsScene):
- def __init__(self, model, parent):
- QGraphicsScene.__init__(self)
- self._cur_drawing = False
- self._mode = None # set from mainwindow on start
- self._connect_from_item = None # mouse pressed on this item if in connect mode
- self._cur_model = model
- self._orig_point = QPointF()
- self._free_draw_lines = []
- self._parent = parent
- def set_mode(self, mode):
- self._mode = mode
- def _in_draw_mode(self):
- # are we in one of the draw modes? (rect, line, free, ...)
- if self._mode == Mode.CONNECT or self._mode == Mode.SELECT:
- return False
- return True
- def draw_edge(self, from_item, to_item, is_new, edge_id=None):
- # type: (GraphicsNodeItem, GraphicsNodeItem, bool, str) -> None
- # draw an edge between two items. If is_new, also add it to the model
- line = GraphicsEdgeItem(from_item, to_item, edge_id)
- line.setFlag(QGraphicsItem.ItemIsMovable, False)
- line.setFlag(QGraphicsItem.ItemIsSelectable, False)
- self.addItem(line)
- line.redraw()
- if is_new:
- edge_id = mvops.add_edge(self._cur_model, from_item.node_id, to_item.node_id)
- line.edge_id = edge_id
- def mousePressEvent(self, event):
- if event.button() == Qt.LeftButton and self._in_draw_mode():
- # start drawing, save click point
- self._orig_point = event.scenePos()
- self._cur_drawing = True
- elif event.button() == Qt.LeftButton and self._mode == Mode.CONNECT:
- # store clicked item to connect it later
- item = self.itemAt(event.scenePos(), QTransform())
- if not item or not isinstance(item, GraphicsNodeItem):
- return
- else:
- self._connect_from_item = item
- elif event.button() == Qt.LeftButton and self._mode == Mode.SELECT:
- item = self.itemAt(event.scenePos(), QTransform())
- if not item:
- return
- elif isinstance(item, GraphicsNodeItem):
- self._parent.plainTextEdit.appendPlainText("Selected node {}:{}".format(item.node_id, item.get_type()))
- self.highlight_node(item.node_id, Qt.blue)
- # load attributes for selected node
- self._parent.tableWidget.setRowCount(0)
- self._parent.tableWidget.blockSignals(True)
- attrs = commons.get_attributes_of_node(self._cur_model, item.node_id)
- for attr in attrs:
- table_item_key = QTableWidgetItem(attr.key)
- table_item_key.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
- table_item_val = QTableWidgetItem(attr.val)
- cur_row_cnt = self._parent.tableWidget.rowCount()
- self._parent.tableWidget.insertRow(cur_row_cnt)
- self._parent.tableWidget.setItem(cur_row_cnt, 0, table_item_key)
- self._parent.tableWidget.setItem(cur_row_cnt, 1, table_item_val)
- self._parent.tableWidget.blockSignals(False)
- elif isinstance(item, GraphicsEdgeItem):
- self._parent.plainTextEdit.appendPlainText("Selected edge")
- else:
- pass
- QGraphicsScene.mousePressEvent(self, event)
- def mouseMoveEvent(self, event):
- # if in freehand mode, draw lines from move movement
- if self._mode == Mode.FREE and self._cur_drawing:
- pt = event.scenePos()
- line = QGraphicsLineItem(self._orig_point.x(), self._orig_point.y(), pt.x(), pt.y())
- line.setPen(QPen(Qt.black, 2, Qt.SolidLine))
- self.addItem(line)
- self._orig_point = pt
- self._free_draw_lines.append(line)
- QGraphicsScene.mouseMoveEvent(self, event)
- def mouseReleaseEvent(self, event):
- if self._cur_drawing:
- end_point = event.scenePos()
- if self._mode == Mode.LINE:
- line = SketchedLineItem(self._orig_point.x(), self._orig_point.y(),
- end_point.x(), end_point.y())
- line.setPen(QPen(Qt.black, 2, Qt.SolidLine))
- self.addItem(line)
- elif self._mode == Mode.RECT:
- width = abs(end_point.x() - self._orig_point.x())
- height = abs(end_point.y() - self._orig_point.y())
- rect = QGraphicsRectItem(self._orig_point.x(), self._orig_point.y(),
- width, height)
- rect.setPen(QPen(Qt.black, 2, Qt.SolidLine))
- self.addItem(rect)
- elif self._mode == Mode.CIRCLE:
- width = abs(end_point.x() - self._orig_point.x())
- height = abs(end_point.y() - self._orig_point.y())
- ellipse = QGraphicsEllipseItem(self._orig_point.x(), self._orig_point.y(),
- width, height)
- ellipse.setPen(QPen(Qt.black, 2, Qt.SolidLine))
- self.addItem(ellipse)
- elif self._mode == Mode.FREE:
- line = QGraphicsLineItem(self._orig_point.x(), self._orig_point.y(),
- end_point.x(), end_point.y())
- line.setPen(QPen(Qt.black, 2, Qt.SolidLine))
- self.addItem(line)
- # group lines together
- self._free_draw_lines.append(line)
- group = self.createItemGroup(self._free_draw_lines)
- group.setFlag(QGraphicsItem.ItemIsSelectable, False)
- group.setFlag(QGraphicsItem.ItemIsMovable, False)
- del self._free_draw_lines[:]
- else:
- pass
- self._cur_drawing = False
- else:
- if self._mode == Mode.SELECT:
- item = self.itemAt(event.scenePos(), QTransform())
- if not item:
- return
- for item in self.items():
- if isinstance(item, GraphicsEdgeItem):
- item.redraw()
- elif self._mode == Mode.CONNECT:
- item = self.itemAt(event.scenePos(), QTransform())
- if not item or not isinstance(item, GraphicsNodeItem):
- return
- self.draw_edge(self._connect_from_item, item, is_new=True)
- else:
- pass
- QGraphicsScene.mouseReleaseEvent(self, event)
- def keyPressEvent(self, event):
- if not self._mode == Mode.SELECT:
- return
- # "del" deletes all selected items
- if event.key() == Qt.Key_Delete:
- self._handle_keypress_delete(self.selectedItems())
- # "G" groups selected items
- elif event.key() == Qt.Key_G:
- selected = self.selectedItems()
- self.clearSelection()
- group = SketchGroup()
- for item in selected:
- group.addToGroup(item)
- bb_rect = QGraphicsRectItem(group.boundingRect())
- bb_rect.setData(0, "groupBBox") # identifier for "does not belong to the actual sketch"
- bb_rect.setPen(QPen(Qt.gray, 1, Qt.DashLine))
- group.addToGroup(bb_rect)
- self.addItem(group)
- group.setFlag(QGraphicsItem.ItemIsSelectable, True)
- group.setFlag(QGraphicsItem.ItemIsMovable, True)
- # "T" lets user type selected element
- elif event.key() == Qt.Key_T:
- # exactly one element that is a group must be selected
- selected = self.selectedItems()
- if len(selected) != 1:
- return
- item = selected[0]
- if isinstance(item, SketchGroup):
- self._handle_keypress_type_on_group(item)
- elif isinstance(item, GraphicsNodeItem):
- self._handle_keypress_type_on_node(item)
- else:
- self._parent.plainTextEdit.appendPlainText("Error: Cannot type element {}".format(item))
- # "A" attributes a node
- elif event.key() == Qt.Key_A:
- self._handle_keypress_attribute(self.selectedItems())
- else:
- QGraphicsScene.keyPressEvent(self, event)
- def _handle_keypress_type_on_node(self, item):
- # type: (GraphicsNodeItem) -> None
- """
- type an already typed node = retype it
- """
- old_type = item.get_type()
- node_type, ok = QInputDialog.getText(self._parent, "Retype node", "New type", text=item.get_type())
- if not ok or not node_type:
- # user canceled or node type empty
- return
- if node_type in commons.get_available_types():
- self._parent.plainTextEdit.appendPlainText("Error: Already such a type: {}".format(node_type))
- return
- # local or global retype?
- scope, ok = QInputDialog.getItem(self._parent, "Select scope", "Scope", ["Local", "Global"])
- if not ok:
- return
- self._parent.plainTextEdit.appendPlainText("Performing retype of node {}".format(node_type))
- QApplication.setOverrideCursor(Qt.WaitCursor)
- retype_handler = NodeRetype()
- if scope == "Global":
- retype_handler.execute(self._cur_model, item.node_id, node_type, local=False)
- else:
- retype_handler.execute(self._cur_model, item.node_id, node_type, local=True)
- # rename on screen
- if scope == "Global":
- for node_item in self.items():
- if not isinstance(node_item, GraphicsNodeItem):
- continue
- if node_item.get_type() == old_type:
- node_item.set_type(node_type)
- else:
- item.set_type(node_type)
- # update list widget
- self._parent.populate_types()
- QApplication.restoreOverrideCursor()
- def _handle_keypress_type_on_group(self, group):
- # type: (SketchGroup) -> None
- """
- type the selected group = make a real node out of it and store it in the model
- also capture its concrete syntax and store it in the modelverse
- """
- # get the type from the user
- node_type, ok = QInputDialog.getText(self._parent, "Type node", "Enter type")
- if not ok or not node_type:
- # user canceled or empty type string
- return
- if node_type in commons.get_available_types():
- self._parent.plainTextEdit.appendPlainText("Error: Already such a type: {}".format(node_type))
- return
- # perform add local or global?
- scope, ok = QInputDialog.getItem(self._parent, "Select scope", "Scope", ["Local", "Global"])
- if not ok:
- return
- self._parent.plainTextEdit.appendPlainText("Typing group to type {}".format(node_type))
- QApplication.setOverrideCursor(Qt.WaitCursor)
- # add the node to the model
- add_handler = NodeAdd()
- if scope == "Global":
- add_handler.execute(self._cur_model, node_type, local=False)
- else:
- add_handler.execute(self._cur_model, node_type, local=True)
- # Get node id of newly added node in current model
- nodeid = commons.all_nodes_with_type(self._cur_model, node_type)[0]
- self._parent.plainTextEdit.appendPlainText("Capturing concrete syntax of group ...")
- self._parent.plainTextEdit.repaint()
- # create concrete syntax model for the sketched elements
- csm = mvops.new_concrete_syntax_model(node_type, IconType.PRIMITIVE)
- if not csm:
- self._parent.plainTextEdit.appendPlainText("Error: Concrete syntax for type {} already exists".format(node_type))
- return
- # check if we need to scale the group items down to 100x100 first
- group_brect = group.boundingRect()
- need_scale = False
- scale_factor = 1.0
- if group_brect.width() > 100 or group_brect.height() > 100:
- need_scale = True
- scale_factor = 100.0 / max(group_brect.width(), group_brect.height())
- # populate CSM with sketched elements
- for item in group.childItems():
- if item.data(0) == "groupBBox":
- # just the bounding box from the group, ignore
- continue
- if isinstance(item, QGraphicsRectItem):
- rect = group.get_item_coord_relative(item)
- if need_scale:
- new_top_left = rect.topLeft() * scale_factor
- new_width = rect.width() * scale_factor
- new_height = rect.height() * scale_factor
- rect.setTopLeft(new_top_left)
- rect.setWidth(new_width)
- rect.setHeight(new_height)
- mvops.add_rect_to_cs(csm, rect)
- elif isinstance(item, QGraphicsEllipseItem):
- rect = group.get_item_coord_relative(item)
- if need_scale:
- new_top_left = rect.topLeft() * scale_factor
- new_width = rect.width() * scale_factor
- new_height = rect.height() * scale_factor
- rect.setTopLeft(new_top_left)
- rect.setWidth(new_width)
- rect.setHeight(new_height)
- mvops.add_rect_to_cs(csm, rect)
- elif isinstance(item, SketchedLineItem):
- p1, p2 = group.get_item_coord_relative(item)
- if need_scale:
- p1 *= scale_factor
- p2 *= scale_factor
- mvops.add_line_to_cs(csm, p1, p2)
- else:
- print("Dont know how to capture CS of item {}".format(item))
- # update view: replace group by actual node item with newly populated CS
- csm_content = mvops.get_consyn_of(node_type)
- nodeitem = GraphicsNodeItem(nodeid, node_type, csm_content)
- nodeitem.setPos(group.scenePos())
- nodeitem.setFlag(QGraphicsItem.ItemIsSelectable, True)
- nodeitem.setFlag(QGraphicsItem.ItemIsMovable, True)
- self.removeItem(group)
- self.addItem(nodeitem)
- self._parent.populate_types()
- QApplication.restoreOverrideCursor()
- self._parent.plainTextEdit.appendPlainText("OK")
- def _handle_keypress_delete(self, selected):
- if len(selected) == 1 and isinstance(selected[0], GraphicsEdgeItem):
- # an edge is to be deleted
- # TODO: use evolution code
- self._parent.plainTextEdit.appendPlainText("Deleting edge")
- edge = selected[0]
- mvops.delete_node(self._cur_model, edge.edge_id)
- self.removeItem(edge)
- return
- elif len(selected) == 1 and isinstance(selected[0], GraphicsNodeItem):
- del_hander = NodeDelete()
- node = selected[0]
- # a node is to be deleted
- self._parent.plainTextEdit.appendPlainText("Deleting node of type {}".format(node.get_type()))
- # when deleting a node, local or global?
- scope, ok = QInputDialog.getItem(self._parent, "Select scope", "Scope", ["Local", "Global"])
- if not ok:
- return
- QApplication.setOverrideCursor(Qt.WaitCursor)
- if scope == "Global":
- # global language evolution, so delete node with same type everywhere
- del_hander.execute(self._cur_model, node.node_id, local=False, check_if_last=False)
- # also delete its associated CS model
- mvops.del_concrete_syntax_model(node.get_type())
- # delete all nodes of type from view
- for item in self.items():
- if not isinstance(item, GraphicsNodeItem):
- continue
- if item.get_type() == node.get_type():
- self.removeItem(item)
- else:
- # just local, delete from this model only
- del_hander.execute(self._cur_model, node.node_id, local=True, check_if_last=True)
- if del_hander.was_last():
- # it was the last node in the language, so delete its CS model as well
- mvops.del_concrete_syntax_model(node.get_type())
- # delete this node from view
- self.removeItem(node)
- # in view, delete edges that were connected to this node as well
- # modelverse does this on its own so do not delete edges explicitly here
- if scope == "Local":
- for edge in self.items():
- if not isinstance(edge, GraphicsEdgeItem):
- continue
- if edge.from_item.node_id == node.node_id or edge.to_item.node_id == node.node_id:
- self.removeItem(edge)
- else:
- # have to remove all edges connected to this type in model
- for edge in self.items():
- if not isinstance(edge, GraphicsEdgeItem):
- continue
- if edge.from_item.get_type() == node.get_type() or edge.to_item.get_type() == node.get_type():
- self.removeItem(edge)
- # repopulate available types just in case
- self._parent.populate_types()
- else:
- if not any(isinstance(x, GraphicsNodeItem) for x in selected) and not any(isinstance(x, GraphicsEdgeItem) for x in selected):
- # neither NodeItem nor EdgeItem in selected -> untyped sketch item, simply remove
- for obj in selected:
- self.removeItem(obj)
- QApplication.restoreOverrideCursor()
- def _handle_keypress_attribute(self, selected):
- if not len(selected) == 1:
- return
- item = selected[0]
- if not isinstance(item, GraphicsNodeItem):
- return
- # ask user for key value
- key, ok = QInputDialog.getText(self._parent, "New attribute", "Key value")
- if not ok or not key:
- return
- # check if key value already used for this node
- attrs = commons.get_attributes_of_node(self._cur_model, item.node_id)
- for attr in attrs:
- if attr.key == key:
- self._parent.plainTextEdit.appendPlainText("Error: Already such a key for the node: {}".format(key))
- return
- # ask of global or local add attribute
- scope, ok = QInputDialog.getItem(self._parent, "Select scope", "Scope", ["Local", "Global"])
- if not ok:
- return
- add_handler = AttributeAdd()
- if scope == "Global":
- add_handler.execute(self._cur_model, item.node_id, key, "unknown", local=False)
- else:
- add_handler.execute(self._cur_model, item.node_id, key, "unknown", local=True)
- # add to view
- self._parent.add_new_attribute(key, "unknown")
- def highlight_node(self, node_id, color):
- for item in self.items():
- if not isinstance(item, GraphicsNodeItem):
- continue
- if item.node_id == node_id:
- item.set_highlighted(True, color)
- else:
- item.set_highlighted(False, color)
- item.update(item.boundingRect())
|