コード例 #1
0
ファイル: minae.py プロジェクト: adam-britton/minae-chess-gui
 def __add_squares(self):
     """Adds initial squares to the board view."""
     for file in 'abcdefgh':
         for rank in '12345678':
             (x, y) = self.__pos_to_x_y(file + rank)
             square = QGraphicsSvgItem(
                 self.IMAGES['l'] if self.
                 __is_light_square(file + rank) else self.IMAGES['d'])
             square.setPos(x, y)
             self.addItem(square)
コード例 #2
0
ファイル: minae.py プロジェクト: adam-britton/minae-chess-gui
    def highlight_squares(self, squares):
        """
        Highlighted a list of squares on the board.

        :param squares: List of squares to highlight.
        """
        for item in self.highlighted_square_items:
            self.removeItem(item)
        self.highlighted_square_items = []

        for pos in squares:
            (x, y) = self.__pos_to_x_y(pos)
            highlighted_square_item = QGraphicsSvgItem(self.IMAGES['h'])
            highlighted_square_item.setPos(x, y)
            self.addItem(highlighted_square_item)
            self.highlighted_square_items += [highlighted_square_item]
コード例 #3
0
ファイル: minae.py プロジェクト: adam-britton/minae-chess-gui
    def set_position(self, populated_squares):
        """See BoardView.set_position()."""
        for item in self.highlighted_square_items:
            self.removeItem(item)
        self.highlighted_square_items = []

        for item in self.piece_items:
            self.removeItem(item)
        self.piece_items = []

        for pos in populated_squares:
            (x, y) = self.__pos_to_x_y(pos)
            piece = QGraphicsSvgItem(self.IMAGES[populated_squares[pos]])
            piece.setPos(x, y)
            self.addItem(piece)
            self.piece_items += [piece]
コード例 #4
0
    def get_q_svg_scene_item(self, parent=None):

        # TODO this is overkill, recreating the SVG Widget way too often.
        self.create(
            config_controller.box_size[0] * self.properties.w,
            config_controller.box_size[1] * self.properties.h,
        )

        q_graphics_svg_item = QGraphicsSvgItem()
        q_graphics_svg_item.parent = self
        q_graphics_svg_item.setFlags(
            QGraphicsItem.ItemIsMovable
            | QGraphicsItem.ItemIsSelectable
            | QGraphicsItem.ItemIsFocusable
            | QGraphicsItem.ItemSendsScenePositionChanges
            | QGraphicsItem.ItemSendsGeometryChanges)
        q_graphics_svg_item.uid = self.uid
        q_graphics_svg_item.setSharedRenderer(self.svg_renderer)
        q_graphics_svg_item.setPos(
            float(config_controller.box_size[0] * self.properties.x),
            float(config_controller.box_size[1] * self.properties.y),
        )
        return q_graphics_svg_item
コード例 #5
0
ファイル: link.py プロジェクト: ridalalven/Spine-Toolbox
class _LinkIcon(QGraphicsEllipseItem):
    """An icon to show over a Link."""
    def __init__(self, x, y, w, h, parent):
        super().__init__(x, y, w, h, parent)
        self._parent = parent
        color = QColor("slateblue")
        self.setBrush(qApp.palette().window())  # pylint: disable=undefined-variable
        self._text_item = QGraphicsTextItem(self)
        font = QFont('Font Awesome 5 Free Solid')
        self._text_item.setFont(font)
        self._text_item.setDefaultTextColor(color)
        self._svg_item = QGraphicsSvgItem(self)
        self._datapkg_renderer = QSvgRenderer()
        self._datapkg_renderer.load(":/icons/datapkg.svg")
        self.setFlag(QGraphicsItem.ItemIsSelectable, enabled=False)
        self._block_updates = False

    def update_icon(self):
        """Sets the icon (filter, datapkg, or none), depending on Connection state."""
        connection = self._parent.connection
        if connection.use_datapackage:
            self.setVisible(True)
            self._svg_item.setVisible(True)
            self._svg_item.setSharedRenderer(self._datapkg_renderer)
            scale = 0.8 * self.rect().width(
            ) / self._datapkg_renderer.defaultSize().width()
            self._svg_item.setScale(scale)
            self._svg_item.setPos(0, 0)
            self._svg_item.setPos(self.sceneBoundingRect().center() -
                                  self._svg_item.sceneBoundingRect().center())
            self._text_item.setVisible(False)
            return
        if connection.has_filters():
            self.setVisible(True)
            self._text_item.setVisible(True)
            self._text_item.setPlainText("\uf0b0")
            self._svg_item.setPos(0, 0)
            self._text_item.setPos(
                self.sceneBoundingRect().center() -
                self._text_item.sceneBoundingRect().center())
            self._svg_item.setVisible(False)
            return
        self.setVisible(False)
        self._text_item.setVisible(False)
        self._svg_item.setVisible(False)
コード例 #6
0
class ProjectItemIcon(QGraphicsRectItem):
    """Base class for project item icons drawn in Design View."""

    ITEM_EXTENT = 64

    def __init__(self, toolbox, icon_file, icon_color, background_color):
        """
        Args:
            toolbox (ToolboxUI): QMainWindow instance
            icon_file (str): Path to icon resource
            icon_color (QColor): Icon's color
            background_color (QColor): Background color
        """
        super().__init__()
        self._toolbox = toolbox
        self.icon_file = icon_file
        self._moved_on_scene = False
        self.previous_pos = QPointF()
        self.current_pos = QPointF()
        self.icon_group = {self}
        self.renderer = QSvgRenderer()
        self.svg_item = QGraphicsSvgItem(self)
        self.colorizer = QGraphicsColorizeEffect()
        self.setRect(
            QRectF(-self.ITEM_EXTENT / 2, -self.ITEM_EXTENT / 2,
                   self.ITEM_EXTENT, self.ITEM_EXTENT))
        self.text_font_size = 10  # point size
        # Make item name graphics item.
        self._name = ""
        self.name_item = QGraphicsSimpleTextItem(self._name, self)
        self.set_name_attributes()  # Set font, size, position, etc.
        # Make connector buttons
        self.connectors = dict(
            bottom=ConnectorButton(self, toolbox, position="bottom"),
            left=ConnectorButton(self, toolbox, position="left"),
            right=ConnectorButton(self, toolbox, position="right"),
        )
        # Make exclamation and rank icons
        self.exclamation_icon = ExclamationIcon(self)
        self.execution_icon = ExecutionIcon(self)
        self.rank_icon = RankIcon(self)
        brush = QBrush(background_color)
        self._setup(brush, icon_file, icon_color)
        shadow_effect = QGraphicsDropShadowEffect()
        shadow_effect.setOffset(1)
        shadow_effect.setEnabled(False)
        self.setGraphicsEffect(shadow_effect)

    def finalize(self, name, x, y):
        """
        Names the icon and moves it by given amount.

        Args:
            name (str): icon's name
            x (int): horizontal offset
            y (int): vertical offset
        """
        self.update_name_item(name)
        self.moveBy(x, y)

    def _setup(self, brush, svg, svg_color):
        """Setup item's attributes.

        Args:
            brush (QBrush): Used in filling the background rectangle
            svg (str): Path to SVG icon file
            svg_color (QColor): Color of SVG icon
        """
        self.setPen(QPen(Qt.black, 1, Qt.SolidLine))
        self.setBrush(brush)
        self.colorizer.setColor(svg_color)
        # Load SVG
        loading_ok = self.renderer.load(svg)
        if not loading_ok:
            self._toolbox.msg_error.emit(
                "Loading SVG icon from resource:{0} failed".format(svg))
            return
        size = self.renderer.defaultSize()
        self.svg_item.setSharedRenderer(self.renderer)
        self.svg_item.setElementId(
            "")  # guess empty string loads the whole file
        dim_max = max(size.width(), size.height())
        rect_w = self.rect().width()  # Parent rect width
        margin = 32
        self.svg_item.setScale((rect_w - margin) / dim_max)
        self.svg_item.setPos(self.rect().center() -
                             self.svg_item.sceneBoundingRect().center())
        self.svg_item.setGraphicsEffect(self.colorizer)
        self.setFlag(QGraphicsItem.ItemIsMovable, enabled=True)
        self.setFlag(QGraphicsItem.ItemIsSelectable, enabled=True)
        self.setFlag(QGraphicsItem.ItemIsFocusable, enabled=True)
        self.setFlag(QGraphicsItem.ItemSendsScenePositionChanges, enabled=True)
        self.setAcceptHoverEvents(True)
        self.setCursor(Qt.PointingHandCursor)
        # Set exclamation, execution_log, and rank icons position
        self.exclamation_icon.setPos(
            self.rect().topRight() -
            self.exclamation_icon.sceneBoundingRect().topRight())
        self.execution_icon.setPos(
            self.rect().bottomRight() -
            0.5 * self.execution_icon.sceneBoundingRect().bottomRight())
        self.rank_icon.setPos(self.rect().topLeft())

    def name(self):
        """Returns name of the item that is represented by this icon.

        Returns:
            str: icon's name
        """
        return self._name

    def update_name_item(self, new_name):
        """Set a new text to name item.

        Args:
            new_name (str): icon's name
        """
        self._name = new_name
        self.name_item.setText(new_name)
        self.set_name_attributes()

    def set_name_attributes(self):
        """Set name QGraphicsSimpleTextItem attributes (font, size, position, etc.)"""
        # Set font size and style
        font = self.name_item.font()
        font.setPointSize(self.text_font_size)
        font.setBold(True)
        self.name_item.setFont(font)
        # Set name item position (centered on top of the master icon)
        name_width = self.name_item.boundingRect().width()
        name_height = self.name_item.boundingRect().height()
        self.name_item.setPos(
            self.rect().x() + self.rect().width() / 2 - name_width / 2,
            self.rect().y() - name_height - 4)

    def conn_button(self, position="left"):
        """Returns item's connector button.

        Args:
            position (str): "left", "right" or "bottom"

        Returns:
            QWidget: connector button
        """
        return self.connectors.get(position, self.connectors["left"])

    def outgoing_links(self):
        """Collects outgoing links.

        Returns:
            list of LinkBase: outgoing links
        """
        return [
            l for conn in self.connectors.values()
            for l in conn.outgoing_links()
        ]

    def incoming_links(self):
        """Collects incoming links.

        Returns:
            list of LinkBase: outgoing links
        """
        return [
            l for conn in self.connectors.values()
            for l in conn.incoming_links()
        ]

    def run_execution_leave_animation(self, skipped):
        """
        Starts the animation associated with execution leaving the icon.

        Args:
            skipped (bool): True if project item was not actually executed.
        """
        animation_group = QParallelAnimationGroup(self._toolbox)
        for link in self.outgoing_links():
            animation_group.addAnimation(
                link.make_execution_animation(skipped))
        animation_group.start()

    def hoverEnterEvent(self, event):
        """Sets a drop shadow effect to icon when mouse enters its boundaries.

        Args:
            event (QGraphicsSceneMouseEvent): Event
        """
        self.prepareGeometryChange()
        self.graphicsEffect().setEnabled(True)
        event.accept()

    def hoverLeaveEvent(self, event):
        """Disables the drop shadow when mouse leaves icon boundaries.

        Args:
            event (QGraphicsSceneMouseEvent): Event
        """
        self.prepareGeometryChange()
        self.graphicsEffect().setEnabled(False)
        event.accept()

    def mousePressEvent(self, event):
        super().mousePressEvent(event)
        self.icon_group = set(x for x in self.scene().selectedItems()
                              if isinstance(x, ProjectItemIcon)) | {self}
        for icon in self.icon_group:
            icon.previous_pos = icon.scenePos()

    def mouseMoveEvent(self, event):
        """Moves icon(s) while the mouse button is pressed.
        Update links that are connected to selected icons.

        Args:
            event (QGraphicsSceneMouseEvent): Event
        """
        super().mouseMoveEvent(event)
        self.update_links_geometry()

    def moveBy(self, dx, dy):
        super().moveBy(dx, dy)
        self.update_links_geometry()

    def update_links_geometry(self):
        """Updates geometry of connected links to reflect this item's most recent position."""
        links = set(link for icon in self.icon_group
                    for conn in icon.connectors.values()
                    for link in conn.links)
        for link in links:
            link.update_geometry()

    def mouseReleaseEvent(self, event):
        for icon in self.icon_group:
            icon.current_pos = icon.scenePos()
        # pylint: disable=undefined-variable
        if (self.current_pos - self.previous_pos
            ).manhattanLength() > qApp.startDragDistance():
            self._toolbox.undo_stack.push(
                MoveIconCommand(self, self._toolbox.project()))
        super().mouseReleaseEvent(event)

    def notify_item_move(self):
        if self._moved_on_scene:
            self._moved_on_scene = False
            scene = self.scene()
            scene.item_move_finished.emit(self)

    def contextMenuEvent(self, event):
        """Show item context menu.

        Args:
            event (QGraphicsSceneMouseEvent): Mouse event
        """
        event.accept()
        self.scene().clearSelection()
        self.setSelected(True)
        ind = self._toolbox.project_item_model.find_item(self.name())
        self._toolbox.show_project_item_context_menu(event.screenPos(), ind)

    def itemChange(self, change, value):
        """
        Reacts to item removal and position changes.

        In particular, destroys the drop shadow effect when the items is removed from a scene
        and 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:
             Whatever super() does with the value parameter
        """
        if change == QGraphicsItem.ItemScenePositionHasChanged:
            self._moved_on_scene = True
        elif change == QGraphicsItem.GraphicsItemChange.ItemSceneChange and value is None:
            self.prepareGeometryChange()
            self.setGraphicsEffect(None)
        return super().itemChange(change, value)

    def select_item(self):
        """Update GUI to show the details of the selected item."""
        ind = self._toolbox.project_item_model.find_item(self.name())
        self._toolbox.ui.treeView_project.setCurrentIndex(ind)
コード例 #7
0
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())
コード例 #8
0
class ProjectItemIcon(QGraphicsRectItem):

    ITEM_EXTENT = 64

    def __init__(self, toolbox, x, y, project_item, icon_file, icon_color,
                 background_color):
        """Base class for project item icons drawn in Design View.

        Args:
            toolbox (ToolBoxUI): QMainWindow instance
            x (float): Icon x coordinate
            y (float): Icon y coordinate
            project_item (ProjectItem): Item
            icon_file (str): Path to icon resource
            icon_color (QColor): Icon's color
            background_color (QColor): Background color
        """
        super().__init__()
        self._toolbox = toolbox
        self._project_item = project_item
        self._moved_on_scene = False
        self._previous_pos = QPointF()
        self._current_pos = QPointF()
        self.icon_group = {self}
        self.renderer = QSvgRenderer()
        self.svg_item = QGraphicsSvgItem(self)
        self.colorizer = QGraphicsColorizeEffect()
        self.setRect(
            QRectF(x - self.ITEM_EXTENT / 2, y - self.ITEM_EXTENT / 2,
                   self.ITEM_EXTENT, self.ITEM_EXTENT))
        self.text_font_size = 10  # point size
        # Make item name graphics item.
        name = project_item.name if project_item else ""
        self.name_item = QGraphicsSimpleTextItem(name, self)
        self.set_name_attributes()  # Set font, size, position, etc.
        # Make connector buttons
        self.connectors = dict(
            bottom=ConnectorButton(self, toolbox, position="bottom"),
            left=ConnectorButton(self, toolbox, position="left"),
            right=ConnectorButton(self, toolbox, position="right"),
        )
        # Make exclamation and rank icons
        self.exclamation_icon = ExclamationIcon(self)
        self.rank_icon = RankIcon(self)
        brush = QBrush(background_color)
        self._setup(brush, icon_file, icon_color)
        self.activate()

    def activate(self):
        """Adds items to scene and setup graphics effect.
        Called in the constructor and when re-adding the item to the project in the context of undo/redo.
        """
        scene = self._toolbox.ui.graphicsView.scene()
        scene.addItem(self)
        shadow_effect = QGraphicsDropShadowEffect()
        shadow_effect.setOffset(1)
        shadow_effect.setEnabled(False)
        self.setGraphicsEffect(shadow_effect)

    def _setup(self, brush, svg, svg_color):
        """Setup item's attributes.

        Args:
            brush (QBrush): Used in filling the background rectangle
            svg (str): Path to SVG icon file
            svg_color (QColor): Color of SVG icon
        """
        self.setPen(QPen(Qt.black, 1, Qt.SolidLine))
        self.setBrush(brush)
        self.colorizer.setColor(svg_color)
        # Load SVG
        loading_ok = self.renderer.load(svg)
        if not loading_ok:
            self._toolbox.msg_error.emit(
                "Loading SVG icon from resource:{0} failed".format(svg))
            return
        size = self.renderer.defaultSize()
        self.svg_item.setSharedRenderer(self.renderer)
        self.svg_item.setElementId(
            "")  # guess empty string loads the whole file
        dim_max = max(size.width(), size.height())
        rect_w = self.rect().width()  # Parent rect width
        margin = 32
        self.svg_item.setScale((rect_w - margin) / dim_max)
        x_offset = (rect_w - self.svg_item.sceneBoundingRect().width()) / 2
        y_offset = (rect_w - self.svg_item.sceneBoundingRect().height()) / 2
        self.svg_item.setPos(self.rect().x() + x_offset,
                             self.rect().y() + y_offset)
        self.svg_item.setGraphicsEffect(self.colorizer)
        self.setFlag(QGraphicsItem.ItemIsMovable, enabled=True)
        self.setFlag(QGraphicsItem.ItemIsSelectable, enabled=True)
        self.setFlag(QGraphicsItem.ItemIsFocusable, enabled=True)
        self.setFlag(QGraphicsItem.ItemSendsScenePositionChanges, enabled=True)
        self.setAcceptHoverEvents(True)
        self.setCursor(Qt.PointingHandCursor)
        # Set exclamation and rank icons position
        self.exclamation_icon.setPos(
            self.rect().topRight() -
            self.exclamation_icon.sceneBoundingRect().topRight())
        self.rank_icon.setPos(self.rect().topLeft())

    def name(self):
        """Returns name of the item that is represented by this icon."""
        return self._project_item.name

    def update_name_item(self, new_name):
        """Set a new text to name item. Used when a project item is renamed."""
        self.name_item.setText(new_name)
        self.set_name_attributes()

    def set_name_attributes(self):
        """Set name QGraphicsSimpleTextItem attributes (font, size, position, etc.)"""
        # Set font size and style
        font = self.name_item.font()
        font.setPointSize(self.text_font_size)
        font.setBold(True)
        self.name_item.setFont(font)
        # Set name item position (centered on top of the master icon)
        name_width = self.name_item.boundingRect().width()
        name_height = self.name_item.boundingRect().height()
        self.name_item.setPos(
            self.rect().x() + self.rect().width() / 2 - name_width / 2,
            self.rect().y() - name_height - 4)

    def conn_button(self, position="left"):
        """Returns items connector button (QWidget)."""
        return self.connectors.get(position, self.connectors["left"])

    def outgoing_links(self):
        return [
            l for conn in self.connectors.values()
            for l in conn.outgoing_links()
        ]

    def incoming_links(self):
        return [
            l for conn in self.connectors.values()
            for l in conn.incoming_links()
        ]

    def hoverEnterEvent(self, event):
        """Sets a drop shadow effect to icon when mouse enters its boundaries.

        Args:
            event (QGraphicsSceneMouseEvent): Event
        """
        self.prepareGeometryChange()
        self.graphicsEffect().setEnabled(True)
        event.accept()

    def hoverLeaveEvent(self, event):
        """Disables the drop shadow when mouse leaves icon boundaries.

        Args:
            event (QGraphicsSceneMouseEvent): Event
        """
        self.prepareGeometryChange()
        self.graphicsEffect().setEnabled(False)
        event.accept()

    def mousePressEvent(self, event):
        super().mousePressEvent(event)
        self.icon_group = set(x for x in self.scene().selectedItems()
                              if isinstance(x, ProjectItemIcon)) | {self}
        for icon in self.icon_group:
            icon._previous_pos = icon.scenePos()

    def mouseMoveEvent(self, event):
        """Moves icon(s) while the mouse button is pressed.
        Update links that are connected to selected icons.

        Args:
            event (QGraphicsSceneMouseEvent): Event
        """
        super().mouseMoveEvent(event)
        self.update_links_geometry()

    def moveBy(self, dx, dy):
        super().moveBy(dx, dy)
        self.update_links_geometry()

    def update_links_geometry(self):
        """Updates geometry of connected links to reflect this item's most recent position."""
        links = set(link for icon in self.icon_group
                    for conn in icon.connectors.values()
                    for link in conn.links)
        for link in links:
            link.update_geometry()

    def mouseReleaseEvent(self, event):
        for icon in self.icon_group:
            icon._current_pos = icon.scenePos()
        # pylint: disable=undefined-variable
        if (self._current_pos - self._previous_pos
            ).manhattanLength() > qApp.startDragDistance():
            self._toolbox.undo_stack.push(MoveIconCommand(self))
        super().mouseReleaseEvent(event)

    def notify_item_move(self):
        if self._moved_on_scene:
            self._moved_on_scene = False
            scene = self.scene()
            scene.item_move_finished.emit(self)

    def contextMenuEvent(self, event):
        """Show item context menu.

        Args:
            event (QGraphicsSceneMouseEvent): Mouse event
        """
        self.scene().clearSelection()
        self.setSelected(True)
        self._toolbox.show_item_image_context_menu(event.screenPos(),
                                                   self.name())

    def keyPressEvent(self, event):
        """Handles deleting and rotating the selected
        item when dedicated keys are pressed.

        Args:
            event (QKeyEvent): Key event
        """
        if event.key() == Qt.Key_Delete and self.isSelected():
            self._project_item._project.remove_item(self.name())
            event.accept()
        elif event.key() == Qt.Key_R and self.isSelected():
            # TODO:
            # 1. Change name item text direction when rotating
            # 2. Save rotation into project file
            rect = self.mapToScene(self.boundingRect()).boundingRect()
            center = rect.center()
            t = QTransform()
            t.translate(center.x(), center.y())
            t.rotate(90)
            t.translate(-center.x(), -center.y())
            self.setPos(t.map(self.pos()))
            self.setRotation(self.rotation() + 90)
            links = set(lnk for conn in self.connectors.values()
                        for lnk in conn.links)
            for link in links:
                link.update_geometry()
            event.accept()
        else:
            super().keyPressEvent(event)

    def itemChange(self, change, value):
        """
        Reacts to item removal and position changes.

        In particular, destroys the drop shadow effect when the items is removed from a scene
        and 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:
             Whatever super() does with the value parameter
        """
        if change == QGraphicsItem.ItemScenePositionHasChanged:
            self._moved_on_scene = True
        elif change == QGraphicsItem.GraphicsItemChange.ItemSceneChange and value is None:
            self.prepareGeometryChange()
            self.setGraphicsEffect(None)
        return super().itemChange(change, value)

    def show_item_info(self):
        """Update GUI to show the details of the selected item."""
        ind = self._toolbox.project_item_model.find_item(self.name())
        self._toolbox.ui.treeView_project.setCurrentIndex(ind)