class EntityItem(QGraphicsRectItem): """Base class for ObjectItem and RelationshipItem.""" def __init__(self, spine_db_editor, x, y, extent, db_map_entity_id=None): """Initializes item Args: spine_db_editor (SpineDBEditor): 'owner' x (float): x-coordinate of central point y (float): y-coordinate of central point extent (int): Preferred extent db_map_entity_id (tuple): db_map, entity id """ super().__init__() self._spine_db_editor = spine_db_editor self.db_mngr = spine_db_editor.db_mngr self.db_map_entity_id = db_map_entity_id self.arc_items = list() self._extent = extent self.setRect(-0.5 * self._extent, -0.5 * self._extent, self._extent, self._extent) self.setPen(Qt.NoPen) self._svg_item = QGraphicsSvgItem(self) self._svg_item.setCacheMode( QGraphicsItem.CacheMode.NoCache ) # Needed for the exported pdf to be vector self.refresh_icon() self.setPos(x, y) self._moved_on_scene = False self._bg = None self._bg_brush = Qt.NoBrush self._init_bg() self._bg.setFlag(QGraphicsItem.ItemStacksBehindParent, enabled=True) self.setZValue(0) self.setFlag(QGraphicsItem.ItemIsSelectable, enabled=True) self.setFlag(QGraphicsItem.ItemIsMovable, enabled=True) self.setFlag(QGraphicsItem.ItemIgnoresTransformations, enabled=True) self.setFlag(QGraphicsItem.ItemSendsScenePositionChanges, enabled=True) self.setAcceptHoverEvents(True) self.setCursor(Qt.ArrowCursor) self.setToolTip(self._make_tool_tip()) def _make_tool_tip(self): raise NotImplementedError() @property def entity_type(self): raise NotImplementedError() @property def entity_name(self): return self.db_mngr.get_item(self.db_map, self.entity_type, self.entity_id)["name"] @property def entity_class_type(self): return { "relationship": "relationship_class", "object": "object_class" }[self.entity_type] @property def entity_class_id(self): return self.db_mngr.get_item(self.db_map, self.entity_type, self.entity_id)["class_id"] @property def entity_class_name(self): return self.db_mngr.get_item(self.db_map, self.entity_class_type, self.entity_class_id)["name"] @property def db_map(self): return self.db_map_entity_id[0] @property def entity_id(self): return self.db_map_entity_id[1] @property def first_db_map(self): return self.db_map @property def display_data(self): return self.entity_name @property def display_database(self): return self.db_map.codename @property def db_maps(self): return (self.db_map, ) def db_map_data(self, _db_map): # NOTE: Needed by EditObjectsDialog and EditRelationshipsDialog return self.db_mngr.get_item(self.db_map, self.entity_type, self.entity_id) def db_map_id(self, _db_map): # NOTE: Needed by EditObjectsDialog and EditRelationshipsDialog return self.entity_id def boundingRect(self): return super().boundingRect() | self.childrenBoundingRect() def moveBy(self, dx, dy): super().moveBy(dx, dy) self.update_arcs_line() def _init_bg(self): self._bg = QGraphicsRectItem(self.boundingRect(), self) self._bg.setPen(Qt.NoPen) def refresh_icon(self): """Refreshes the icon.""" renderer = self.db_mngr.entity_class_renderer(self.db_map, self.entity_class_type, self.entity_class_id) self._set_renderer(renderer) def _set_renderer(self, renderer): self._svg_item.setSharedRenderer(renderer) size = renderer.defaultSize() scale = self._extent / max(size.width(), size.height()) self._svg_item.setScale(scale) self._svg_item.setPos(self.rect().center() - 0.5 * scale * QPointF(size.width(), size.height())) def shape(self): """Returns a shape containing the entire bounding rect, to work better with icon transparency.""" path = QPainterPath() path.setFillRule(Qt.WindingFill) path.addRect(self._bg.boundingRect()) return path def paint(self, painter, option, widget=None): """Shows or hides the selection halo.""" if option.state & (QStyle.State_Selected): self._paint_as_selected() option.state &= ~QStyle.State_Selected else: self._paint_as_deselected() super().paint(painter, option, widget) def _paint_as_selected(self): self._bg.setBrush(QGuiApplication.palette().highlight()) def _paint_as_deselected(self): self._bg.setBrush(self._bg_brush) def add_arc_item(self, arc_item): """Adds an item to the list of arcs. Args: arc_item (ArcItem) """ self.arc_items.append(arc_item) arc_item.update_line() def apply_zoom(self, factor): """Applies zoom. Args: factor (float): The zoom factor. """ if factor > 1: factor = 1 self.setScale(factor) def apply_rotation(self, angle, center): """Applies rotation. Args: angle (float): The angle in degrees. center (QPoint): Rotates around this point. """ line = QLineF(center, self.pos()) line.setAngle(line.angle() + angle) self.setPos(line.p2()) self.update_arcs_line() def block_move_by(self, dx, dy): self.moveBy(dx, dy) def mouseMoveEvent(self, event): """Moves the item and all connected arcs. Args: event (QGraphicsSceneMouseEvent) """ if event.buttons() & Qt.LeftButton == 0: super().mouseMoveEvent(event) return move_by = event.scenePos() - event.lastScenePos() # Move selected items together for item in self.scene().selectedItems(): if isinstance(item, (EntityItem)): item.block_move_by(move_by.x(), move_by.y()) def update_arcs_line(self): """Moves arc items.""" for item in self.arc_items: item.update_line() def itemChange(self, change, value): """ Keeps track of item's movements on the scene. Args: change (GraphicsItemChange): a flag signalling the type of the change value: a value related to the change Returns: the same value given as input """ if change == QGraphicsItem.ItemScenePositionHasChanged: self._moved_on_scene = True return value def set_all_visible(self, on): """Sets visibility status for this item and all arc items. Args: on (bool) """ for item in self.arc_items: item.setVisible(on) self.setVisible(on) def _make_menu(self): return self._spine_db_editor.ui.graphicsView.make_items_menu() def contextMenuEvent(self, e): """Shows context menu. Args: e (QGraphicsSceneMouseEvent): Mouse event """ e.accept() if not self.isSelected() and not e.modifiers() & Qt.ControlModifier: self.scene().clearSelection() self.setSelected(True) menu = self._make_menu() menu.popup(e.screenPos())
class EntityItem(QGraphicsPixmapItem): """Base class for ObjectItem and RelationshipItem.""" def __init__(self, data_store_form, x, y, extent, entity_id=None): """Initializes item Args: data_store_form (DataStoreForm): 'owner' x (float): x-coordinate of central point y (float): y-coordinate of central point extent (int): Preferred extent entity_id (int): The entity id """ super().__init__() self._data_store_form = data_store_form self.db_mngr = data_store_form.db_mngr self.db_map = data_store_form.db_map self.entity_id = entity_id self.arc_items = list() self._extent = extent self.refresh_icon() self.setPos(x, y) rect = self.boundingRect() self.setOffset(-rect.width() / 2, -rect.height() / 2) self._moved_on_scene = False self._bg = None self._bg_brush = Qt.NoBrush self._init_bg() self._bg.setFlag(QGraphicsItem.ItemStacksBehindParent, enabled=True) self.setZValue(0) self.setFlag(QGraphicsItem.ItemIsSelectable, enabled=True) self.setFlag(QGraphicsItem.ItemIsMovable, enabled=True) self.setFlag(QGraphicsItem.ItemIgnoresTransformations, enabled=True) self.setFlag(QGraphicsItem.ItemSendsScenePositionChanges, enabled=True) self.setAcceptHoverEvents(True) self.setCursor(Qt.ArrowCursor) @property def entity_type(self): raise NotImplementedError() @property def entity_name(self): return self.db_mngr.get_item(self.db_map, self.entity_type, self.entity_id)["name"] @property def entity_class_type(self): return { "relationship": "relationship class", "object": "object class" }[self.entity_type] @property def entity_class_id(self): return self.db_mngr.get_item(self.db_map, self.entity_type, self.entity_id)["class_id"] @property def entity_class_name(self): return self.db_mngr.get_item(self.db_map, self.entity_class_type, self.entity_class_id)["name"] @property def first_db_map(self): return self.db_map @property def display_data(self): return self.entity_name @property def display_database(self): return self.db_map.codename @property def db_maps(self): return (self.db_map, ) def db_map_data(self, _db_map): return self.db_mngr.get_item(self.db_map, self.entity_type, self.entity_id) def db_map_id(self, _db_map): return self.entity_id def boundingRect(self): return super().boundingRect() | self.childrenBoundingRect() def moveBy(self, dx, dy): super().moveBy(dx, dy) self.update_arcs_line() def _init_bg(self): self._bg = QGraphicsRectItem(self.boundingRect(), self) self._bg.setPen(Qt.NoPen) def refresh_icon(self): """Refreshes the icon.""" pixmap = self.db_mngr.entity_class_icon( self.db_map, self.entity_class_type, self.entity_class_id).pixmap(self._extent) self.setPixmap(pixmap) def shape(self): """Returns a shape containing the entire bounding rect, to work better with icon transparency.""" path = QPainterPath() path.setFillRule(Qt.WindingFill) path.addRect(self._bg.boundingRect()) return path def paint(self, painter, option, widget=None): """Shows or hides the selection halo.""" if option.state & (QStyle.State_Selected): self._paint_as_selected() option.state &= ~QStyle.State_Selected else: self._paint_as_deselected() super().paint(painter, option, widget) def _paint_as_selected(self): self._bg.setBrush(QGuiApplication.palette().highlight()) def _paint_as_deselected(self): self._bg.setBrush(self._bg_brush) def add_arc_item(self, arc_item): """Adds an item to the list of arcs. Args: arc_item (ArcItem) """ self.arc_items.append(arc_item) arc_item.update_line() def apply_zoom(self, factor): """Applies zoom. Args: factor (float): The zoom factor. """ if factor > 1: factor = 1 self.setScale(factor) def apply_rotation(self, angle, center): """Applies rotation. Args: angle (float): The angle in degrees. center (QPoint): Rotates around this point. """ line = QLineF(center, self.pos()) line.setAngle(line.angle() + angle) self.setPos(line.p2()) self.update_arcs_line() def block_move_by(self, dx, dy): self.moveBy(dx, dy) def mouseMoveEvent(self, event): """Moves the item and all connected arcs. Also checks for a merge target and sets an appropriate mouse cursor. Args: event (QGraphicsSceneMouseEvent) """ if event.buttons() & Qt.LeftButton == 0: super().mouseMoveEvent(event) return move_by = event.scenePos() - event.lastScenePos() # Move selected items together for item in self.scene().selectedItems(): if isinstance(item, (EntityItem)): item.block_move_by(move_by.x(), move_by.y()) def update_arcs_line(self): """Moves arc items.""" for item in self.arc_items: item.update_line() def itemChange(self, change, value): """ Keeps track of item's movements on the scene. Args: change (GraphicsItemChange): a flag signalling the type of the change value: a value related to the change Returns: the same value given as input """ if change == QGraphicsItem.ItemScenePositionHasChanged: self._moved_on_scene = True return value def set_all_visible(self, on): """Sets visibility status for this item and all arc items. Args: on (bool) """ for item in self.arc_items: item.setVisible(on) self.setVisible(on) def _make_menu(self): menu = QMenu(self._data_store_form) menu.addAction(self._data_store_form.ui.actionSave_positions) menu.addAction(self._data_store_form.ui.actionClear_positions) menu.addSeparator() menu.addAction(self._data_store_form.ui.actionHide_selected) menu.addAction(self._data_store_form.ui.actionPrune_selected_entities) menu.addAction(self._data_store_form.ui.actionPrune_selected_classes) menu.addSeparator() menu.addAction(self._data_store_form.ui.actionEdit_selected) menu.addAction(self._data_store_form.ui.actionRemove_selected) return menu def contextMenuEvent(self, e): """Shows context menu. Args: e (QGraphicsSceneMouseEvent): Mouse event """ e.accept() if not self.isSelected() and not e.modifiers() & Qt.ControlModifier: self.scene().clearSelection() self.setSelected(True) self._data_store_form._handle_menu_graph_about_to_show() self._menu.popup(e.screenPos())