diff --git a/spinetoolbox/spine_db_editor/graphics_items.py b/spinetoolbox/spine_db_editor/graphics_items.py index ce7b2fb87..8120ef681 100644 --- a/spinetoolbox/spine_db_editor/graphics_items.py +++ b/spinetoolbox/spine_db_editor/graphics_items.py @@ -921,7 +921,7 @@ def _place_resizers(self): for anchor, resizer in self._resizers.items(): getter, _ = self._getter_setter[anchor] resizer.setPos( - getattr(self.rect(), getter)() - resizer.rect().center() / self.scale() / self._scaling_factor + getattr(self.rect(), getter)() - getattr(resizer.rect(), getter)() / self.scale() / self._scaling_factor ) def _resize(self, anchor, delta, strong): @@ -950,6 +950,9 @@ def fit_rect(self, rect): rect = QRectF(*rect) self._do_resize(rect, True) + def scene_rect(self): + return self.mapToScene(self.rect()).boundingRect() + def fit_coordinates(self, p1, p2, scen1, scen2): # NOTE: not in use at the moment size = self._renderer.defaultSize() @@ -1002,7 +1005,7 @@ class _Resizer(QGraphicsRectItem): class SignalsProvider(QObject): resized = Signal(QPointF, bool) - def __init__(self, rect=QRectF(0, 0, 12, 12), parent=None): + def __init__(self, rect=QRectF(0, 0, 20, 20), parent=None): super().__init__(rect, parent) self._original_rect = self.rect() self._press_pos = None diff --git a/spinetoolbox/spine_db_editor/widgets/custom_qgraphicsviews.py b/spinetoolbox/spine_db_editor/widgets/custom_qgraphicsviews.py index 4de583123..e612cc10c 100644 --- a/spinetoolbox/spine_db_editor/widgets/custom_qgraphicsviews.py +++ b/spinetoolbox/spine_db_editor/widgets/custom_qgraphicsviews.py @@ -18,7 +18,7 @@ import tempfile from contextlib import contextmanager from PySide6.QtCore import Qt, QTimeLine, Signal, Slot, QRectF -from PySide6.QtWidgets import QMenu, QGraphicsView, QInputDialog, QColorDialog +from PySide6.QtWidgets import QMenu, QGraphicsView, QInputDialog, QColorDialog, QMessageBox, QLineEdit from PySide6.QtGui import QCursor, QPainter, QIcon, QAction, QPageSize, QPixmap from PySide6.QtPrintSupport import QPrinter from PySide6.QtSvg import QSvgGenerator @@ -125,6 +125,7 @@ def __init__(self, parent): self.name_parameter = "" self.color_parameter = "" self.arc_width_parameter = "" + self._current_state_name = "" self._margin = 0.05 self._bg_item = None self.selected_items = [] @@ -175,6 +176,18 @@ def __init__(self, parent): self._items_per_class = {} self._db_map_graph_data_by_name = {} + @property + def _qsettings(self): + return self._spine_db_editor.qsettings + + @property + def db_mngr(self): + return self._spine_db_editor.db_mngr + + @property + def entity_items(self): + return [x for x in self.scene().items() if isinstance(x, EntityItem) and x not in self.removed_items] + def get_property(self, name): return self._properties[name].value @@ -202,18 +215,6 @@ def get_pruned_entity_ids(self, db_map): def get_pruned_db_map_entity_ids(self): return [db_map_id for db_map_ids in self.pruned_db_map_entity_ids.values() for db_map_id in db_map_ids] - @property - def _qsettings(self): - return self._spine_db_editor.qsettings - - @property - def db_mngr(self): - return self._spine_db_editor.db_mngr - - @property - def entity_items(self): - return [x for x in self.scene().items() if isinstance(x, EntityItem) and x not in self.removed_items] - @Slot() def handle_scene_selection_changed(self): """Filters parameters by selected objects in the graph.""" @@ -293,17 +294,26 @@ def populate_context_menu(self): self._menu.aboutToShow.connect(self._update_actions_visibility) def _save_state(self): - name, ok = QInputDialog.getText(self, "Save state...", "Enter a name for the state.") + name, ok = QInputDialog.getText( + self, "Save state...", "Enter a name for the state.", QLineEdit.Normal, self._current_state_name + ) if not ok: return - if name in self._db_map_graph_data_by_name: - self._spine_db_editor.msg_error.emit(f"State {name} already exists") + db_map_graph_data = self._db_map_graph_data_by_name.get(name) + if db_map_graph_data is not None: + button = QMessageBox.question( + self._spine_db_editor, + self._spine_db_editor.windowTitle(), + f"State {name} already exists. Do you want to overwrite it?", + ) + if button == QMessageBox.StandardButton.Yes: + self._spine_db_editor.overwrite_graph_data(db_map_graph_data) return self._spine_db_editor.save_graph_data(name) @Slot(QAction) def _load_state(self, action): - name = action.text() + self._current_state_name = name = action.text() db_map_graph_data = self._db_map_graph_data_by_name.get(name) self._spine_db_editor.load_graph_data(db_map_graph_data) @@ -558,7 +568,8 @@ def _clear_positions(self, items): return db_map_ids = {} for item in items: - db_map_ids.setdefault(item.db_map, set()).add(item.entity_id) + for db_map, entity_id in item.db_map_ids: + db_map_ids.setdefault(db_map, set()).add(entity_id) db_map_typed_data = {} for db_map, ids in db_map_ids.items(): db_map_typed_data[db_map] = { @@ -603,7 +614,7 @@ def set_bg_rect(self, rect): def get_bg_rect(self): if self._bg_item is not None: - rect = self._bg_item.rect() + rect = self._bg_item.scene_rect() return rect.x(), rect.y(), rect.width(), rect.height() def clear_scene(self): diff --git a/spinetoolbox/spine_db_editor/widgets/graph_view_mixin.py b/spinetoolbox/spine_db_editor/widgets/graph_view_mixin.py index 9416ae24a..cef215091 100644 --- a/spinetoolbox/spine_db_editor/widgets/graph_view_mixin.py +++ b/spinetoolbox/spine_db_editor/widgets/graph_view_mixin.py @@ -340,8 +340,8 @@ def rebuild_graph(self, _checked=False): self.db_map_entity_id_sets.clear() self.build_graph() - def save_graph_data(self, name): - db_map_data = {} + def _get_db_map_graph_data(self): + db_map_graph_data = {} for db_map in self.db_maps: graph_data = { "type": "graph_data", @@ -359,10 +359,22 @@ def save_graph_data(self, name): "bg_rect": self.ui.graphicsView.get_bg_rect(), "properties": self.ui.graphicsView.get_all_properties(), } - db_map_data[db_map] = [{"name": name, "value": json.dumps(graph_data)}] + db_map_graph_data[db_map] = json.dumps(graph_data) + return db_map_graph_data + + def save_graph_data(self, name): + db_map_graph_data = self._get_db_map_graph_data() + db_map_data = {db_map: [{"name": name, "value": gd}] for db_map, gd in db_map_graph_data.items()} self.db_mngr.add_metadata(db_map_data) # TODO: also add entity_metadata so it sticks + def overwrite_graph_data(self, db_map_graph_data): + db_map_graph_data_ = self._get_db_map_graph_data() + db_map_data = { + db_map: [{"id": db_map_graph_data[db_map]["id"], "value": gd}] for db_map, gd in db_map_graph_data_.items() + } + self.db_mngr.update_metadata(db_map_data) + def get_db_map_graph_data_by_name(self): db_map_graph_data_by_name = {} for db_map in self.db_maps: @@ -372,6 +384,7 @@ def get_db_map_graph_data_by_name(self): except json.decoder.JSONDecodeError: continue if isinstance(graph_data, dict) and graph_data.get("type") == "graph_data": + graph_data["id"] = metadata_item["id"] db_map_graph_data_by_name.setdefault(metadata_item["name"], {})[db_map] = graph_data return db_map_graph_data_by_name