From 2a38ccd55490128772da320ba0213f4e85fba72e Mon Sep 17 00:00:00 2001 From: jmkerloch Date: Fri, 29 Nov 2024 12:18:02 +0100 Subject: [PATCH 1/4] feat(layer load): add function load_layer_list for layer list load instead of using group menu --- menu_from_project/logic/layer_load.py | 124 +++++++++------------- menu_from_project/menu_from_project.py | 68 ++++-------- menu_from_project/toolbelt/preferences.py | 41 +++++++ 3 files changed, 113 insertions(+), 120 deletions(-) diff --git a/menu_from_project/logic/layer_load.py b/menu_from_project/logic/layer_load.py index de3a10f..5183e66 100644 --- a/menu_from_project/logic/layer_load.py +++ b/menu_from_project/logic/layer_load.py @@ -17,11 +17,11 @@ ) from qgis.PyQt import QtXml from qgis.PyQt.QtCore import QCoreApplication, QFileInfo, Qt, QUuid -from qgis.PyQt.QtWidgets import QMenu, QWidget from qgis.utils import iface, plugins # project from menu_from_project.__about__ import __title__ +from menu_from_project.datamodel.project_config import MenuLayerConfig from menu_from_project.logic.qgs_manager import ( QgsDomManager, is_absolute, @@ -435,29 +435,26 @@ def buildRelations( return targetRelations + relationsToBuild - def loadLayer( - self, - uri: Optional[str], - fileName: Optional[str], - layerId: Optional[str], - menu: Optional[QMenu] = None, - visible: Optional[bool] = None, - expanded: Optional[bool] = None, - ): + def load_layer_list( + self, layer_config_list: List[MenuLayerConfig], group_name: str + ) -> None: + """Load a list of layer to current QgsProject + + :param layer_config_list: menu layer configuration list + :type layer_config_list: List[MenuLayerConfig] + :param group_name: group name in case of create group option + :type group_name: str + """ + for layer_config in layer_config_list: + self.load_layer(layer_config=layer_config, group_name=group_name) + + def load_layer(self, layer_config: MenuLayerConfig, group_name: str) -> None: """Load layer to current QgsProject - :param uri: The layer URI (file path or PG URI) - :type uri: Optional[str] - :param fileName: path to QgsProject file, None for Load all option - :type fileName: Optional[str] - :param layerId: id of layer to load (from QgsProject file), None for Load all option - :type layerId: Optional[str] - :param menu: QMenu where the action is located, defaults to None - :type menu: Optional[QMenu], optional - :param visible: define layer visibility in layer tree, defaults to None - :type visible: Optional[bool], optional - :param expanded: define if layer is expanded in layer tree, defaults to None - :type expanded: Optional[bool], optional + :param layer_config: configuration of layer to load + :type layer_config: MenuLayerConfig + :param group_name: group name in case of create group option + :type group_name: str """ self.canvas.freeze(True) self.canvas.setRenderFlag(False) @@ -468,60 +465,45 @@ def loadLayer( settings = self.plg_settings.get_plg_settings() try: - if ( - menu - and isinstance(menu.parentWidget(), (QMenu, QWidget)) - and settings.optionCreateGroup - ): - groupName = menu.title().replace("&", "") - group = QgsProject.instance().layerTreeRoot().findGroup(groupName) + if settings.optionCreateGroup: + group = QgsProject.instance().layerTreeRoot().findGroup(group_name) if group is None: group = ( - QgsProject.instance().layerTreeRoot().insertGroup(0, groupName) + QgsProject.instance().layerTreeRoot().insertGroup(0, group_name) ) + doc, _ = self.qgs_dom_manager.getQgsDoc(layer_config.filename) + + # Loading layer + layer, relationsToBuild = self.addLayer( + layer_config.filename, + doc, + layer_config.layer_id, + group, + layer_config.visible, + layer_config.expanded, + {}, + 0, + ) + for relDict in relationsToBuild: + self.buildProjectRelation(doc, relDict) - # load all layers - if fileName is None and layerId is None and settings.optionLoadAll: - for action in menu.actions()[::-1]: - if ( - action.text() != self.tr("Load all") - and action.text() != "Load all" - ): - action.trigger() - else: - doc, _ = self.qgs_dom_manager.getQgsDoc(fileName) - - # Loading layer - layer, relationsToBuild = self.addLayer( - uri, doc, layerId, group, visible, expanded, {}, 0 - ) - for relDict in relationsToBuild: - self.buildProjectRelation(doc, relDict) - - # is joined layers exists ? - if ( - settings.optionOpenLinks - and layer - and isinstance(layer, QgsVectorLayer) - ): - for j in layer.vectorJoins(): - try: - joinLayer, joinRelations = self.addLayer( - uri, doc, j.joinLayerId(), group - ) - for relDict in joinRelations: - self.buildProjectRelation(doc, relDict) - - if joinLayer: - j.setJoinLayerId(joinLayer.id()) - j.setJoinLayer(joinLayer) - layer.addJoin(j) - except Exception: - self.log( - "Joined layer {} not added.".format(j.joinLayerId()) - ) - pass + # is joined layers exists ? + if settings.optionOpenLinks and layer and isinstance(layer, QgsVectorLayer): + for j in layer.vectorJoins(): + try: + joinLayer, joinRelations = self.addLayer( + layer_config.filename, doc, j.joinLayerId(), group + ) + for relDict in joinRelations: + self.buildProjectRelation(doc, relDict) + if joinLayer: + j.setJoinLayerId(joinLayer.id()) + j.setJoinLayer(joinLayer) + layer.addJoin(j) + except Exception: + self.log("Joined layer {} not added.".format(j.joinLayerId())) + pass except Exception as e: # fixme fileName is not defined # self.log( diff --git a/menu_from_project/menu_from_project.py b/menu_from_project/menu_from_project.py index 19e3188..76b8d97 100644 --- a/menu_from_project/menu_from_project.py +++ b/menu_from_project/menu_from_project.py @@ -48,12 +48,7 @@ read_from_http, ) from menu_from_project.logic.tools import icon_per_layer_type -from menu_from_project.toolbelt.preferences import ( - SOURCE_MD_LAYER, - SOURCE_MD_NOTE, - SOURCE_MD_OGC, - PlgOptionsManager, -) +from menu_from_project.toolbelt.preferences import PlgOptionsManager from menu_from_project.ui.menu_conf_dlg import MenuConfDialog # noqa: F4 I001 # ############################################################################ @@ -246,7 +241,9 @@ def create_project_menu( self.menubarActions.append(project_action) return project_menu - def add_group_childs(self, group: MenuGroupConfig, grp_menu: QMenu) -> bool: + def add_group_childs( + self, group: MenuGroupConfig, grp_menu: QMenu + ) -> List[MenuLayerConfig]: """Add all childs of a group config :param uri: initial uri of project (can be from local file / http / postgres) @@ -255,16 +252,16 @@ def add_group_childs(self, group: MenuGroupConfig, grp_menu: QMenu) -> bool: :type group: MenuGroupConfig :param grp_menu: menu for group :type grp_menu: QMenu - :return: True if a layer was inserted, False otherwise - :rtype: bool + :return: list of inserted layer configuration + :rtype: List[MenuLayerConfig] """ - layer_inserted = False + layer_inserted = [] for child in group.childs: if isinstance(child, MenuGroupConfig): self.add_group(child, grp_menu) elif isinstance(child, MenuLayerConfig): - layer_inserted = True - self.add_layer(child, grp_menu) + layer_inserted.append(child) + self.add_layer(child, grp_menu, group.name, child.name) return layer_inserted def add_group(self, group: MenuGroupConfig, menu: QMenu) -> None: @@ -300,69 +297,42 @@ def add_group(self, group: MenuGroupConfig, menu: QMenu) -> None: layer_inserted = self.add_group_childs(group=group, grp_menu=grp_menu) - if layer_inserted and settings.optionLoadAll: + if len(layer_inserted) and settings.optionLoadAll: action = QAction(self.tr("Load all"), self.iface.mainWindow()) font = QFont() font.setBold(True) action.setFont(font) grp_menu.addAction(action) action.triggered.connect( - lambda checked, f=None, w=None, m=grp_menu: LayerLoad().loadLayer( - None, f, w, m - ) + lambda checked: LayerLoad().load_layer_list(layer_inserted, name) ) - def add_layer(self, layer: MenuLayerConfig, menu: QMenu) -> None: + def add_layer( + self, layer: MenuLayerConfig, menu: QMenu, group_name: str, action_text: str + ) -> None: """Add layer menu configuration to a menu :param uri: initial uri of project (can be from local file / http / postgres) :type uri: str :param layer: layer menu configuration :type layer: MenuLayerConfig + :param group_name: group name in case of create group option + :type group_name: str :param menu: input menu :type menu: QMenu """ settings = self.plg_settings.get_plg_settings() - action = QAction(layer.name, self.iface.mainWindow()) + action = QAction(action_text, self.iface.mainWindow()) # add menu item action.triggered.connect( - lambda checked, uri=layer.filename, f=layer.filename, lid=layer.layer_id, m=menu, v=layer.visible, x=layer.expanded: LayerLoad().loadLayer( - uri, f, lid, m, v, x - ) + lambda checked: LayerLoad().load_layer(layer, group_name) ) action.setIcon( icon_per_layer_type(layer.is_spatial, layer.layer_type, layer.geometry_type) ) if settings.optionTooltip: - if settings.optionSourceMD == SOURCE_MD_OGC: - abstract = layer.abstract or layer.metadata_abstract - title = layer.title or layer.metadata_title - else: - abstract = layer.metadata_abstract or layer.abstract - title = layer.metadata_title or layer.title - - abstract = "" - title = "" - for oSource in settings.optionSourceMD: - if oSource == SOURCE_MD_OGC: - abstract = layer.metadata_abstract if abstract == "" else abstract - title = title or layer.metadata_title - - if oSource == SOURCE_MD_LAYER: - abstract = layer.abstract if abstract == "" else abstract - title = title or layer.title - - if oSource == SOURCE_MD_NOTE: - abstract = layer.layer_notes if abstract == "" else abstract - - if (abstract != "") and (title == ""): - action.setToolTip("

{}

".format(abstract)) - else: - if abstract != "" or title != "": - action.setToolTip("{}
{}".format(title, abstract)) - else: - action.setToolTip("") + action.setToolTip(settings.tooltip_for_layer(layer)) menu.addAction(action) diff --git a/menu_from_project/toolbelt/preferences.py b/menu_from_project/toolbelt/preferences.py index e7cd52e..525d587 100644 --- a/menu_from_project/toolbelt/preferences.py +++ b/menu_from_project/toolbelt/preferences.py @@ -16,6 +16,7 @@ # package from menu_from_project.__about__ import __version__ from menu_from_project.datamodel.project import Project, ProjectCacheConfig +from menu_from_project.datamodel.project_config import MenuLayerConfig from menu_from_project.logic.tools import guess_type_from_uri # ############################################################################ @@ -50,6 +51,46 @@ class PlgSettingsStructure: # Internal option is_setup_visible: bool = True + def tooltip_for_layer(self, layer_config: MenuLayerConfig) -> str: + """Define tooltip from layer configuration and current settings + + :param layer_config: layer configuration + :type layer_config: MenuLayerConfig + :return: tooltip + :rtype: str + """ + if self.optionSourceMD == SOURCE_MD_OGC: + abstract = layer_config.abstract or layer_config.metadata_abstract + title = layer_config.title or layer_config.metadata_title + else: + abstract = layer_config.metadata_abstract or layer_config.abstract + title = layer_config.metadata_title or layer_config.title + + abstract = "" + title = "" + for oSource in self.optionSourceMD: + if oSource == SOURCE_MD_OGC: + abstract = ( + layer_config.metadata_abstract if abstract == "" else abstract + ) + title = title or layer_config.metadata_title + + if oSource == SOURCE_MD_LAYER: + abstract = layer_config.abstract if abstract == "" else abstract + title = title or layer_config.title + + if oSource == SOURCE_MD_NOTE: + abstract = layer_config.layer_notes if abstract == "" else abstract + + if (abstract != "") and (title == ""): + tooltip = "

{}

".format(abstract) + else: + if abstract != "" or title != "": + tooltip = "{}
{}".format(title, abstract) + else: + tooltip = "" + return tooltip + class PlgOptionsManager: @staticmethod From 07e973e2e6856c1f129b1f41ed8ba576720b4e8e Mon Sep 17 00:00:00 2001 From: jmkerloch Date: Fri, 29 Nov 2024 12:20:14 +0100 Subject: [PATCH 2/4] wip(browser): init browser use with a custom QgsDataItemProvider --- menu_from_project/menu_from_project.py | 13 + menu_from_project/ui/browser.py | 344 +++++++++++++++++++++++++ 2 files changed, 357 insertions(+) create mode 100644 menu_from_project/ui/browser.py diff --git a/menu_from_project/menu_from_project.py b/menu_from_project/menu_from_project.py index 76b8d97..853b88e 100644 --- a/menu_from_project/menu_from_project.py +++ b/menu_from_project/menu_from_project.py @@ -49,6 +49,7 @@ ) from menu_from_project.logic.tools import icon_per_layer_type from menu_from_project.toolbelt.preferences import PlgOptionsManager +from menu_from_project.ui.browser import MenuLayerProvider from menu_from_project.ui.menu_conf_dlg import MenuConfDialog # noqa: F4 I001 # ############################################################################ @@ -93,6 +94,9 @@ def __init__(self, iface): self.action_project_configuration = None self.action_menu_help = None + self.registry = QgsApplication.instance().dataItemProviderRegistry() + self.provider = None + @staticmethod def tr(message): return QCoreApplication.translate("MenuFromProject", message) @@ -173,9 +177,15 @@ def project_config_loaded( """ QgsApplication.setOverrideCursor(Qt.CursorShape.WaitCursor) previous = None + project_config_list = [] for project, project_config in project_configs: # Add to QGIS instance + project_config_list.append(project_config) previous = self.add_project_config(project, project_config, previous) + if self.provider: + self.registry.removeProvider(self.provider) + self.provider = MenuLayerProvider(project_config_list) + self.registry.addProvider(self.provider) QgsApplication.restoreOverrideCursor() @@ -395,6 +405,9 @@ def unload(self): self.iface.initializationCompleted.disconnect(self.on_initializationCompleted) + if self.provider: + self.registry.removeProvider(self.provider) + def open_projects_config(self): dlg = MenuConfDialog(self.iface.mainWindow()) dlg.setModal(True) diff --git a/menu_from_project/ui/browser.py b/menu_from_project/ui/browser.py new file mode 100644 index 0000000..61a16bd --- /dev/null +++ b/menu_from_project/ui/browser.py @@ -0,0 +1,344 @@ +# Standard library +import os.path +from typing import Dict, List + +# PyQGIS +from qgis.core import ( + QgsApplication, + QgsDataCollectionItem, + QgsDataItem, + QgsDataItemProvider, + QgsDataProvider, +) +from qgis.PyQt.QtGui import QIcon +from qgis.PyQt.QtWidgets import QAction, QMenu, QWidget + +# project +from menu_from_project.logic.layer_load import LayerLoad +from menu_from_project.logic.project_read import ( + MenuGroupConfig, + MenuLayerConfig, + MenuProjectConfig, +) +from menu_from_project.logic.tools import icon_per_layer_type +from menu_from_project.toolbelt.preferences import PlgOptionsManager + + +class MenuLayerProvider(QgsDataItemProvider): + """Provider for plugin data item""" + + def __init__(self, project_configs: List[MenuProjectConfig]): + """Constructor for provider + + :param project_configs: list of project configuration + :type project_configs: List[MenuProjectConfig] + """ + QgsDataItemProvider.__init__(self) + self.project_configs = project_configs + + def name(self) -> str: + """Human readable name + + :return: name of item + :rtype: str + """ + return "Layer from project" + + def capabilities(self) -> int: + """Returns combination of flags from QgsDataProvider::DataCapabilities. + + :return: item data capabilities + :rtype: int + """ + return QgsDataProvider.Net + + def createDataItem(self, path: str, parentItem: QgsDataItem) -> QgsDataItem: + """Create root collection for provider + + :param path: current path (unused) + :type path: str + :param parentItem: parent + :type parentItem: QgsDataItem + :return: RootCollection data item + :rtype: QgsDataItem + """ + return RootCollection(parent=parentItem, project_configs=self.project_configs) + + +class RootCollection(QgsDataCollectionItem): + """QgsDataCollectionItem to add available project as children""" + + def __init__(self, parent: QgsDataItem, project_configs: List[MenuProjectConfig]): + """_summary_ + + :param parent: parent + :type parent: QgsDataItem + :param project_configs: list of project configuration + :type project_configs: List[MenuProjectConfig] + """ + QgsDataCollectionItem.__init__(self, parent, "MenuLayer", "/MenuLayer") + # TODO : define icon + self.project_configs = project_configs + + def createChildren(self) -> List[QgsDataItem]: + """Create children for each project + + :return: QgsDataItem for each project + :rtype: List[QgsDataItem] + """ + children = [] + for pfc in [ + ProjectCollection(parent=self, project_menu_config=project_config) + for project_config in self.project_configs + ]: + children.append(pfc) + return children + + +class ProjectCollection(QgsDataCollectionItem): + """QgsDataCollectionItem to add all group and layer available in a project""" + + def __init__(self, parent: QgsDataItem, project_menu_config: MenuProjectConfig): + """Constructor for a project QgsDataCollectionItem + + :param parent: parent + :type parent: QgsDataItem + :param project_menu_config: project configuration + :type project_menu_config: MenuProjectConfig + """ + self.path = "/MenuLayer/" + project_menu_config.project_name.lower() + self.parent = parent + QgsDataCollectionItem.__init__( + self, parent, project_menu_config.project_name, self.path + ) + self.project_menu_config = project_menu_config + self.setName(project_menu_config.project_name) + self.setIcon(QIcon(QgsApplication.iconPath("mIconFolderProject.svg"))) + + def createChildren(self) -> List[QgsDataItem]: + """Create children for all group and layer available in project + + :return: QgsDataItem for each project + :rtype: List[QgsDataItem] + """ + root_group = GroupItem( + parent=self, group_config=self.project_menu_config.root_group + ) + return root_group.createChildren() + + +class GroupItem(QgsDataCollectionItem): + """QgsDataCollectionItem to add all group and layer available in a group""" + + def __init__(self, parent: QgsDataItem, group_config: MenuGroupConfig): + """Constructor for a group QgsDataCollectionItem + + :param parent: parent + :type parent: QgsDataItem + :param group_config: group configuration + :type group_config: MenuGroupConfig + """ + self.path = os.path.join(parent.path, group_config.name) + self.group_config = group_config + QgsDataCollectionItem.__init__(self, parent, group_config.name, self.path) + self.setIcon(QIcon(QgsApplication.iconPath("mIconFolder.svg"))) + + self.layer_inserted = [] + + def createChildren(self) -> List[QgsDataItem]: + """Create children for all group and layer available in a group + + :return: QgsDataItem for each project + :rtype: List[QgsDataItem] + """ + children = [] + self.layer_inserted = [] + for child in self.group_config.childs: + if isinstance(child, MenuGroupConfig): + children.insert(0, GroupItem(parent=self, group_config=child)) + elif isinstance(child, MenuLayerConfig): + self.layer_inserted.append(child) + children.insert( + 0, + LayerItem( + parent=self, + layer_config=child, + group_name=self.group_config.name, + ), + ) + return children + + def actions(self, parent: QWidget) -> List[QAction]: + """Return list of available actions for layer + + :param parent: parent + :type parent: QWidget + :return: list of available actions + :rtype: List[QAction] + """ + settings = PlgOptionsManager().get_plg_settings() + + if len(self.layer_inserted) != 0 and settings.optionLoadAll: + ac_show_layer = QAction(self.tr("Load all"), parent) + ac_show_layer.triggered.connect(self._add_layer_inserted) + return [ac_show_layer] + return [] + + def _add_layer_inserted(self) -> None: + """Add inserted layers to current QGIS project""" + LayerLoad().load_layer_list(self.layer_inserted, self.group_config.name) + + +def create_add_layer_action( + layer: MenuLayerConfig, action_text: str, group_name: str, parent: QWidget +) -> QAction: + action = QAction(action_text, parent) + action.triggered.connect(lambda checked: LayerLoad().load_layer(layer, group_name)) + settings = PlgOptionsManager().get_plg_settings() + + if settings.optionTooltip: + action.setToolTip(settings.tooltip_for_layer(layer)) + + action.setIcon( + icon_per_layer_type( + is_spatial=layer.is_spatial, + layer_type=layer.layer_type, + geometry_type=layer.geometry_type, + ) + ) + return action + + +class LayerDictItem(QgsDataItem): + """QgsDataCollectionItem to add all group and layer available in a group""" + + def __init__( + self, + parent: QgsDataItem, + layer_dict: Dict[str, Dict[str, MenuLayerConfig]], + group_name: str, + ): + """Constructor for a group QgsDataCollectionItem + + :param parent: parent + :type parent: QgsDataItem + :param group_config: group configuration + :type group_config: MenuGroupConfig + """ + self.first_layer = list(list(layer_dict.values())[0].values())[0] + self.path = os.path.join(parent.path, self.first_layer.name) + self.layer_dict = layer_dict + self.group_name = group_name + QgsDataItem.__init__( + self, QgsDataItem.Custom, parent, self.first_layer.name, self.path + ) + self.setState(QgsDataItem.Populated) # no children + + settings = PlgOptionsManager().get_plg_settings() + + if settings.optionTooltip: + self.setToolTip(settings.tooltip_for_layer(self.first_layer)) + self.setIcon( + icon_per_layer_type( + is_spatial=self.first_layer.is_spatial, + layer_type=self.first_layer.layer_type, + geometry_type=self.first_layer.geometry_type, + ) + ) + + def handleDoubleClick(self) -> None: + """Load layer at double click""" + LayerLoad().load_layer(self.first_layer, self.group_name) + return True + + def actions(self, parent: QWidget) -> List[QAction]: + """Return list of available actions for layer + + :param parent: parent + :type parent: QWidget + :return: list of available actions + :rtype: List[QAction] + """ + actions = [] + actions.append( + create_add_layer_action( + self.first_layer, self.tr("Display layer"), self.group_name, parent + ) + ) + + for version, format_dict in self.layer_dict.items(): + if len(format_dict) > 1: + ac_version = QAction(version, parent) + version_menu = QMenu(version, parent) + ac_version.setMenu(version_menu) + actions.append(ac_version) + else: + version_menu = None + for format_, layer in format_dict.items(): + if len(format_dict) > 1: + action_text = format_ + else: + action_text = f"{layer.version} - {layer.format}" + ac_layer = create_add_layer_action( + layer, action_text, self.group_name, parent + ) + if version_menu: + version_menu.addAction(ac_layer) + else: + actions.append(ac_layer) + return actions + + +class LayerItem(QgsDataItem): + """QgsDataItem for layer""" + + def __init__( + self, parent: QgsDataItem, layer_config: MenuLayerConfig, group_name: str + ): + """Constructor for a QgsDataItem to display layer configuration + + :param parent: parent + :type parent: QgsDataItem + :param layer_config: layer configuration + :type layer_config: MenuLayerConfig + :param group_name: group name + :type group_name: str + """ + self.layer_config = layer_config + self.group_name = group_name + self.path = os.path.join(parent.path, layer_config.name) + QgsDataItem.__init__( + self, QgsDataItem.Custom, parent, layer_config.name, self.path + ) + self.setState(QgsDataItem.Populated) # no children + + settings = PlgOptionsManager().get_plg_settings() + + if settings.optionTooltip: + self.setToolTip(settings.tooltip_for_layer(layer_config)) + self.setIcon( + icon_per_layer_type( + is_spatial=self.layer_config.is_spatial, + layer_type=self.layer_config.layer_type, + geometry_type=self.layer_config.geometry_type, + ) + ) + + def handleDoubleClick(self) -> None: + """Load layer at double click""" + LayerLoad().load_layer(self.layer_config, self.group_name) + return True + + def actions(self, parent: QWidget) -> List[QAction]: + """Return list of available actions for layer + + :param parent: parent + :type parent: QWidget + :return: list of available actions + :rtype: List[QAction] + """ + return [ + create_add_layer_action( + self.layer_config, self.tr("Display layer"), self.group_name, parent + ) + ] From d92923ed372bffc440106cf239553cd17cc2b635 Mon Sep 17 00:00:00 2001 From: jmkerloch Date: Fri, 20 Dec 2024 09:10:02 +0100 Subject: [PATCH 3/4] feat(browser): add option in settings for browser use --- menu_from_project/menu_from_project.py | 28 ++++++++++-------- menu_from_project/ui/browser.py | 41 +++++++++++++++++++------- menu_from_project/ui/menu_conf_dlg.py | 8 ++++- 3 files changed, 54 insertions(+), 23 deletions(-) diff --git a/menu_from_project/menu_from_project.py b/menu_from_project/menu_from_project.py index 853b88e..8288b53 100644 --- a/menu_from_project/menu_from_project.py +++ b/menu_from_project/menu_from_project.py @@ -166,7 +166,7 @@ def load_all_project_config( return result def project_config_loaded( - self, exception: Any, project_configs: List[Tuple[Any, MenuProjectConfig]] + self, exception: Any, project_configs: List[Tuple[Project, MenuProjectConfig]] ) -> None: """Add menu after project configuration load @@ -176,17 +176,17 @@ def project_config_loaded( :type project_configs: List[Tuple[Any, MenuProjectConfig]] """ QgsApplication.setOverrideCursor(Qt.CursorShape.WaitCursor) + + if self.provider: + self.registry.removeProvider(self.provider) + self.provider = MenuLayerProvider(project_configs) + previous = None - project_config_list = [] for project, project_config in project_configs: # Add to QGIS instance - project_config_list.append(project_config) previous = self.add_project_config(project, project_config, previous) - if self.provider: - self.registry.removeProvider(self.provider) - self.provider = MenuLayerProvider(project_config_list) - self.registry.addProvider(self.provider) + self.registry.addProvider(self.provider) QgsApplication.restoreOverrideCursor() def add_project_config( @@ -194,7 +194,7 @@ def add_project_config( project: Project, project_config: MenuProjectConfig, previous: Optional[QMenu], - ) -> QMenu: + ) -> Optional[QMenu]: """Add a project menu configuration to current QGIS instance :param menu_name: Name of the menu to create @@ -211,13 +211,14 @@ def add_project_config( project_menu = self.create_project_menu( menu_name=project_config.project_name, project=project, previous=previous ) - self.add_group_childs(project_config.root_group, project_menu) + if project_menu: + self.add_group_childs(project_config.root_group, project_menu) return project_menu def create_project_menu( self, menu_name: str, project: Project, previous: Optional[QMenu] - ) -> QMenu: + ) -> Optional[QMenu]: """Create project menu and add it to QGIS instance :param menu_name: Name of the menu to create @@ -227,13 +228,14 @@ def create_project_menu( :param previous: previous created menu :type previous: Optional[QMenu] :return: created menu - :rtype: QMenu + :rtype: Optional[QMenu] """ + project_menu = None location = project.location if location == "merge" and previous: project_menu = previous project_menu.addSeparator() - else: + elif location in ["layer", "new"]: if location == "layer": menu_bar = self.iface.addLayerMenu() if location == "new": @@ -249,6 +251,8 @@ def create_project_menu( self.layerMenubarActions.append(project_action) if location == "new": self.menubarActions.append(project_action) + else: + project_menu = None return project_menu def add_group_childs( diff --git a/menu_from_project/ui/browser.py b/menu_from_project/ui/browser.py index 61a16bd..d326057 100644 --- a/menu_from_project/ui/browser.py +++ b/menu_from_project/ui/browser.py @@ -1,6 +1,6 @@ # Standard library import os.path -from typing import Dict, List +from typing import Dict, List, Tuple # PyQGIS from qgis.core import ( @@ -14,6 +14,7 @@ from qgis.PyQt.QtWidgets import QAction, QMenu, QWidget # project +from menu_from_project.datamodel.project import Project from menu_from_project.logic.layer_load import LayerLoad from menu_from_project.logic.project_read import ( MenuGroupConfig, @@ -27,7 +28,7 @@ class MenuLayerProvider(QgsDataItemProvider): """Provider for plugin data item""" - def __init__(self, project_configs: List[MenuProjectConfig]): + def __init__(self, project_configs: List[Tuple[Project, MenuProjectConfig]]): """Constructor for provider :param project_configs: list of project configuration @@ -68,13 +69,17 @@ def createDataItem(self, path: str, parentItem: QgsDataItem) -> QgsDataItem: class RootCollection(QgsDataCollectionItem): """QgsDataCollectionItem to add available project as children""" - def __init__(self, parent: QgsDataItem, project_configs: List[MenuProjectConfig]): + def __init__( + self, + parent: QgsDataItem, + project_configs: List[Tuple[Project, MenuProjectConfig]], + ): """_summary_ :param parent: parent :type parent: QgsDataItem :param project_configs: list of project configuration - :type project_configs: List[MenuProjectConfig] + :type project_configs: List[Tuple[Project, MenuProjectConfig]] """ QgsDataCollectionItem.__init__(self, parent, "MenuLayer", "/MenuLayer") # TODO : define icon @@ -87,11 +92,20 @@ def createChildren(self) -> List[QgsDataItem]: :rtype: List[QgsDataItem] """ children = [] - for pfc in [ - ProjectCollection(parent=self, project_menu_config=project_config) - for project_config in self.project_configs - ]: - children.append(pfc) + previous = None + for project, project_config in self.project_configs: + if project.location == "merge" and previous: + pfc = ProjectCollection( + parent=previous, project_menu_config=project_config + ) + previous.merged_project.append(pfc) + elif project.location == "browser": + previous = ProjectCollection( + parent=self, project_menu_config=project_config + ) + children.append(previous) + else: + previous = None return children @@ -115,6 +129,8 @@ def __init__(self, parent: QgsDataItem, project_menu_config: MenuProjectConfig): self.setName(project_menu_config.project_name) self.setIcon(QIcon(QgsApplication.iconPath("mIconFolderProject.svg"))) + self.merged_project = [] + def createChildren(self) -> List[QgsDataItem]: """Create children for all group and layer available in project @@ -124,7 +140,12 @@ def createChildren(self) -> List[QgsDataItem]: root_group = GroupItem( parent=self, group_config=self.project_menu_config.root_group ) - return root_group.createChildren() + children = root_group.createChildren() + + for project in self.merged_project: + children.extend(project.createChildren()) + + return children class GroupItem(QgsDataCollectionItem): diff --git a/menu_from_project/ui/menu_conf_dlg.py b/menu_from_project/ui/menu_conf_dlg.py index b1b40b1..651e49e 100644 --- a/menu_from_project/ui/menu_conf_dlg.py +++ b/menu_from_project/ui/menu_conf_dlg.py @@ -97,8 +97,14 @@ def __init__(self, parent): "index": 1, "label": QgsApplication.translate("ConfDialog", "Add layer menu", None), }, - "merge": { + "browser": { "index": 2, + "label": QgsApplication.translate( + "ConfDialog", "Add browser menu", None + ), + }, + "merge": { + "index": 3, "label": QgsApplication.translate( "ConfDialog", "Merge with previous", None ), From 836eedbf8d2017a22eb6bfef7ceba3011524a79f Mon Sep 17 00:00:00 2001 From: jmkerloch Date: Fri, 20 Dec 2024 09:12:43 +0100 Subject: [PATCH 4/4] feat(browser): use explicite file name for QgsDataItemProvider --- menu_from_project/menu_from_project.py | 2 +- .../ui/{browser.py => menu_layer_data_item_provider.py} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename menu_from_project/ui/{browser.py => menu_layer_data_item_provider.py} (100%) diff --git a/menu_from_project/menu_from_project.py b/menu_from_project/menu_from_project.py index 8288b53..cc097a5 100644 --- a/menu_from_project/menu_from_project.py +++ b/menu_from_project/menu_from_project.py @@ -49,8 +49,8 @@ ) from menu_from_project.logic.tools import icon_per_layer_type from menu_from_project.toolbelt.preferences import PlgOptionsManager -from menu_from_project.ui.browser import MenuLayerProvider from menu_from_project.ui.menu_conf_dlg import MenuConfDialog # noqa: F4 I001 +from menu_from_project.ui.menu_layer_data_item_provider import MenuLayerProvider # ############################################################################ # ########## Classes ############### diff --git a/menu_from_project/ui/browser.py b/menu_from_project/ui/menu_layer_data_item_provider.py similarity index 100% rename from menu_from_project/ui/browser.py rename to menu_from_project/ui/menu_layer_data_item_provider.py