From a0ea8df611d8d0010d530d64167d0a228e2d2003 Mon Sep 17 00:00:00 2001 From: Pekka T Savolainen Date: Mon, 30 Sep 2024 14:59:57 +0100 Subject: [PATCH] Bug fixes - Call SetPos after a Group moves when an item in the Group is moved - Fix Renaming Groups - Fix Ungrouping --- spinetoolbox/group.py | 118 +++++++++++++++--- spinetoolbox/project.py | 9 +- spinetoolbox/project_commands.py | 41 +++++- spinetoolbox/project_item/project_item.py | 1 - spinetoolbox/project_item_icon.py | 21 ++-- spinetoolbox/resources_icons_rc.py | 6 +- .../widgets/custom_qgraphicsviews.py | 6 +- spinetoolbox/ui/mainwindow.py | 16 ++- spinetoolbox/ui/mainwindow.ui | 15 +++ spinetoolbox/ui_main.py | 16 +-- spinetoolbox/ui_main_lite.py | 4 - spinetoolbox/widgets/custom_qgraphicsviews.py | 73 +++++------ 12 files changed, 232 insertions(+), 94 deletions(-) diff --git a/spinetoolbox/group.py b/spinetoolbox/group.py index 1bef17795..bd011d4c4 100644 --- a/spinetoolbox/group.py +++ b/spinetoolbox/group.py @@ -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, @@ -21,6 +21,8 @@ QStyle ) from .project_item_icon import ProjectItemIcon +from .project_commands import RenameGroupCommand +from widgets.notification import Notification class Group(QGraphicsRectItem): @@ -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 @@ -46,17 +48,19 @@ 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) @@ -64,6 +68,11 @@ def __init__(self, toolbox, name, item_names): 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): @@ -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() @@ -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.""" @@ -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": diff --git a/spinetoolbox/project.py b/spinetoolbox/project.py index 3805d6e23..667ef75b2 100644 --- a/spinetoolbox/project.py +++ b/spinetoolbox/project.py @@ -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 @@ -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) diff --git a/spinetoolbox/project_commands.py b/spinetoolbox/project_commands.py index 2cb99a7e1..f204c265a 100644 --- a/spinetoolbox/project_commands.py +++ b/spinetoolbox/project_commands.py @@ -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.""" @@ -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}") diff --git a/spinetoolbox/project_item/project_item.py b/spinetoolbox/project_item/project_item.py index d04f99f6b..a64ddd9cc 100644 --- a/spinetoolbox/project_item/project_item.py +++ b/spinetoolbox/project_item/project_item.py @@ -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}") diff --git a/spinetoolbox/project_item_icon.py b/spinetoolbox/project_item_icon.py index 23790db6a..68112dbac 100644 --- a/spinetoolbox/project_item_icon.py +++ b/spinetoolbox/project_item_icon.py @@ -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() @@ -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.""" @@ -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 @@ -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) @@ -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. diff --git a/spinetoolbox/resources_icons_rc.py b/spinetoolbox/resources_icons_rc.py index 8f3db47be..a062391e2 100644 --- a/spinetoolbox/resources_icons_rc.py +++ b/spinetoolbox/resources_icons_rc.py @@ -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\ @@ -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\ @@ -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\ diff --git a/spinetoolbox/spine_db_editor/widgets/custom_qgraphicsviews.py b/spinetoolbox/spine_db_editor/widgets/custom_qgraphicsviews.py index 66d781512..b1b061fac 100644 --- a/spinetoolbox/spine_db_editor/widgets/custom_qgraphicsviews.py +++ b/spinetoolbox/spine_db_editor/widgets/custom_qgraphicsviews.py @@ -854,7 +854,7 @@ def set_cross_hairs_items(self, entity_class, cross_hairs_items): item.apply_zoom(self.zoom_factor) cursor_pos = self.mapFromGlobal(QCursor.pos()) self._update_cross_hairs_pos(cursor_pos) - self.viewport().setCursor(Qt.BlankCursor) + self.viewport().setCursor(Qt.CursorShape.BlankCursor) def clear_cross_hairs_items(self): self.entity_class = None @@ -875,7 +875,7 @@ def mousePressEvent(self, event): if not self.cross_hairs_items: super().mousePressEvent(event) return - if event.buttons() & Qt.RightButton or not self._hovered_ent_item: + if event.buttons() & Qt.MouseButton.RightButton or not self._hovered_ent_item: self.clear_cross_hairs_items() return if self._cross_hairs_has_valid_target(): @@ -937,7 +937,7 @@ def mouseReleaseEvent(self, event): def keyPressEvent(self, event): """Aborts relationship creation if user presses ESC.""" super().keyPressEvent(event) - if event.key() == Qt.Key_Escape and self.cross_hairs_items: + if event.key() == Qt.Key.Key_Escape and self.cross_hairs_items: self._spine_db_editor.msg.emit("Relationship creation aborted.") self.clear_cross_hairs_items() diff --git a/spinetoolbox/ui/mainwindow.py b/spinetoolbox/ui/mainwindow.py index 042dcb199..c6a0f5ba1 100644 --- a/spinetoolbox/ui/mainwindow.py +++ b/spinetoolbox/ui/mainwindow.py @@ -197,6 +197,12 @@ def setupUi(self, MainWindow): icon19 = QIcon() icon19.addFile(u":/icons/menu_icons/retweet.svg", QSize(), QIcon.Mode.Normal, QIcon.State.Off) self.actionSwitch_to_user_mode.setIcon(icon19) + self.actionGroup_items = QAction(MainWindow) + self.actionGroup_items.setObjectName(u"actionGroup_items") + icon20 = QIcon() + icon20.addFile(u":/icons/menu_icons/object-group.svg", QSize(), QIcon.Mode.Normal, QIcon.State.Off) + self.actionGroup_items.setIcon(icon20) + self.actionGroup_items.setMenuRole(QAction.MenuRole.NoRole) self.centralwidget = QWidget(MainWindow) self.centralwidget.setObjectName(u"centralwidget") MainWindow.setCentralWidget(self.centralwidget) @@ -261,9 +267,9 @@ def setupUi(self, MainWindow): self.toolButton_executions = QToolButton(self.dockWidgetContents) self.toolButton_executions.setObjectName(u"toolButton_executions") - icon20 = QIcon() - icon20.addFile(u":/icons/check-circle.svg", QSize(), QIcon.Mode.Normal, QIcon.State.Off) - self.toolButton_executions.setIcon(icon20) + icon21 = QIcon() + icon21.addFile(u":/icons/check-circle.svg", QSize(), QIcon.Mode.Normal, QIcon.State.Off) + self.toolButton_executions.setIcon(icon21) self.toolButton_executions.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup) self.toolButton_executions.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextBesideIcon) @@ -619,6 +625,10 @@ def retranslateUi(self, MainWindow): #if QT_CONFIG(shortcut) self.actionSwitch_to_user_mode.setShortcut(QCoreApplication.translate("MainWindow", u"\u00a7", None)) #endif // QT_CONFIG(shortcut) + self.actionGroup_items.setText(QCoreApplication.translate("MainWindow", u"Group items", None)) +#if QT_CONFIG(tooltip) + self.actionGroup_items.setToolTip(QCoreApplication.translate("MainWindow", u"Group selected items", None)) +#endif // QT_CONFIG(tooltip) self.menuFile.setTitle(QCoreApplication.translate("MainWindow", u"&File", None)) self.menuHelp.setTitle(QCoreApplication.translate("MainWindow", u"&Help", None)) self.menuEdit.setTitle(QCoreApplication.translate("MainWindow", u"&Edit", None)) diff --git a/spinetoolbox/ui/mainwindow.ui b/spinetoolbox/ui/mainwindow.ui index f42529dd3..8cf03c7ac 100644 --- a/spinetoolbox/ui/mainwindow.ui +++ b/spinetoolbox/ui/mainwindow.ui @@ -895,6 +895,21 @@ ยง + + + + :/icons/menu_icons/object-group.svg:/icons/menu_icons/object-group.svg + + + Group items + + + Group selected items + + + QAction::MenuRole::NoRole + + diff --git a/spinetoolbox/ui_main.py b/spinetoolbox/ui_main.py index 35a0121ef..09b981d20 100644 --- a/spinetoolbox/ui_main.py +++ b/spinetoolbox/ui_main.py @@ -306,6 +306,7 @@ def connect_signals(self): self.ui.actionOpen_item_directory.triggered.connect(self._open_project_item_directory) self.ui.actionRename_item.triggered.connect(self._rename_project_item) self.ui.actionRemove.triggered.connect(self._remove_selected_items) + self.ui.actionGroup_items.triggered.connect(self.ui.graphicsView.group_items) # Debug actions self.show_properties_tabbar.triggered.connect(self.toggle_properties_tabbar_visibility) self.show_supported_img_formats.triggered.connect(supported_img_formats) @@ -1649,6 +1650,7 @@ def show_project_or_item_context_menu(self, pos, item): menu.aboutToShow.connect(self.refresh_edit_action_states) menu.aboutToHide.connect(self.enable_edit_actions) if not item: # Clicked on a blank area in Design view + menu.addAction(self.ui.actionGroup_items) menu.addAction(self.ui.actionPaste) menu.addAction(self.ui.actionPasteAndDuplicateFiles) menu.addSeparator() @@ -1716,6 +1718,7 @@ def refresh_edit_action_states(self): selected_project_items = [x for x in selected_items if isinstance(x, ProjectItemIcon)] _methods = [getattr(self.project.get_item(x.name), "copy_local_data") for x in selected_project_items] can_duplicate_files = any(m.__qualname__.partition(".")[0] != "ProjectItem" for m in _methods) + can_make_group = True if len(selected_project_items) > 0 else False # Renaming an item should always be allowed except when it's a Data Store that is open in an editor for item in (self.project.get_item(x.name) for x in selected_project_items): if item.item_type() == "Data Store" and item.has_listeners(): @@ -1733,6 +1736,7 @@ def refresh_edit_action_states(self): self.ui.actionDuplicateAndDuplicateFiles.setEnabled(can_duplicate_files) self.ui.actionRemove.setEnabled(bool(selected_items)) self.ui.actionRemove_all.setEnabled(has_items) + self.ui.actionGroup_items.setEnabled(can_make_group) def disable_edit_actions(self): """Disables edit actions.""" @@ -2130,20 +2134,12 @@ def _remove_selected_items(self, _): def _rename_project_item(self, _): """Renames active project item.""" item = self.active_project_item - new_name = self._show_simple_input_dialog("Rename Item", "New name:", item.name) + new_name = self.show_simple_input_dialog("Rename Item", "New name:", item.name) if not new_name: return self.undo_stack.push(RenameProjectItemCommand(self._project, item.name, new_name)) - @Slot(bool) - def rename_group(self, _, group_name): - """Renames Group.""" - new_name = self._show_simple_input_dialog("Rename Group", "New name:", group_name) - if not new_name: - return - self.undo_stack.push(RenameGroupCommand(self._project, group_name, new_name)) - - def _show_simple_input_dialog(self, title, label_txt, prefilled_text): + def show_simple_input_dialog(self, title, label_txt, prefilled_text): """Shows a QInputDialog and returns typed text.""" answer = QInputDialog.getText( self, diff --git a/spinetoolbox/ui_main_lite.py b/spinetoolbox/ui_main_lite.py index 70abc4e79..9d4205b90 100644 --- a/spinetoolbox/ui_main_lite.py +++ b/spinetoolbox/ui_main_lite.py @@ -14,8 +14,6 @@ from PySide6.QtCore import Qt, Slot, QRect from PySide6.QtWidgets import QMainWindow, QToolBar, QMenu, QComboBox, QProgressBar from PySide6.QtGui import QStandardItemModel, QStandardItem, QPainterPath, QTransform -# from .project_item_icon import ProjectItemIcon -# from .link import JumpOrLink class ToolboxUILite(QMainWindow): @@ -199,14 +197,12 @@ def _select_group(self, group_name): group = self.project.groups[group_name] path = QPainterPath() path.addRect(group.rect()) - print(f"selection path:{path}") self.ui.graphicsView.scene().setSelectionArea(path, QTransform()) def execute_group(self): """Executes a group.""" if self.groups_combobox.currentIndex() == 0: return - print(f"Executing {self.groups_combobox.currentText()}") if self.groups_combobox.currentText() == "All": self.toolboxui._execute_project() return diff --git a/spinetoolbox/widgets/custom_qgraphicsviews.py b/spinetoolbox/widgets/custom_qgraphicsviews.py index 20631c981..c73db5624 100644 --- a/spinetoolbox/widgets/custom_qgraphicsviews.py +++ b/spinetoolbox/widgets/custom_qgraphicsviews.py @@ -122,10 +122,10 @@ def mouseReleaseEvent(self, event): if self._drag_duration_passed(event): context_menu_disabled = True self.disable_context_menu() - # elif event.button() == Qt.MouseButton.RightButton: # TODO: This creates a second context menu on Design View - # self.contextMenuEvent( - # QContextMenuEvent(QContextMenuEvent.Reason.Mouse, event.pos(), event.globalPos(), event.modifiers()) - # ) + elif event.button() == Qt.MouseButton.RightButton: # TODO: This creates a second context menu on Design View + self.contextMenuEvent( + QContextMenuEvent(QContextMenuEvent.Reason.Mouse, event.pos(), event.globalPos(), event.modifiers()) + ) event = _fake_left_button_event(event) super().mouseReleaseEvent(event) self.setDragMode(QGraphicsView.DragMode.RubberBandDrag) @@ -402,35 +402,17 @@ def _compute_max_zoom(self): viewport_extent = min(self.viewport().width(), self.viewport().height()) return viewport_extent / item_view_rect.width() - def mouseReleaseEvent(self, event): - """Makes an execution group if rubber band contains items and links.""" - if self.dragMode() == QGraphicsView.DragMode.RubberBandDrag: - if self.rubberBandRect(): - selected_items = list() - for item in self.scene().selectedItems(): - if isinstance(item, (ProjectItemIcon, Link, JumpLink)): - selected_items.append(item) - if self.can_make_group(selected_items): - self.push_make_group_command(selected_items) - super().mouseReleaseEvent(event) - - def can_make_group(self, items): - """Checks whether a group can be made from given items. - - Args: - items (list): List of ProjectItemIcons, Links, or JumpLinks - - Returns: - bool: True when a new group can be made, False otherwise - """ - item_icons = [it for it in items if isinstance(it, ProjectItemIcon)] - if len(item_icons) < 2: # Groups must have at least two ProjectItems - return False - for group in self._toolbox.project.groups.values(): # Don't make duplicate groups - if set(item_icons) == set(group.project_items): - self._toolbox.msg_warning.emit(f"{group.name} already has the same items") - return False - return True + # def mouseReleaseEvent(self, event): + # """Makes an execution group if rubber band contains items and links.""" + # if self.dragMode() == QGraphicsView.DragMode.RubberBandDrag: + # if self.rubberBandRect(): + # selected_items = list() + # for item in self.scene().selectedItems(): + # if isinstance(item, (ProjectItemIcon, Link, JumpLink)): + # selected_items.append(item) + # # if self.can_make_group(selected_items): + # # self.push_make_group_command(selected_items) + # super().mouseReleaseEvent(event) @Slot(str) def add_icon(self, item_name): @@ -462,13 +444,24 @@ def remove_icon(self, item_name): scene.removeItem(icon) self._set_preferred_scene_rect() - def push_make_group_command(self, items): - """Pushes an MakeGroupCommand to toolbox undo stack. + @Slot(bool) + def group_items(self, _): + selected = self.scene().selectedItems() + all_item_icons = [item for item in selected if isinstance(item, (ProjectItemIcon, Link, JumpLink))] + all_item_icon_names = [item.name for item in all_item_icons] + project_item_icons = [icon for icon in all_item_icons if isinstance(icon, ProjectItemIcon)] + for group in self._toolbox.project.groups.values(): # Don't make duplicate groups + if set(project_item_icons) == set(group.project_items): + self._toolbox.msg_warning.emit(f"{group.name} already has the same item(s)") + return + self.push_make_group_command(all_item_icon_names) + + def push_make_group_command(self, item_names): + """Pushes a MakeGroupCommand to toolbox undo stack. Args: - items (list): List of selected icons to group + item_names (list): List of item names to group """ - item_names = [item.name for item in items if isinstance(item, (ProjectItemIcon, Link, JumpLink))] self._toolbox.undo_stack.push(MakeGroupCommand(self._toolbox.project, item_names)) @Slot(object) @@ -478,6 +471,8 @@ def add_group_on_scene(self, group): Args: group (Group): Group to add """ + if not group.graphicsEffect(): + group.set_graphics_effects() self.scene().addItem(group) @Slot(bool, str, str) @@ -491,12 +486,10 @@ def push_remove_item_from_group_command(self, _, item_name, group_name): """ self._toolbox.undo_stack.push(RemoveItemFromGroupCommand(self._toolbox.project, item_name, group_name)) - @Slot(bool, str) - def push_disband_group_command(self, _, group_name): + def push_disband_group_command(self, group_name): """Pushes a DisbandGroupCommand to toolbox undo stack. Args: - _ (bool): Boolean sent by triggered signal group_name (str): Group to disband """ self._toolbox.undo_stack.push(DisbandGroupCommand(self._toolbox.project, group_name))