Skip to content

Commit

Permalink
Merge pull request #87 from jmkerloch/refactor-project-load
Browse files Browse the repository at this point in the history
Use QgsProject to load needed informations for menu creation
  • Loading branch information
xcaeag authored Sep 20, 2024
2 parents e8b3c22 + bfa80bf commit 359d495
Show file tree
Hide file tree
Showing 5 changed files with 523 additions and 424 deletions.
224 changes: 224 additions & 0 deletions menu_from_project/logic/project_read.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
# standard
from dataclasses import dataclass
from typing import Any, List, Optional, Tuple

# PyQGIS
from qgis.PyQt import QtXml
from qgis.core import (
QgsLayerTreeNode,
QgsMapLayerType,
QgsProject,
QgsWkbTypes,
QgsLayerNotesUtils,
QgsReadWriteContext,
QgsMapLayer,
)

# project
from menu_from_project.logic.xml_utils import getFirstChildByTagNameValue


@dataclass
class MenuLayerConfig:
"""Class to store configuration for layer menu creation"""

name: str
layer_id: str
filename: str
visible: bool
expanded: bool
embedded: str
is_spatial: bool
layer_type: QgsMapLayerType
metadata_abstract: str
metadata_title: str
layer_notes: str
abstract: str
title: str
geometry_type: Optional[QgsWkbTypes.GeometryType] = None


@dataclass
class MenuGroupConfig:
"""Class to store configuration for group menu creation"""

name: str
filename: str
childs: List[Any] # List of Union[MenuLayerConfig,MenuGroupConfig]
embedded: bool


@dataclass
class MenuProjectConfig:
"""Class to store configuration for project menu creation"""

filename: str
uri: str
root_group: MenuGroupConfig


def get_embedded_project_from_layer_tree(
layer_tree: QgsLayerTreeNode, project: QgsProject
) -> str:
"""Get embedded project path from layer tree and his parent
:param layer_tree: layer tree to inspect
:type layer_tree: QgsLayerTreeNode
:param project: project where layer tree is used
:type project: QgsProject
:return: path to embedded project
:rtype: str
"""
filename = project.readPath(layer_tree.customProperty("embedded_project"))
if filename == "" and layer_tree.parent():
return get_embedded_project_from_layer_tree(
layer_tree=layer_tree.parent(), project=project
)
return filename


def read_embedded_properties(
layer_tree: QgsLayerTreeNode, project: QgsProject
) -> Tuple[bool, str]:
"""Read embedded properties from a QgsLayerTreeNode in a QgsProject
:param layer_tree: layer tree to inspect
:type layer_tree: QgsLayerTreeNode
:param project: project where layer tree is used
:type project: QgsProject
:return: Boolean indicating if the layer tree is embedded and the filename of the project used
:rtype: Tuple[bool, str]
"""
# Embedded property is not read if QgsProject.FlagDontResolveLayers flag is use when reading QgsProject
if layer_tree.customProperty("embedded"):
embedded = True
filename = get_embedded_project_from_layer_tree(
layer_tree=layer_tree, project=project
)
else:
embedded = False
filename = project.absoluteFilePath()
return embedded, filename


def get_layer_user_notes(layer: QgsMapLayer, doc: QtXml.QDomDocument) -> str:
"""Get layer user notes
:param layer: layer
:type layer: QgsMapLayer
:param doc: xml doc for project
:type doc: QtXml.QDomDocument
:return: layer user notes
:rtype: str
"""
# HACK for layer not vector the layer notes are not available
# see issue https://github.com/qgis/QGIS/issues/58818
# To have the value available we read directly from xml doc
if layer.type() != QgsMapLayerType.VectorLayer:
node = getFirstChildByTagNameValue(
doc.documentElement(), "maplayer", "id", layer.id()
)
layer_notes = ""
elt_user_notes = node.namedItem("userNotes")
if elt_user_notes.toElement().hasAttribute("value"):
layer_notes = elt_user_notes.toElement().attribute("value")
else:
layer_notes = QgsLayerNotesUtils.layerNotes(layer)
return layer_notes


def get_layer_menu_config(
layer_tree: QgsLayerTreeNode, project: QgsProject, doc: QtXml.QDomDocument
) -> MenuLayerConfig:
"""Get layer menu configuration from a QgsLayerTreeNode in a QgsProject
:param layer_tree: layer tree to inspect
:type layer_tree: QgsLayerTreeNode
:param project: project where layer tree is used
:type project: QgsProject
:param doc: xml doc extracted from qgis project
:type doc: QtXml.QDomDocument
:return: Layer menu configuration
:rtype: MenuLayerConfig
"""

embedded, filename = read_embedded_properties(
layer_tree=layer_tree, project=project
)

# Get project map layer
layer = project.mapLayer(layer_tree.layerId())

layer_notes = get_layer_user_notes(layer, doc)

return MenuLayerConfig(
name=layer_tree.name(),
layer_id=layer_tree.layerId(),
filename=filename,
visible=layer_tree.itemVisibilityChecked(),
expanded=layer_tree.isExpanded(),
embedded=embedded,
layer_type=layer.type(),
metadata_abstract=layer.metadata().abstract(),
metadata_title=layer.metadata().title(),
abstract=layer.abstract(),
is_spatial=layer.isSpatial(),
title=layer.title(),
geometry_type=(
layer.geometryType()
if layer.type() == QgsMapLayerType.VectorLayer
else None
),
layer_notes=layer_notes,
)


def get_group_menu_config(
layer_tree: QgsLayerTreeNode, project: QgsProject, doc: QtXml.QDomDocument
) -> MenuGroupConfig:
"""Get group menu configuration from a QgsLayerTreeNode in a QgsProject
:param layer_tree: layer tree to inspect
:type layer_tree: QgsLayerTreeNode
:param project: project where layer tree is used
:type project: QgsProject
:param doc: xml doc extracted from qgis project
:type doc: QtXml.QDomDocument
:return: Group menu configuration
:rtype: MenuGroupConfig
"""
embedded, filename = read_embedded_properties(
layer_tree=layer_tree, project=project
)

childs = []

for child in layer_tree.children():
if child.nodeType() == QgsLayerTreeNode.NodeGroup:
childs.append(get_group_menu_config(child, project, doc))
elif child.nodeType() == QgsLayerTreeNode.NodeLayer:
childs.append(get_layer_menu_config(child, project, doc))
return MenuGroupConfig(
name=layer_tree.name(), embedded=embedded, filename=filename, childs=childs
)


def get_project_menu_config(
qgs_project: QgsProject, uri: str, doc: QtXml.QDomDocument
) -> MenuProjectConfig:
"""Get project menu configuration from a QgsProject
:param qgs_project: project
:type qgs_project: QgsProject
:param uri: initial uri of project (can be from local file / http / postgres)
:type uri: str
:param doc: xml doc extracted from qgis project
:type doc: QtXml.QDomDocument
:return: Project menu configuration
:rtype: MenuProjectConfig
"""
return MenuProjectConfig(
filename=qgs_project.absoluteFilePath(),
uri=uri,
root_group=get_group_menu_config(qgs_project.layerTreeRoot(), qgs_project, doc),
)
18 changes: 0 additions & 18 deletions menu_from_project/logic/qgs_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,24 +59,6 @@ def is_absolute(doc: QtXml.QDomDocument) -> bool:
return absolute


def get_project_title(doc: QtXml.QDomDocument) -> str:
"""Return the project title defined in the XML document.
:param doc: The QGIS project as XML document. Default to None.
:type doc: QDomDocument
:return: The title or None.
:rtype: string
"""
tags = doc.elementsByTagName("qgis")
if tags.count():
node = tags.at(0)
title_node = node.namedItem("title")
return title_node.firstChild().toText().data()

return None


@lru_cache()
def read_from_file(uri: str) -> Tuple[QtXml.QDomDocument, str]:
"""Read a QGIS project (.qgs and .qgz) from a file path and returns d
Expand Down
55 changes: 31 additions & 24 deletions menu_from_project/logic/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
# Standard library
import logging
from functools import lru_cache
from typing import Optional

# PyQGIS
from qgis.core import QgsApplication, QgsLayerItem
from qgis.core import QgsApplication, QgsLayerItem, QgsMapLayerType, QgsWkbTypes
from qgis.PyQt.QtGui import QIcon

# project
from menu_from_project.__about__ import DIR_PLUGIN_ROOT
Expand Down Expand Up @@ -60,33 +62,38 @@ def icon_per_storage_type(type_storage: str) -> str:


@lru_cache()
def icon_per_geometry_type(geometry_type: str):
"""Return the icon for a geometry type.
If not found, it will return the default icon.
:param geometry_type: The geometry as a string.
:type geometry_type: basestring
:return: The icon.
def icon_per_layer_type(
is_spatial: bool,
layer_type: QgsMapLayerType,
geometry_type: Optional[QgsWkbTypes.GeometryType],
) -> QIcon:
"""Return a icon from a layer type
:param is_spatial: true if layer is spatial, false otherwise
:type is_spatial: bool
:param layer_type: layer type
:type layer_type: QgsMapLayerType
:param geometry_type: geometry type if layer is a QgsVectorLayer
:type geometry_type: Optional[QgsWkbTypes.GeometryType]
:return: icon for layer
:rtype: QIcon
"""
geometry_type = geometry_type.lower()
if geometry_type == "raster":
if not is_spatial:
return QgsLayerItem.iconTable()
if layer_type == QgsMapLayerType.RasterLayer:
return QgsLayerItem.iconRaster()
elif geometry_type == "mesh":
elif layer_type == QgsMapLayerType.MeshLayer:
return QgsLayerItem.iconMesh()
elif geometry_type == "vector-tile":
elif layer_type == QgsMapLayerType.VectorTileLayer:
return QgsLayerItem.iconVectorTile()
elif geometry_type == "point-cloud":
elif layer_type == QgsMapLayerType.PointCloudLayer:
return QgsLayerItem.iconPointCloud()
elif geometry_type == "point":
elif layer_type == QgsMapLayerType.VectorLayer:
if geometry_type == QgsWkbTypes.GeometryType.PointGeometry:
return QgsLayerItem.iconPoint()
elif geometry_type == QgsWkbTypes.GeometryType.LineGeometry:
return QgsLayerItem.iconLine()
elif geometry_type == QgsWkbTypes.GeometryType.PolygonGeometry:
return QgsLayerItem.iconPolygon()
return QgsLayerItem.iconPoint()
elif geometry_type == "line":
return QgsLayerItem.iconLine()
elif geometry_type == "polygon":
return QgsLayerItem.iconPolygon()
elif geometry_type == "no geometry":
return QgsLayerItem.iconTable()
else:
return QgsLayerItem.iconDefault()
return QgsLayerItem.iconDefault()
13 changes: 13 additions & 0 deletions menu_from_project/logic/xml_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# PyQGIS
from qgis.PyQt.QtXml import QDomNode


def getFirstChildByTagNameValue(elt, tagName, key, value):
nodes = elt.elementsByTagName(tagName)
for node in (nodes.at(i) for i in range(nodes.size())):
nd = node.namedItem(key)
if nd and value == nd.firstChild().toText().data():
# layer founds
return node

return None
Loading

0 comments on commit 359d495

Please sign in to comment.