######################################################################################################################
# Copyright (C) 2017-2021 Spine project consortium
# This file is part of Spine Toolbox.
# Spine Toolbox is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General
# Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option)
# any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
# Public License for more details. You should have received a copy of the GNU Lesser General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.
######################################################################################################################
"""
Contains the TreeViewMixin class.
:author: M. Marin (KTH)
:date: 26.11.2018
"""
from PySide2.QtCore import Signal, Slot
from PySide2.QtWidgets import QInputDialog
from .add_items_dialogs import (
AddObjectClassesDialog,
AddObjectsDialog,
AddRelationshipClassesDialog,
AddRelationshipsDialog,
AddObjectGroupDialog,
ManageRelationshipsDialog,
ManageMembersDialog,
)
from .edit_or_remove_items_dialogs import (
EditObjectClassesDialog,
EditObjectsDialog,
EditRelationshipClassesDialog,
EditRelationshipsDialog,
RemoveEntitiesDialog,
)
from ..mvcmodels.tool_feature_model import ToolFeatureModel
from ..mvcmodels.parameter_value_list_model import ParameterValueListModel
from ..mvcmodels.alternative_scenario_model import AlternativeScenarioModel
from ..mvcmodels.entity_tree_models import ObjectTreeModel, RelationshipTreeModel
from ...spine_db_parcel import SpineDBParcel
[docs]class TreeViewMixin:
"""Provides object and relationship trees for the Spine db editor.
"""
[docs] _object_classes_added = Signal()
[docs] _relationship_classes_added = Signal()
[docs] _object_classes_fetched = Signal()
[docs] _relationship_classes_fetched = Signal()
"""Emitted from fetcher thread, connected to Slots in GUI thread."""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.object_tree_model = ObjectTreeModel(self, self.db_mngr)
self.relationship_tree_model = RelationshipTreeModel(self, self.db_mngr)
self.tool_feature_model = ToolFeatureModel(self, self.db_mngr)
self.alternative_scenario_model = AlternativeScenarioModel(self, self.db_mngr)
self.parameter_value_list_model = ParameterValueListModel(self, self.db_mngr)
# Set models
self.ui.treeView_object.setModel(self.object_tree_model)
self.ui.treeView_relationship.setModel(self.relationship_tree_model)
self.ui.treeView_parameter_value_list.setModel(self.parameter_value_list_model)
self.ui.treeView_alternative_scenario.setModel(self.alternative_scenario_model)
self.ui.treeView_tool_feature.setModel(self.tool_feature_model)
# Connect DS form to view
self.ui.treeView_object.connect_spine_db_editor(self)
self.ui.treeView_relationship.connect_spine_db_editor(self)
self.ui.treeView_parameter_value_list.connect_spine_db_editor(self)
self.ui.treeView_alternative_scenario.connect_spine_db_editor(self)
self.ui.treeView_tool_feature.connect_spine_db_editor(self)
[docs] def connect_signals(self):
"""Connects signals to slots."""
super().connect_signals()
self.ui.treeView_object.tree_selection_changed.connect(self.ui.treeView_relationship.clear_any_selections)
self.ui.treeView_relationship.tree_selection_changed.connect(self.ui.treeView_object.clear_any_selections)
self._object_classes_added.connect(self._expand_object_tree_root_index)
self._relationship_classes_added.connect(self._expand_relationship_tree_root_index)
self._object_classes_added.connect(lambda: self.ui.treeView_object.resizeColumnToContents(0))
self._relationship_classes_added.connect(lambda: self.ui.treeView_relationship.resizeColumnToContents(0))
[docs] def init_models(self):
"""Initializes models."""
super().init_models()
self.object_tree_model.db_maps = self.db_maps
self.relationship_tree_model.db_maps = self.db_maps
self.tool_feature_model.db_maps = self.db_maps
self.alternative_scenario_model.db_maps = self.db_maps
self.parameter_value_list_model.db_maps = self.db_maps
for view in (
self.ui.treeView_tool_feature,
self.ui.treeView_alternative_scenario,
self.ui.treeView_parameter_value_list,
):
view.model().build_tree()
for item in view.model().visit_all():
index = view.model().index_from_item(item)
view.expand(index)
view.resizeColumnToContents(0)
self.object_tree_model.build_tree()
self.relationship_tree_model.build_tree()
@Slot()
[docs] def _expand_object_tree_root_index(self):
qApp.processEvents() # pylint: disable=undefined-variable
self.ui.treeView_object.expand(self.object_tree_model.root_index)
@Slot()
[docs] def _expand_relationship_tree_root_index(self):
qApp.processEvents() # pylint: disable=undefined-variable
self.ui.treeView_relationship.expand(self.relationship_tree_model.root_index)
@Slot("QItemSelection", "QItemSelection")
[docs] def _handle_object_tree_selection_changed(self, selected, deselected):
"""Updates object filter and sets default rows."""
indexes = self.ui.treeView_object.selectionModel().selectedIndexes()
self.object_tree_model.select_indexes(indexes)
self._clear_tree_selections_silently(self.ui.treeView_relationship)
self.set_default_parameter_data(self.ui.treeView_object.currentIndex())
self._update_object_filter()
@Slot("QItemSelection", "QItemSelection")
[docs] def _handle_relationship_tree_selection_changed(self, selected, deselected):
"""Updates relationship filter and sets default rows."""
indexes = self.ui.treeView_relationship.selectionModel().selectedIndexes()
self.relationship_tree_model.select_indexes(indexes)
self._clear_tree_selections_silently(self.ui.treeView_object)
self.set_default_parameter_data(self.ui.treeView_relationship.currentIndex())
self._update_relationship_filter()
@staticmethod
[docs] def _clear_tree_selections_silently(tree_view):
"""Clears the selections on a given abstract item view without emitting any signals."""
selection_model = tree_view.selectionModel()
if selection_model.hasSelection():
selection_model.blockSignals(True)
selection_model.clearSelection()
selection_model.blockSignals(False)
@staticmethod
[docs] def _db_map_items(indexes):
"""Groups items from given tree indexes by db map.
Returns:
dict: lists of dictionary items keyed by DiffDatabaseMapping
"""
d = dict()
for index in indexes:
item = index.model().item_from_index(index)
for db_map in item.db_maps:
d.setdefault(db_map, []).append(item.db_map_data(db_map))
return d
[docs] def _db_map_ids(self, indexes):
return self.db_mngr.db_map_ids(self._db_map_items(indexes))
[docs] def _db_map_class_ids(self, indexes):
return self.db_mngr.db_map_class_ids(self._db_map_items(indexes))
[docs] def export_selected(self, selected_indexes):
"""Exports data from given indexes in the entity tree."""
parcel = SpineDBParcel(self.db_mngr)
obj_cls_inds = set(selected_indexes.get("object_class", {}).keys())
obj_inds = set(selected_indexes.get("object", {}).keys())
rel_cls_inds = set(selected_indexes.get("relationship_class", {}).keys())
rel_inds = set(selected_indexes.get("relationship", {}).keys())
db_map_obj_cls_ids = self._db_map_ids(obj_cls_inds)
db_map_obj_ids = self._db_map_ids(obj_inds)
db_map_rel_cls_ids = self._db_map_ids(rel_cls_inds)
db_map_rel_ids = self._db_map_ids(rel_inds)
parcel.full_push_object_class_ids(db_map_obj_cls_ids)
parcel.full_push_object_ids(db_map_obj_ids)
parcel.full_push_relationship_class_ids(db_map_rel_cls_ids)
parcel.full_push_relationship_ids(db_map_rel_ids)
self.export_data(parcel.data)
[docs] def duplicate_object(self, index):
"""
Duplicates the object at the given object tree model index.
Args:
index (QModelIndex)
"""
object_item = index.internalPointer()
orig_name = object_item.display_data
dup_name, ok = QInputDialog.getText(
self, "Duplicate object", "Enter a name for the duplicate object:", text=orig_name + "_copy"
)
if not ok:
return
parcel = SpineDBParcel(self.db_mngr)
db_map_obj_ids = {db_map: {object_item.db_map_id(db_map)} for db_map in object_item.db_maps}
parcel.inner_push_object_ids(db_map_obj_ids)
self.db_mngr.duplicate_object(object_item.db_maps, parcel.data, orig_name, dup_name)
@Slot(bool)
@Slot(bool)
@Slot(bool)
@Slot(bool)
[docs] def edit_entity_tree_items(self, selected_indexes):
"""Starts editing given indexes."""
obj_cls_items = {ind.internalPointer() for ind in selected_indexes.get("object_class", {})}
obj_items = {ind.internalPointer() for ind in selected_indexes.get("object", {})}
rel_cls_items = {ind.internalPointer() for ind in selected_indexes.get("relationship_class", {})}
rel_items = {ind.internalPointer() for ind in selected_indexes.get("relationship", {})}
self.show_edit_object_classes_form(obj_cls_items)
self.show_edit_objects_form(obj_items)
self.show_edit_relationship_classes_form(rel_cls_items)
self.show_edit_relationships_form(rel_items)
@Slot()
@Slot(dict)
@Slot()
[docs] def update_export_enabled(self):
self.ui.actionExport.setEnabled(self.object_tree_model.root_item.has_children())
[docs] def log_changes(self, action, item_type, db_map_data):
"""Enables or disables actions and informs the user about what just happened."""
super().log_changes(action, item_type, db_map_data)
self.update_export_enabled()
[docs] def receive_alternatives_added(self, db_map_data):
super().receive_alternatives_added(db_map_data)
self.alternative_scenario_model.add_alternatives(db_map_data)
[docs] def receive_scenarios_added(self, db_map_data):
super().receive_scenarios_added(db_map_data)
self.alternative_scenario_model.add_scenarios(db_map_data)
[docs] def receive_object_classes_added(self, db_map_data):
super().receive_object_classes_added(db_map_data)
self.object_tree_model.add_object_classes(db_map_data)
self._object_classes_added.emit()
[docs] def receive_objects_added(self, db_map_data):
super().receive_objects_added(db_map_data)
self.object_tree_model.add_objects(db_map_data)
[docs] def receive_relationship_classes_added(self, db_map_data):
super().receive_relationship_classes_added(db_map_data)
self.object_tree_model.add_relationship_classes(db_map_data)
self.relationship_tree_model.add_relationship_classes(db_map_data)
self._relationship_classes_added.emit()
[docs] def receive_relationships_added(self, db_map_data):
super().receive_relationships_added(db_map_data)
self.object_tree_model.add_relationships(db_map_data)
self.relationship_tree_model.add_relationships(db_map_data)
[docs] def receive_entity_groups_added(self, db_map_data):
super().receive_entity_groups_added(db_map_data)
self.object_tree_model.add_entity_groups(db_map_data)
[docs] def receive_parameter_value_lists_added(self, db_map_data):
super().receive_parameter_value_lists_added(db_map_data)
self.parameter_value_list_model.add_parameter_value_lists(db_map_data)
[docs] def receive_features_added(self, db_map_data):
super().receive_features_added(db_map_data)
self.tool_feature_model.add_features(db_map_data)
[docs] def receive_alternatives_updated(self, db_map_data):
super().receive_alternatives_updated(db_map_data)
self.alternative_scenario_model.update_alternatives(db_map_data)
[docs] def receive_scenarios_updated(self, db_map_data):
super().receive_scenarios_updated(db_map_data)
self.alternative_scenario_model.update_scenarios(db_map_data)
[docs] def receive_object_classes_updated(self, db_map_data):
super().receive_object_classes_updated(db_map_data)
self.object_tree_model.update_object_classes(db_map_data)
[docs] def receive_objects_updated(self, db_map_data):
super().receive_objects_updated(db_map_data)
self.object_tree_model.update_objects(db_map_data)
[docs] def receive_relationship_classes_updated(self, db_map_data):
super().receive_relationship_classes_updated(db_map_data)
self.object_tree_model.update_relationship_classes(db_map_data)
self.relationship_tree_model.update_relationship_classes(db_map_data)
[docs] def receive_relationships_updated(self, db_map_data):
super().receive_relationships_updated(db_map_data)
self.object_tree_model.update_relationships(db_map_data)
self.relationship_tree_model.update_relationships(db_map_data)
[docs] def receive_parameter_value_lists_updated(self, db_map_data):
super().receive_parameter_value_lists_updated(db_map_data)
self.parameter_value_list_model.update_parameter_value_lists(db_map_data)
[docs] def receive_features_updated(self, db_map_data):
super().receive_features_updated(db_map_data)
self.tool_feature_model.update_features(db_map_data)
[docs] def receive_alternatives_removed(self, db_map_data):
super().receive_alternatives_removed(db_map_data)
self.alternative_scenario_model.remove_alternatives(db_map_data)
[docs] def receive_scenarios_removed(self, db_map_data):
super().receive_scenarios_removed(db_map_data)
self.alternative_scenario_model.remove_scenarios(db_map_data)
[docs] def receive_object_classes_removed(self, db_map_data):
super().receive_object_classes_removed(db_map_data)
self.object_tree_model.remove_object_classes(db_map_data)
[docs] def receive_objects_removed(self, db_map_data):
super().receive_objects_removed(db_map_data)
self.object_tree_model.remove_objects(db_map_data)
[docs] def receive_relationship_classes_removed(self, db_map_data):
super().receive_relationship_classes_removed(db_map_data)
self.object_tree_model.remove_relationship_classes(db_map_data)
self.relationship_tree_model.remove_relationship_classes(db_map_data)
[docs] def receive_relationships_removed(self, db_map_data):
super().receive_relationships_removed(db_map_data)
self.object_tree_model.remove_relationships(db_map_data)
self.relationship_tree_model.remove_relationships(db_map_data)
[docs] def receive_entity_groups_removed(self, db_map_data):
super().receive_entity_groups_removed(db_map_data)
self.object_tree_model.remove_entity_groups(db_map_data)
[docs] def receive_parameter_value_lists_removed(self, db_map_data):
super().receive_parameter_value_lists_removed(db_map_data)
self.parameter_value_list_model.remove_parameter_value_lists(db_map_data)
[docs] def receive_features_removed(self, db_map_data):
super().receive_features_removed(db_map_data)
self.tool_feature_model.remove_features(db_map_data)