Source code for qgis_macros.utils

#  Copyright (c) 2025-2026 macro-qgis-plugin contributors.
#
#
#  This file is part of macro-qgis-plugin.
#
#  macro-qgis-plugin is free software: you can redistribute it and/or
#  modify it under the terms of the GNU General Public License as published
#  by the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  macro-qgis-plugin 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 General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with macro-qgis-plugin. If not, see <https://www.gnu.org/licenses/>.

"""Utility functions for widget lookup, event position handling, and Qt compat."""

from collections.abc import Iterator
from typing import (
    TYPE_CHECKING,
    cast,
)

from qgis.PyQt.QtCore import QObject, QPoint
from qgis.PyQt.QtGui import QMouseEvent, QWheelEvent
from qgis.PyQt.QtWidgets import QAbstractButton, QWidget
from qgis.utils import iface as iface_

if TYPE_CHECKING:
    from qgis.gui import QgisInterface

iface = cast("QgisInterface", iface_)


[docs] def is_object_map_canvas(obj: QObject) -> bool: """Return True if *obj* is the map canvas viewport widget.""" return obj == iface.mapCanvas().viewport()
[docs] def get_widget_text(widget: QWidget) -> str: """Return the display text of *widget*, falling back to its object name.""" text = "" if isinstance(widget, QAbstractButton): text = widget.text() return text or widget.objectName()
[docs] def get_sibling_index(widget: QWidget, parent: QWidget) -> int: """Get the index of a widget among its same-class siblings in the parent.""" widget_class = widget.__class__.__name__ same_class_siblings = [ child for child in parent.findChildren(QWidget) if child.__class__.__name__ == widget_class and child.parentWidget() is parent ] for i, sibling in enumerate(same_class_siblings): if sibling is widget: return i return 0
[docs] def find_nearest_visible_children_of_type( target_point: QPoint, parent_widget: QWidget, widget_class: str ) -> Iterator[QWidget]: """Yield visible children of *widget_class* sorted by distance to *target_point*.""" widgets = find_nearest_visible_children_with_threshold(target_point, parent_widget) return (widget for widget in widgets if widget.__class__.__name__ == widget_class)
[docs] def find_nearest_visible_children_with_threshold( target_point: QPoint, parent_widget: QWidget ) -> Iterator[QWidget]: """Yield all visible descendants of *parent_widget* sorted by distance.""" nearest_visible_children = set() def distance_to_widget(widget: QWidget) -> int: widget_center = widget.geometry().center() # Calculate the Euclidean distance (squared) return (target_point.x() - widget_center.x()) ** 2 + ( target_point.y() - widget_center.y() ) ** 2 def find_recursive(widget: QWidget) -> None: for child in widget.findChildren(QWidget): if child.isVisible(): nearest_visible_children.add((child, (distance_to_widget(child)))) find_recursive(child) # Start the recursive search from the parent widget find_recursive(parent_widget) # Sort the results by distance return (child[0] for child in sorted(nearest_visible_children, key=lambda x: x[1]))
[docs] def enum_value(enum_or_flag: object) -> int: """Convert a Qt enum/flag to int, compatible with both PyQt5 and PyQt6.""" if hasattr(enum_or_flag, "value"): return enum_or_flag.value # type: ignore[union-attr] return int(enum_or_flag) # type: ignore[arg-type,call-overload]
[docs] def event_pos(event: QMouseEvent | QWheelEvent) -> QPoint: """Get local position from a mouse/wheel event (PyQt5/6 compatible).""" if hasattr(event, "position"): return event.position().toPoint() return event.pos()
[docs] def event_global_pos(event: QMouseEvent | QWheelEvent) -> QPoint: """Get global position from a mouse/wheel event (PyQt5/6 compatible).""" if hasattr(event, "globalPosition"): return event.globalPosition().toPoint() return event.globalPos()