Skip to content

Commit

Permalink
Bug fixes
Browse files Browse the repository at this point in the history
- Call SetPos after a Group moves when an item in the Group is moved
- Fix Renaming Groups
- Fix Ungrouping
  • Loading branch information
ptsavol committed Sep 30, 2024
1 parent 95fd6e7 commit a0ea8df
Show file tree
Hide file tree
Showing 12 changed files with 232 additions and 94 deletions.
118 changes: 103 additions & 15 deletions spinetoolbox/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
######################################################################################################################

"""Class for drawing an item group on QGraphicsScene."""
from PySide6.QtCore import Qt, QMarginsF, QRectF
from PySide6.QtCore import Qt, Slot, QMarginsF, QRectF, QPointF
from PySide6.QtGui import QBrush, QPen, QAction, QPainterPath, QTransform
from PySide6.QtWidgets import (
QGraphicsItem,
Expand All @@ -21,6 +21,8 @@
QStyle
)
from .project_item_icon import ProjectItemIcon
from .project_commands import RenameGroupCommand
from widgets.notification import Notification


class Group(QGraphicsRectItem):
Expand All @@ -29,8 +31,8 @@ class Group(QGraphicsRectItem):

def __init__(self, toolbox, name, item_names):
super().__init__()
print(f"toolbox:{toolbox}")
self._toolbox = toolbox
self._scene = None
self._name = name
self._item_names = item_names # strings
self._items = dict() # QGraphicsItems
Expand All @@ -46,24 +48,31 @@ def __init__(self, toolbox, name, item_names):
item_icon.my_groups.add(self)
self._n_items = len(self._items)
disband_action = QAction("Ungroup items")
disband_action.triggered.connect(lambda checked=False, group_name=self.name: self._toolbox.ui.graphicsView.push_disband_group_command(checked, group_name))
disband_action.triggered.connect(self.call_disband_group)
rename_group_action = QAction("Rename group...")
rename_group_action.triggered.connect(lambda checked=False, group_name=self.name: self._toolbox.rename_group(checked, group_name))
rename_group_action.triggered.connect(self.rename_group)
self._actions = [disband_action, rename_group_action]
self.margins = QMarginsF(0, 0, 0, 10.0) # left, top, right, bottom
self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable, enabled=True)
self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsMovable, enabled=True)
self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemSendsScenePositionChanges, enabled=True)
self.setAcceptHoverEvents(True)
self.setZValue(-10)
self.name_item = QGraphicsTextItem(self._name, parent=self)
self.set_name_attributes()
self.setRect(self.rect())
self.setRect(self.current_rect())
self._reposition_name_item()
self.setBrush(self._toolbox.ui.graphicsView.scene().bg_color.lighter(107))
self.normal_pen = QPen(QBrush("gray"), 1, Qt.PenStyle.SolidLine)
self.selected_pen_for_ui_lite = QPen(QBrush("gray"), 5, Qt.PenStyle.DashLine)
self.selected_pen = QPen(QBrush("black"), 1, Qt.PenStyle.DashLine)
self.setPen(self.normal_pen)
self.set_graphics_effects()
self.previous_pos = QPointF()
self._moved_on_scene = False
self._bumping = True
# self.setOpacity(0.5)
self.mouse_press_pos = None

@property
def name(self):
Expand Down Expand Up @@ -145,26 +154,81 @@ def remove_item(self, name):
item.my_groups.remove(self)
self.update_group_rect()

@Slot(bool)
def call_disband_group(self, _=False):
self._toolbox.toolboxuibase.active_ui_window.ui.graphicsView.push_disband_group_command(self.name)

@Slot(bool)
def rename_group(self, _=False):
"""Renames Group."""
new_name = self._toolbox.show_simple_input_dialog("Rename Group", "New name:", self.name)
if not new_name or new_name == self.name:
return
if new_name in self._toolbox.project.groups.keys():
notif = Notification(self._toolbox.toolboxuibase.active_ui_window, f"Group {new_name} already exists")
notif.show()
return
self._toolbox.toolboxuibase.undo_stack.push(RenameGroupCommand(self._toolbox.project, self.name, new_name))

def remove_all_items(self):
"""Removes all items (ProjectItemIcons) from this group."""
for item in self.project_items:
self.remove_item(item.name)

def update_group_rect(self):
"""Updates group rectangle and it's attributes when group member(s) is/are moved."""
self.setRect(self.rect())
def update_group_rect(self, current_pos=None):
"""Updates group rectangle, and it's position when group member(s) is/are moved."""
self.prepareGeometryChange()
r = self.current_rect()
self.setRect(r)
if current_pos is not None:
diff_x = current_pos.x() - self.mouse_press_pos.x()
diff_y = current_pos.y() - self.mouse_press_pos.y()
self.setPos(QPointF(diff_x, diff_y))
self._reposition_name_item()

def rect(self):
def current_rect(self):
"""Calculates the size of the rectangle for this group."""
united_rect = QRectF()
for item in self.items:
if isinstance(item, ProjectItemIcon):
united_rect = united_rect.united(item.name_item.sceneBoundingRect().united(item.sceneBoundingRect()))
# Combine item icon box and name item
icon_rect = item.name_item.sceneBoundingRect().united(item.sceneBoundingRect())
# Combine spec item rect if available
if item.spec_item is not None:
icon_rect = icon_rect.united(item.spec_item.sceneBoundingRect())
united_rect = united_rect.united(icon_rect)
else:
united_rect = united_rect.united(item.sceneBoundingRect())
rect_with_margins = united_rect.marginsAdded(self.margins)
return rect_with_margins
return united_rect

def itemChange(self, change, value):
"""
Reacts to item removal and position changes.
In particular, destroys the drop shadow effect when the item 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.GraphicsItemChange.ItemScenePositionHasChanged:
self._moved_on_scene = True
elif change == QGraphicsItem.GraphicsItemChange.ItemSceneChange and value is None:
self.prepareGeometryChange()
self.setGraphicsEffect(None)
elif change == QGraphicsItem.GraphicsItemChange.ItemSceneHasChanged:
scene = value
if scene is None:
self._scene.removeItem(self.name_item)
else:
self._scene = scene
self._scene.addItem(self.name_item)
self._reposition_name_item()
return super().itemChange(change, value)

def set_graphics_effects(self):
shadow_effect = QGraphicsDropShadowEffect()
Expand All @@ -179,13 +243,37 @@ def mousePressEvent(self, event):
event (QMousePressEvent): Event
"""
event.accept()
self.scene().clearSelection()
path = QPainterPath()
path.addRect(self.rect())
path.addRect(self.sceneBoundingRect())
self._toolbox.toolboxuibase.active_ui_window.ui.graphicsView.scene().setSelectionArea(path, QTransform())
icon_group = set(self.project_items)
for icon in icon_group:
icon.this_icons_group_is_moving = True
icon.previous_pos = icon.scenePos()
self.scene().icon_group = icon_group

def mouseReleaseEvent(self, event):
"""Accepts the event to prevent graphics view's mouseReleaseEvent from clearing the selections."""
event.accept()
if (self.scenePos() - self.previous_pos).manhattanLength() > qApp.startDragDistance():
# self._toolbox.undo_stack.push(MoveGroupCommand(self, self._toolbox.project))
self.notify_item_move()
event.accept()
# icon_group = set(self.project_items)
# for icon in icon_group:
# icon.this_icons_group_is_moving = False
super().mouseReleaseEvent(event)

def set_pos_without_bumping(self, pos):
self._bumping = False
self.setPos(pos)
self._bumping = True

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):
"""Opens context-menu in design mode."""
Expand Down Expand Up @@ -222,7 +310,7 @@ def to_dict(self):
return {self.name: self.item_names}

def paint(self, painter, option, widget=None):
"""Sets a dashline pen when selected."""
"""Sets a dash line pen when selected."""
if option.state & QStyle.StateFlag.State_Selected:
option.state &= ~QStyle.StateFlag.State_Selected
if self._toolbox.active_ui_mode == "toolboxui":
Expand Down
9 changes: 4 additions & 5 deletions spinetoolbox/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,7 @@ def __init__(self, toolbox, p_dir, plugin_specs, app_settings, settings, logger)
self._settings = settings
self._engine_workers = []
self._execution_in_progress = False
# self._execution_groups = {}
self._groups = dict()
self._groups = dict() # Dictionary, which maps group names to group instances
self.project_dir = None # Full path to project directory
self.config_dir = None # Full path to .spinetoolbox directory
self.items_dir = None # Full path to items directory
Expand Down Expand Up @@ -215,10 +214,10 @@ def add_item_to_group(self, item_name, group_name):
self.groups[group_name].add_item(item_name)

def remove_item_from_group(self, item_name, group_name):
"""Removes item with given name from given group. If only
one item remains in the group, destroys the whole group."""
"""Removes item with given name from given group. If the last item in
the group was removed, destroys the whole group."""
self.groups[group_name].remove_item(item_name)
if len(self.groups[group_name].project_items) == 1:
if not self.groups[group_name].project_items:
self.disband_group(False, group_name)

@Slot(bool, str)
Expand Down
41 changes: 40 additions & 1 deletion spinetoolbox/project_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,45 @@ def _move_to(self, positions):
self._representative.notify_item_move()


class MoveGroupCommand(SpineToolboxCommand):
"""Command to move Group in the Design view."""

def __init__(self, icon, project):
"""
Args:
icon (Group): the icon
project (SpineToolboxProject): project
"""
super().__init__()
self._icon = icon
self._project = project
icon_group = icon.scene().icon_group
self._representative = next(iter(icon_group), None)
if self._representative is None:
self.setObsolete(True)
self._previous_group_position = self._icon.previous_pos
self._current_group_position = self._icon.scenePos()
self._previous_item_positions = {x.name: x.previous_pos for x in icon_group}
self._current_item_positions = {x.name: x.scenePos() for x in icon_group}
if len(icon_group) == 1:
self.setText(f"move {self._icon.name}")
else:
self.setText("move multiple items")

def redo(self):
self._move_to(self._current_item_positions, self._current_group_position)

def undo(self):
self._move_to(self._previous_item_positions, self._previous_group_position)

def _move_to(self, item_positions, group_position):
for item_name, position in item_positions.items():
icon = self._project.get_item(item_name).get_icon()
icon.set_pos_without_bumping(position)
self._icon.set_pos_without_bumping(group_position)
self._icon.notify_item_move()


class SetProjectDescriptionCommand(SpineToolboxCommand):
"""Command to set the project description."""

Expand Down Expand Up @@ -313,7 +352,7 @@ def __init__(self, project, item_name, group_name):
self._item_names = self._project.groups[group_name].item_names
self._links_removed = []
self._remake_group = False
if len(self._project.groups[group_name].project_items) == 2:
if len(self._project.groups[group_name].project_items) == 1:
self._remake_group = True
self.setText(f"disband {self._group_name}")

Expand Down
1 change: 0 additions & 1 deletion spinetoolbox/project_item/project_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,6 @@ def set_rank(self, rank):
self.get_icon().rank_icon.set_rank("X")

def update_progress_bar(self):
# print(f"{self._project.active_toolboxui}")
if self._toolbox.active_ui_mode == "toolboxuilite":
n_selected = len(self._toolbox.ui.graphicsView.scene().selectedItems())
print(f"[{n_selected}] started: {self.name}")
Expand Down
21 changes: 12 additions & 9 deletions spinetoolbox/project_item_icon.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ def __init__(self, toolbox, icon_file, icon_color):
self.icon_file = icon_file
self._icon_color = icon_color
self._moved_on_scene = False
self.this_icons_group_is_moving = False
self.previous_pos = QPointF()
self.icon_group = {self}
self.my_groups = set()
Expand Down Expand Up @@ -308,11 +309,19 @@ def hoverLeaveEvent(self, event):

def mousePressEvent(self, event):
"""Updates scene's icon group."""
super().mousePressEvent(event)
for group in self.my_groups:
group.mouse_press_pos = event.pos()
icon_group = set(x for x in self.scene().selectedItems() if isinstance(x, ProjectItemIcon)) | {self}
for icon in icon_group:
icon.previous_pos = icon.scenePos()
self.scene().icon_group = icon_group
super().mousePressEvent(event)

def mouseMoveEvent(self, event):
"""Updates group rectangles on scene, if this item is in group(s)."""
for group in self.my_groups:
group.update_group_rect(event.pos())
super().mouseMoveEvent(event)

def update_links_geometry(self):
"""Updates geometry of connected links to reflect this item's most recent position."""
Expand All @@ -330,6 +339,8 @@ def update_links_geometry(self):

def mouseReleaseEvent(self, event):
"""Clears pre-bump rects, and pushes a move icon command if necessary."""
for group in self.my_groups:
group.mouse_press_pos = None
for icon in self.scene().icon_group:
icon.bumped_rects.clear()
# pylint: disable=undefined-variable
Expand Down Expand Up @@ -375,7 +386,6 @@ def itemChange(self, change, value):
self._reposition_name_item()
self.update_links_geometry()
self._handle_collisions()
self.update_group_rectangle()
elif change == QGraphicsItem.GraphicsItemChange.ItemSceneChange and value is None:
self.prepareGeometryChange()
self.setGraphicsEffect(None)
Expand All @@ -389,13 +399,6 @@ def itemChange(self, change, value):
self._reposition_name_item()
return super().itemChange(change, value)

def update_group_rectangle(self):
"""Updates group icon if this icon is in a group."""
if not self.my_groups:
return
for group in self.my_groups:
group.update_group_rect()

def set_pos_without_bumping(self, pos):
"""Sets position without bumping other items. Needed for undoing move operations.
Expand Down
6 changes: 3 additions & 3 deletions spinetoolbox/resources_icons_rc.py
Original file line number Diff line number Diff line change
Expand Up @@ -32565,7 +32565,7 @@
\x00\x00\x02R\x00\x00\x00\x00\x00\x01\x00\x06A\x9c\
\x00\x00\x01\x8f\xec\xcf\x12\xea\
\x00\x00\x03\xf6\x00\x00\x00\x00\x00\x01\x00\x06\x978\
\x00\x00\x01\x91y6\xd1)\
\x00\x00\x01\x91\xe0y,6\
\x00\x00\x01\xea\x00\x00\x00\x00\x00\x01\x00\x062\x92\
\x00\x00\x01\x8f\xec\xcf\x13\x09\
\x00\x00\x04\x94\x00\x00\x00\x00\x00\x01\x00\x06\xaa\xa4\
Expand Down Expand Up @@ -32625,7 +32625,7 @@
\x00\x00\x09z\x00\x00\x00\x00\x00\x01\x00\x07P}\
\x00\x00\x01\x8f\xec\xcf\x12\xfa\
\x00\x00\x08\xd8\x00\x00\x00\x00\x00\x01\x00\x07=|\
\x00\x00\x01f\xd4x\xbb0\
\x00\x00\x01\x91\xe0y,7\
\x00\x00\x0a\x00\x00\x00\x00\x00\x00\x01\x00\x07gO\
\x00\x00\x01\x8f\xec\xcf\x12\xfa\
\x00\x00\x076\x00\x00\x00\x00\x00\x01\x00\x07\x0f\x05\
Expand Down Expand Up @@ -32699,7 +32699,7 @@
\x00\x00\x07\xca\x00\x00\x00\x00\x00\x01\x00\x07!\x85\
\x00\x00\x01\x8f\xec\xcf\x12\xfa\
\x00\x00\x06\xf8\x00\x00\x00\x00\x00\x01\x00\x06\xfc\x96\
\x00\x00\x01\x91y6\xd1*\
\x00\x00\x01\x91\xe0y,8\
\x00\x00\x0a\xfe\x00\x00\x00\x00\x00\x01\x00\x07\x9b6\
\x00\x00\x01\x8f\xec\xcf\x12\xfa\
\x00\x00\x06\xce\x00\x00\x00\x00\x00\x01\x00\x06\xf6R\
Expand Down
Loading

0 comments on commit a0ea8df

Please sign in to comment.