Source code for wolfhece._viewer_plugin_handlers

"""
Per-action mouse handlers for WolfMapViewer.

Rationale
---------
``On_Mouse_Right_Down`` and ``On_Mouse_Motion`` were monolithic catch-all
chains of ``if/elif`` blocks — one branch per action.  This module extracts
those branches into individual handler functions and exposes two dispatch
tables:

* ``ACTION_RDOWN_HANDLERS``   — called on right mouse-button press.
* ``ACTION_MOTION_HANDLERS``  — called on mouse motion while an action is active.

Usage (in WolfMapViewer)::

    ctx = MouseContext(x, y, x_snap, y_snap, x_pixel=0, y_pixel=0,
                       keyboard=KeyboardSnapshot(alt=alt, ctrl=ctrl, shift=shift))
    if self.action in ACTION_RDOWN_HANDLERS:
        ACTION_RDOWN_HANDLERS[self.action](self, ctx)

Adding a new action
-------------------
1. Write ``_rdown_<action>(viewer, ctx)`` and/or ``_motion_<action>(viewer, ctx)``.
2. Register in ``ACTION_RDOWN_HANDLERS`` / ``ACTION_MOTION_HANDLERS``.
3. Add an ``ActionKind`` entry to ``_action_kind.py`` if not yet present.

Each handler receives the viewer (``WolfMapViewer``) and a ``MouseContext``.
Handlers must NOT import ``WolfMapViewer`` at module level to avoid circular
imports — use ``TYPE_CHECKING`` for type hints only.
"""

from __future__ import annotations

import logging
import numpy as np
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Callable

from ._action_kind import ActionKind
from .PyTranslate import _
from .PyVertex import wolfvertex

if TYPE_CHECKING:
    from .PyDraw import WolfMapViewer


# ---------------------------------------------------------------------------
# Keyboard snapshot (polled state + key-event info)
# ---------------------------------------------------------------------------

@dataclass(slots=True)
[docs] class KeyboardSnapshot: """Keyboard state snapshot — covers both polled and event-driven contexts. *When embedded in a* :class:`MouseContext` *(polled at mouse-event time)*: ``key_code = 0``, ``is_down = True``, ``held`` contains all non-modifier keys currently pressed. *When built from a* ``wx.KeyEvent`` *(key-down / key-up)*: ``key_code`` = triggering key, ``is_down`` = True/False, ``held`` = empty frozenset (not polled for key events). """
[docs] ctrl: bool = False #: Ctrl modifier held
[docs] shift: bool = False #: Shift modifier held
[docs] alt: bool = False #: Alt modifier held
[docs] key_code: int = 0 #: triggering key code (0 = polled, not an event)
[docs] is_down: bool = True #: True = key-down event (unused for polled snapshots)
[docs] held: frozenset = field(default_factory=frozenset)
#: non-modifier keys held at sample time (ord/WXK codes)
[docs] def is_key_down(self, key_code: int) -> bool: """Return ``True`` if *key_code* is currently held.""" return key_code in self.held
# --------------------------------------------------------------------------- # Mouse event context # --------------------------------------------------------------------------- @dataclass(slots=True)
[docs] class MouseContext: """Preprocessed mouse event data passed to every action handler. All coordinates are in *world* space (map units, not pixels). Keyboard modifier state is accessible via the :attr:`keyboard` attribute or the convenience properties :attr:`alt`, :attr:`ctrl`, :attr:`shift`. """
[docs] x: float #: raw world X of the cursor
[docs] y: float #: raw world Y of the cursor
[docs] x_snap: float #: grid-snapped world X (== x when snapping is off)
[docs] y_snap: float #: grid-snapped world Y (== y when snapping is off)
[docs] x_pixel: int #: pixel X of the cursor (relative to the canvas)
[docs] y_pixel: int #: pixel Y of the cursor (relative to the canvas)
# ── Keyboard snapshot (modifiers + polled key state) ──
[docs] keyboard: KeyboardSnapshot = field(default_factory=KeyboardSnapshot)
# ── Button states ──
[docs] left_down: bool = False #: left mouse button held during motion
[docs] middle_down: bool = False #: middle mouse button held during motion
[docs] right_down: bool = False #: right mouse button held / just pressed
[docs] pressure: float = 1.0 #: stylus pressure in [0, 1] (1.0 = full / mouse)
# ── Wheel extras (non-zero only when built from a wheel event) ──
[docs] wheel_rotation: int = 0 #: e.GetWheelRotation() (signed, pixels)
[docs] wheel_delta: int = 120 #: e.GetWheelDelta() (unit per notch, usually 120)
# ── Convenience properties (delegate to keyboard) ──────────────────────── @property
[docs] def alt(self) -> bool: """Alt key held (delegates to ``keyboard.alt``).""" return self.keyboard.alt
@property
[docs] def ctrl(self) -> bool: """Ctrl key held (delegates to ``keyboard.ctrl``).""" return self.keyboard.ctrl
@property
[docs] def shift(self) -> bool: """Shift key held (delegates to ``keyboard.shift``).""" return self.keyboard.shift
# --------------------------------------------------------------------------- # Type aliases # --------------------------------------------------------------------------- #: Right-down / motion handler ``(viewer, MouseContext) -> None``
[docs] _RightDownHandler = Callable[['WolfMapViewer', MouseContext], None]
#: Left-down handler ``(viewer, MouseContext) -> None``
[docs] _LeftDownHandler = Callable[['WolfMapViewer', MouseContext], None]
#: Key handler ``(viewer, KeyboardSnapshot) -> bool`` #: Return ``True`` to consume the event (prevents default processing).
[docs] _KeyHandler = Callable[['WolfMapViewer', KeyboardSnapshot], bool]
#: Paint hook ``(viewer) -> None`` #: Called after all data layers, before UI overlays.
[docs] _PaintHandler = Callable[['WolfMapViewer'], None]
# =========================================================================== # RIGHT-DOWN handlers # =========================================================================== # --- MOVE_VECTOR -----------------------------------------------------------
[docs] def _rdown_move_vector(v: 'WolfMapViewer', ctx: MouseContext) -> None: """Right-click handler for MOVE_VECTOR. First click records the start position; second click commits the move. Shift constrains vertical movement to zero; Alt constrains horizontal. """ if v.active_vector is None: logging.warning(_('No vector selected -- Please select a vector first !')) return if v.active_vector._move_start is None: v.active_vector._move_start = (ctx.x, ctx.y) return delta_x = ctx.x - v.active_vector._move_start[0] delta_y = ctx.y - v.active_vector._move_start[1] if ctx.shift: delta_y = 0. if ctx.alt: delta_x = 0. v.active_vector.move(delta_x, delta_y) v.active_vector.clear_cache() v.active_vector._move_start = None v.end_action(_('End move vector')) if v.active_fig is not None: try: opts = getattr(v, 'active_fig_options', None) if opts is not None and v.active_vector is opts.get('vector'): v.active_vector.update_linked_wx( v.active_fig, opts['linkedarrays'] ) except Exception: logging.warning( _('Error while updating the figure linked to the moved vector' ' -- Maybe the figure was closed ?') )
# --- ROTATE_VECTOR ---------------------------------------------------------
[docs] def _rdown_rotate_vector(v: 'WolfMapViewer', ctx: MouseContext) -> None: """Right-click handler for ROTATE_VECTOR. First click sets the rotation centre; second click commits the rotation. Shift+click sets a discrete rotation step; Shift+Ctrl+click clears it. """ if v.active_vector is None: logging.warning(_('No vector selected -- Please select a vector first !')) return if v.active_vector._rotation_center is None: v.active_vector._rotation_center = (ctx.x, ctx.y) return if ctx.shift: if ctx.ctrl: v.active_vector._rotation_step = None else: v.active_vector._rotation_step = np.degrees( np.arctan2( ctx.y - v.active_vector._rotation_center[1], ctx.x - v.active_vector._rotation_center[0], ) ) v.active_vector.rotate_xy(ctx.x, ctx.y) v.active_vector.clear_cache() v.active_vector._rotation_center = None v.end_action(_('End rotate vector'))
# --- MOVE_TRIANGLES --------------------------------------------------------
[docs] def _rdown_move_triangles(v: 'WolfMapViewer', ctx: MouseContext) -> None: """Right-click handler for MOVE_TRIANGLES. First click records the start; second click commits the move. Shift constrains vertical movement; Alt constrains horizontal. """ if v.active_tri is None: logging.warning(_('No triangles selected -- Please select a triangulation first !')) return if v.active_tri._move_start is None: v.active_tri._move_start = (ctx.x, ctx.y) return delta_x = ctx.x - v.active_tri._move_start[0] delta_y = ctx.y - v.active_tri._move_start[1] if ctx.shift: delta_y = 0. if ctx.alt: delta_x = 0. v.active_tri.move(delta_x, delta_y) v.active_tri.reset_plot() v.active_tri._move_start = None v.active_tri.clear_cache() v.end_action(_('End move triangulation'))
# --- ROTATE_TRIANGLES ------------------------------------------------------
[docs] def _rdown_rotate_triangles(v: 'WolfMapViewer', ctx: MouseContext) -> None: """Right-click handler for ROTATE_TRIANGLES. First click sets the rotation centre; second click commits the rotation. Shift+click sets a discrete step; Shift+Ctrl+click clears it. """ if v.active_tri is None: logging.warning(_('No vector selected -- Please select a triangulation first !')) return if v.active_tri._rotation_center is None: v.active_tri._rotation_center = (ctx.x, ctx.y) return if ctx.shift: if ctx.ctrl: v.active_tri._rotation_step = None else: v.active_tri._rotation_step = np.degrees( np.arctan2( ctx.y - v.active_tri._rotation_center[1], ctx.x - v.active_tri._rotation_center[0], ) ) v.active_tri.rotate_xy(ctx.x, ctx.y) v.active_tri._rotation_center = None v.active_tri.clear_cache() v.active_tri.reset_plot() v.end_action(_('End rotate triangulation'))
# --- MOVE_ZONE -------------------------------------------------------------
[docs] def _rdown_move_zone(v: 'WolfMapViewer', ctx: MouseContext) -> None: """Right-click handler for MOVE_ZONE. First click records the start; second click commits the move. .. note:: The shift/alt constraints set ``delta_x``/``delta_y`` local variables that are **not** forwarded to ``zone.move()`` — this matches the original behaviour (preserved intentionally). """ if v.active_zone is None: logging.warning(_('No zone selected -- Please select a zone first !')) return if v.active_zone._move_start is None: v.active_zone._move_start = (ctx.x, ctx.y) return if ctx.shift: delta_y = 0. # noqa: F841 – preserved from original (not used below) if ctx.alt: delta_x = 0. # noqa: F841 – preserved from original (not used below) v.active_zone.move( ctx.x - v.active_zone._move_start[0], ctx.y - v.active_zone._move_start[1], ) v.active_zone.clear_cache() v.active_zone._move_start = None v.end_action(_('End move zone'))
# --- ROTATE_ZONE -----------------------------------------------------------
[docs] def _rdown_rotate_zone(v: 'WolfMapViewer', ctx: MouseContext) -> None: """Right-click handler for ROTATE_ZONE. First click sets the rotation centre; second click commits the rotation. """ if v.active_zone is None: logging.warning(_('No zone selected -- Please select a zone first !')) return if v.active_zone._rotation_center is None: v.active_zone._rotation_center = (ctx.x, ctx.y) return v.active_zone.rotate_xy(ctx.x, ctx.y) v.active_zone.clear_cache() v.active_zone._rotation_center = None v.end_action(_('End rotate zone'))
# --- ADD_POINTS_TO_CLOUD ---------------------------------------------------
[docs] def _rdown_add_points_to_cloud(v: 'WolfMapViewer', ctx: MouseContext) -> None: """Shift: remove nearest vertex. Ctrl: remove last vertex. Else: add snapped vertex.""" if v.active_cloud is None: return if ctx.shift: v.active_cloud.remove_nearest_vertex(ctx.x, ctx.y) elif ctx.ctrl: v.active_cloud.remove_last_vertex() else: v.active_cloud.add_vertex(wolfvertex(ctx.x_snap, ctx.y_snap))
# --- MOVE_POINT_IN_CLOUD ---------------------------------------------------
[docs] def _rdown_move_point_in_cloud(v: 'WolfMapViewer', ctx: MouseContext) -> None: """First click picks nearest vertex; second click commits the final position.""" if v.active_cloud is None: logging.warning(_('No active cloud -- Please load data first')) return if v.active_cloud_vertex_id is None: max_dist = v._cloud_move_pick_tolerance() picked = v.active_cloud.find_nearest_id([[ctx.x, ctx.y]], max_distance=max_dist) if picked is None: logging.warning(_('No nearby point found to move')) return v.active_cloud_vertex_id = picked return moved = v.active_cloud.move_vertex( v.active_cloud_vertex_id, ctx.x_snap, ctx.y_snap, invalidate_tree=True, notify=True, recompute_bounds=True, ) if moved: v.active_cloud_vertex_id = None
# --- PLOT_ALARO_XY ---------------------------------------------------------
[docs] def _rdown_plot_alaro_xy(v: 'WolfMapViewer', ctx: MouseContext) -> None: if v.active_alaro._gdf is None: logging.warning(_('No Alaro run loaded -- Please load a run first')) return fig = v.active_alaro.plot_Rain_and_TotPrecip4XY(ctx.x, ctx.y) fig.show()
# --- DISTANCE_ALONG_VECTOR -------------------------------------------------
[docs] def _rdown_distance_along_vector(v: 'WolfMapViewer', ctx: MouseContext) -> None: v._tmp_vector_distance.add_vertex(wolfvertex(ctx.x, ctx.y))
# --- PICK_PIE_CENTER -------------------------------------------------------
[docs] def _rdown_pick_pie_center(v: 'WolfMapViewer', ctx: MouseContext) -> None: ctrl = getattr(v, '_pie_pick_controller', None) ed = getattr(v, '_pie_pick_editor', None) try: if ctrl is not None: ctrl.update_geometry(x=ctx.x, y=ctx.y, rebuild=True) if ed is not None and hasattr(ed, 'refresh_from_controller'): ed.refresh_from_controller() ed.Raise() finally: v._pie_pick_controller = None v._pie_pick_editor = None v.end_action(_('Pie center selected'))
# --- PICK_BAR_POSITION -----------------------------------------------------
[docs] def _rdown_pick_bar_position(v: 'WolfMapViewer', ctx: MouseContext) -> None: ctrl = getattr(v, '_bar_pick_controller', None) ed = getattr(v, '_bar_pick_editor', None) try: if ctrl is not None: ctrl.update_geometry(x=ctx.x, y=ctx.y, rebuild=True) if ed is not None and hasattr(ed, 'refresh_from_controller'): ed.refresh_from_controller() ed.Raise() finally: v._bar_pick_controller = None v._bar_pick_editor = None v.end_action(_('Bar position selected'))
# --- PICK_CURVE_ORIGIN -----------------------------------------------------
[docs] def _rdown_pick_curve_origin(v: 'WolfMapViewer', ctx: MouseContext) -> None: ctrl = getattr(v, '_curve_pick_controller', None) ed = getattr(v, '_curve_pick_editor', None) try: if ctrl is not None: ctrl.update_geometry(canvas_origin=(ctx.x, ctx.y), rebuild=True) if ed is not None and hasattr(ed, 'refresh_from_controller'): ed.refresh_from_controller() ed.Raise() finally: v._curve_pick_controller = None v._curve_pick_editor = None v.end_action(_('Curve canvas origin selected'))
# --- PICK_LANDMAP (shared by FULL and LOW) ----------------------------------
[docs] def _rdown_pick_landmap(v: 'WolfMapViewer', ctx: MouseContext) -> None: if v.active_landmap is None: logging.warning(_('No landmap available -- Please activate the data and retry !')) return which = 'full' if v.action == ActionKind.PICK_LANDMAP_FULL else 'low' v.active_landmap.load_texture(ctx.x, ctx.y, which=which) v.Refresh()
# --- PICK_MUNICIPALITY -----------------------------------------------------
[docs] def _rdown_pick_municipality(v: 'WolfMapViewer', ctx: MouseContext) -> None: if v.active_qdfidf is None: logging.warning(_('No municipality data available -- Please activate the data and retry !')) return v.active_qdfidf.pick_municipality(ctx.x, ctx.y, v.get_canvas_bounds())
# --- PICK_A_PICTURE --------------------------------------------------------
[docs] def _rdown_pick_a_picture(v: 'WolfMapViewer', ctx: MouseContext) -> None: if v.active_picturecollection is None: logging.warning(_('No picture collection available -- Please activate the data and retry !')) return vec = v.active_picturecollection.find_vector_containing_point(ctx.x, ctx.y) vec.myprop.imagevisible = not vec.myprop.imagevisible if ctx.shift: vec.myprop.legendvisible = not vec.myprop.legendvisible vec.myprop.update_myprops() vec.parentzone.reset_listogl() v.active_picturecollection.Activate_vector(vec) v.Refresh()
# --- PICK_BRIDGE -----------------------------------------------------------
[docs] def _rdown_pick_bridge(v: 'WolfMapViewer', ctx: MouseContext) -> None: v.pick_bridge(ctx.x, ctx.y)
# --- PICK_WEIR -------------------------------------------------------------
[docs] def _rdown_pick_weir(v: 'WolfMapViewer', ctx: MouseContext) -> None: v.pick_weir(ctx.x, ctx.y)
# --- BRIDGE_GLTF -----------------------------------------------------------
[docs] def _rdown_bridge_gltf(v: 'WolfMapViewer', ctx: MouseContext) -> None: """Delegate to PyDraw._bridge_gltf_dialog() which owns the wx dialogs.""" v._bridge_gltf_dialog(ctx.x, ctx.y)
# --- PLOT_CROSS_SECTION ----------------------------------------------------
[docs] def _rdown_plot_cross_section(v: 'WolfMapViewer', ctx: MouseContext) -> None: v.plot_cross(ctx.x, ctx.y)
# --- SET_1D_PROFILE --------------------------------------------------------
[docs] def _rdown_set_1d_profile(v: 'WolfMapViewer', ctx: MouseContext) -> None: """Delegate to PyDraw._set_1d_profile_rdown() which owns the notebook/wx logic.""" v._set_1d_profile_rdown(ctx.x, ctx.y)
# --- SELECT_NEAREST_PROFILE ------------------------------------------------
[docs] def _rdown_select_nearest_profile(v: 'WolfMapViewer', ctx: MouseContext) -> None: """Delegate to PyDraw._select_nearest_profile_rdown() which owns the notebook/wx logic.""" v._select_nearest_profile_rdown(ctx.x, ctx.y)
# --- SELECT_ACTIVE_TILE ----------------------------------------------------
[docs] def _rdown_select_active_tile(v: 'WolfMapViewer', ctx: MouseContext) -> None: v.active_tile.select_vectors_from_point(ctx.x, ctx.y, True) v.active_vector = v.active_tile.get_selected_vectors() tilearray = v.active_tile.get_array(v.active_vector) if tilearray is not None: if v.active_vector.myname == '': bbox = v.active_vector.get_bounds() id_label = '{}-{}'.format(bbox[0][0], bbox[1][1]) else: id_label = v.active_vector.myname v.add_object('array', newobj=tilearray, ToCheck=True, id=id_label)
# --- SELECT_ACTIVE_IMAGE_TILE ----------------------------------------------
[docs] def _rdown_select_active_image_tile(v: 'WolfMapViewer', ctx: MouseContext) -> None: v.active_imagestiles.select_vectors_from_point(ctx.x, ctx.y, True) active_tile = v.active_imagestiles.get_selected_vectors() active_tile.myprop.imagevisible = not active_tile.myprop.imagevisible
# --- PICK_CATCHMENT_LIDAXE -------------------------------------------------
[docs] def _rdown_pick_catchment_lidaxe(v: 'WolfMapViewer', ctx: MouseContext) -> None: if v.active_lidaxe is None: logging.warning(_('No Lidaxe data available -- Please activate the data and retry !')) return catchment, accumulation, direction = v.active_lidaxe.get_catchment(ctx.x, ctx.y) if catchment is not None: v.add_object('array', newobj=catchment, ToCheck=True, id='catchment_{}_{}'.format(ctx.x, ctx.y)) if accumulation is not None: v.add_object('array', newobj=accumulation, ToCheck=True, id='accumulation_{}_{}'.format(ctx.x, ctx.y)) accumulation.mypal.distribute_values(0., 10_000.) v.Refresh()
# --- PICK_PATH_LIDAXE ------------------------------------------------------
[docs] def _rdown_pick_path_lidaxe(v: 'WolfMapViewer', ctx: MouseContext) -> None: if v.active_lidaxe is None: logging.warning(_('No Lidaxe data available -- Please activate the data and retry !')) return path = v.active_lidaxe.get_longest_flow_path(ctx.x, ctx.y, snap_to_max=False) v.Active_vector(path) v.Refresh()
# --- SELECT_ACTIVE_VECTOR (4 variants share this function) -----------------
[docs] def _rdown_select_active_vector(v: 'WolfMapViewer', ctx: MouseContext) -> None: """Handler inspects v.action to differentiate inside/outside and single/all-zone.""" inside = v.action in (ActionKind.SELECT_ACTIVE_VECTOR_INSIDE, ActionKind.SELECT_ACTIVE_VECTOR2_INSIDE) onlyonezone = v.action in (ActionKind.SELECT_ACTIVE_VECTOR2_INSIDE, ActionKind.SELECT_ACTIVE_VECTOR2_ALL) if onlyonezone: v.active_zone.select_vectors_from_point(ctx.x, ctx.y, inside) v.active_vector = v.active_zone.get_selected_vectors()[0] if v.active_vector is not None: v.active_zone.parent.Activate_vector(v.active_vector) v.active_zone.active_vector = v.active_vector v.active_zones.active_zone = v.active_vector.parentzone else: v.active_zones.select_vectors_from_point(ctx.x, ctx.y, inside) v.active_vector = v.active_zones.get_selected_vectors() if v.active_vector is not None: v.active_zones.Activate_vector(v.active_vector) v.active_zone = v.active_vector.parentzone v.active_zones.expand_tree(v.active_zone)
# --- SELECT_NODE (2 variants share this function) --------------------------
[docs] def _rdown_select_node(v: 'WolfMapViewer', ctx: MouseContext) -> None: if v.action == ActionKind.SELECT_NODE_BY_NODE_RESULTS: if v.active_res2d is None: logging.warning(_('No 2D results available -- Please load a file or create data !')) v.end_action(_('Force end select node by node')) return curobj = v.active_res2d.SelectionData else: if v.active_array is None: logging.warning(_('No array available -- Please load a file or create data !')) v.end_action(_('Force end select node by node')) return curobj = v.active_array.SelectionData if curobj.myselection == 'all': logging.warning(_('All nodes are selected !!')) logging.warning(_('Selecting node by node will force to reset the selection')) logging.warning(_('and start from scratch')) curobj.add_node_to_selection(ctx.x, ctx.y) curobj.update_nb_nodes_selection() v.Paint()
# --- SELECT_BY_VECTOR (5 variants share this function) --------------------
[docs] def _rdown_select_by_vector(v: 'WolfMapViewer', ctx: MouseContext) -> None: v.active_vector.add_vertex(wolfvertex(ctx.x, ctx.y))
# --- LAZ_TMP_VECTOR --------------------------------------------------------
[docs] def _rdown_laz_tmp_vector(v: 'WolfMapViewer', ctx: MouseContext) -> None: v.active_vector.add_vertex(wolfvertex(ctx.x, ctx.y)) v.active_vector.find_minmax()
# --- CREATE_POLYGON_TILES --------------------------------------------------
[docs] def _rdown_create_polygon_tiles(v: 'WolfMapViewer', ctx: MouseContext) -> None: v.active_vector.add_vertex(wolfvertex(ctx.x, ctx.y)) v.active_vector.find_minmax()
# --- CAPTURE_VERTICES ------------------------------------------------------
[docs] def _rdown_capture_vertices(v: 'WolfMapViewer', ctx: MouseContext) -> None: """Update the trailing preview vertex, append a new one, optionally sample Z.""" new_wv = wolfvertex(ctx.x, ctx.y) last_wv = v.active_vector.myvertices[-1] last_wv.x = ctx.x_snap last_wv.y = ctx.y_snap v.active_vector.add_vertex(new_wv) v.active_vertex = new_wv if ctx.ctrl: if v.active_array is not None: last_wv.z = v.active_array.get_value(ctx.x_snap, ctx.y_snap) else: logging.warning(_('No array available and ctrl is pressed -- Please load a file or create data !')) v.active_vector.find_minmax() v.active_zone.find_minmax()
# --- OFFSET_SCALE_IMAGE ----------------------------------------------------
[docs] def _rdown_offset_scale_image(v: 'WolfMapViewer', ctx: MouseContext) -> None: """First click records start; second click commits the image offset.""" if v.active_vector is None: logging.warning(_('No vector selected -- Please select a vector first !')) return if v.active_vector.myprop.textureimage is None: logging.warning(_('No image available -- Please load an image first !')) return if v.active_vector._move_start is None: v.active_vector._move_start = (ctx.x, ctx.y) return delta_x = ctx.x - v.active_vector._move_start[0] delta_y = ctx.y - v.active_vector._move_start[1] v.active_vector.myprop.offset_image(delta_x, delta_y) v.active_vector.myprop.update_myprops() v.active_vector._move_start = None v.end_action(_('End offset/scale image'))
# --- DYNAMIC_PARALLEL ------------------------------------------------------
[docs] def _rdown_dynamic_parallel(v: 'WolfMapViewer', ctx: MouseContext) -> None: if ctx.ctrl: if v.active_array is not None: v.active_vector.myvertices[-1].z = v.active_array.get_value(ctx.x, ctx.y) else: logging.warning(_('No array available and ctrl is pressed -- Please load a file or create data !')) v.active_vector.add_vertex(wolfvertex(ctx.x, ctx.y)) v.active_zone.parallel_active(v.dynapar_dist)
# --- MODIFY_VERTICES -------------------------------------------------------
[docs] def _rdown_modify_vertices(v: 'WolfMapViewer', ctx: MouseContext) -> None: """First click picks nearest vertex; second click confirms (optionally samples Z).""" if v.active_vector is None: logging.warning(_('No vector selected -- Please select a vector first !')) return if v.active_vertex is None: v.active_vertex = v.active_vector.find_nearest_vertex(ctx.x, ctx.y) else: v.active_vertex.limit2bounds(v.active_vector._mylimits) if ctx.ctrl: if v.active_array is not None: v.active_vertex.z = v.active_array.get_value(ctx.x_snap, ctx.y_snap) else: logging.warning(_('No array available and ctrl is pressed -- Please load a file or create data !')) v.active_vertex = None
# --- INSERT_VERTICES -------------------------------------------------------
[docs] def _rdown_insert_vertices(v: 'WolfMapViewer', ctx: MouseContext) -> None: """First click inserts vertex at nearest edge; second click confirms.""" if v.active_vector is None: logging.warning(_('No vector selected -- Please select a vector first !')) return if v.active_vertex is None: v.active_vertex = v.active_vector.insert_nearest_vert(ctx.x, ctx.y) else: if ctx.ctrl: if v.active_array is not None: v.active_vertex.z = v.active_array.get_value(ctx.x_snap, ctx.y_snap) else: logging.warning(_('No array available and ctrl is pressed -- Please load a file or create data !')) v.active_vertex = None
# =========================================================================== # MOTION handlers — migrated from On_Mouse_Motion # ===========================================================================
[docs] def _motion_move_vector(v: 'WolfMapViewer', ctx: MouseContext) -> None: """Motion handler for MOVE_VECTOR — live preview while the start is set.""" if v.active_vector is not None and v.active_vector._move_start is not None: delta_x = ctx.x - v.active_vector._move_start[0] delta_y = ctx.y - v.active_vector._move_start[1] if ctx.shift: delta_y = 0. if ctx.alt: delta_x = 0. v.active_vector.move(delta_x, delta_y)
[docs] def _motion_rotate_vector(v: 'WolfMapViewer', ctx: MouseContext) -> None: """Motion handler for ROTATE_VECTOR — live preview while centre is set.""" if v.active_vector is not None and v.active_vector._rotation_center is not None: v.active_vector.rotate_xy(ctx.x, ctx.y)
[docs] def _motion_move_triangles(v: 'WolfMapViewer', ctx: MouseContext) -> None: """Motion handler for MOVE_TRIANGLES — live preview while start is set.""" if v.active_tri is not None and v.active_tri._move_start is not None: delta_x = ctx.x - v.active_tri._move_start[0] delta_y = ctx.y - v.active_tri._move_start[1] if ctx.shift: delta_y = 0. if ctx.alt: delta_x = 0. v.active_tri.move(delta_x, delta_y) v.active_tri.reset_plot()
[docs] def _motion_rotate_triangles(v: 'WolfMapViewer', ctx: MouseContext) -> None: """Motion handler for ROTATE_TRIANGLES — live preview while centre is set.""" if v.active_tri is not None and v.active_tri._rotation_center is not None: v.active_tri.rotate_xy(ctx.x, ctx.y) v.active_tri.reset_plot()
[docs] def _motion_move_zone(v: 'WolfMapViewer', ctx: MouseContext) -> None: """Motion handler for MOVE_ZONE — live preview while start is set.""" if v.active_zone is not None and v.active_zone._move_start is not None: delta_x = ctx.x - v.active_zone._move_start[0] delta_y = ctx.y - v.active_zone._move_start[1] if ctx.shift: delta_y = 0. if ctx.alt: delta_x = 0. v.active_zone.move(delta_x, delta_y)
[docs] def _motion_rotate_zone(v: 'WolfMapViewer', ctx: MouseContext) -> None: """Motion handler for ROTATE_ZONE — live preview while centre is set.""" if v.active_zone is not None and v.active_zone._rotation_center is not None: v.active_zone.rotate_xy(ctx.x, ctx.y)
[docs] def _motion_move_point_in_cloud(v: 'WolfMapViewer', ctx: MouseContext) -> None: """Live preview for MOVE_POINT_IN_CLOUD while vertex is picked.""" if v.active_cloud is not None and v.active_cloud_vertex_id is not None: v.active_cloud.move_vertex( v.active_cloud_vertex_id, ctx.x_snap, ctx.y_snap, invalidate_tree=False, notify=True, recompute_bounds=False, )
[docs] def _motion_dynamic_parallel(v: 'WolfMapViewer', ctx: MouseContext) -> None: """Recompute the parallel line on every mouse-move (no coords needed).""" v.active_zone.parallel_active(v.dynapar_dist)
# =========================================================================== # Dispatch tables # =========================================================================== #: Maps ``ActionKind`` → right-down handler. #: To register a new action, add one entry here — no changes to the #: monolithic ``On_Mouse_Right_Down`` body are needed.
[docs] ACTION_RDOWN_HANDLERS: dict[ActionKind, _RightDownHandler] = { # --- triangulation --- ActionKind.MOVE_TRIANGLES: _rdown_move_triangles, ActionKind.ROTATE_TRIANGLES: _rdown_rotate_triangles, # --- vector / zone --- ActionKind.MOVE_VECTOR: _rdown_move_vector, ActionKind.ROTATE_VECTOR: _rdown_rotate_vector, ActionKind.MOVE_ZONE: _rdown_move_zone, ActionKind.ROTATE_ZONE: _rdown_rotate_zone, # --- cloud --- ActionKind.ADD_POINTS_TO_CLOUD: _rdown_add_points_to_cloud, ActionKind.MOVE_POINT_IN_CLOUD: _rdown_move_point_in_cloud, # --- alaro --- ActionKind.PLOT_ALARO_XY: _rdown_plot_alaro_xy, # --- distance --- ActionKind.DISTANCE_ALONG_VECTOR: _rdown_distance_along_vector, # --- asset picks --- ActionKind.PICK_PIE_CENTER: _rdown_pick_pie_center, ActionKind.PICK_BAR_POSITION: _rdown_pick_bar_position, ActionKind.PICK_CURVE_ORIGIN: _rdown_pick_curve_origin, # --- landmap (two variants share same function) --- ActionKind.PICK_LANDMAP_FULL: _rdown_pick_landmap, ActionKind.PICK_LANDMAP_LOW: _rdown_pick_landmap, # --- municipality / pictures --- ActionKind.PICK_MUNICIPALITY: _rdown_pick_municipality, ActionKind.PICK_A_PICTURE: _rdown_pick_a_picture, # --- bridges / weirs --- ActionKind.PICK_BRIDGE: _rdown_pick_bridge, ActionKind.PICK_WEIR: _rdown_pick_weir, ActionKind.BRIDGE_GLTF: _rdown_bridge_gltf, # --- cross sections --- ActionKind.PLOT_CROSS_SECTION: _rdown_plot_cross_section, ActionKind.SET_1D_PROFILE: _rdown_set_1d_profile, ActionKind.SELECT_NEAREST_PROFILE: _rdown_select_nearest_profile, # --- tiles --- ActionKind.SELECT_ACTIVE_TILE: _rdown_select_active_tile, ActionKind.SELECT_ACTIVE_IMAGE_TILE: _rdown_select_active_image_tile, # --- lidaxe --- ActionKind.PICK_CATCHMENT_LIDAXE: _rdown_pick_catchment_lidaxe, ActionKind.PICK_PATH_LIDAXE: _rdown_pick_path_lidaxe, # --- select active vector (4 variants share same function) --- ActionKind.SELECT_ACTIVE_VECTOR_INSIDE: _rdown_select_active_vector, ActionKind.SELECT_ACTIVE_VECTOR_ALL: _rdown_select_active_vector, ActionKind.SELECT_ACTIVE_VECTOR2_INSIDE: _rdown_select_active_vector, ActionKind.SELECT_ACTIVE_VECTOR2_ALL: _rdown_select_active_vector, # --- select node (2 variants share same function) --- ActionKind.SELECT_NODE_BY_NODE: _rdown_select_node, ActionKind.SELECT_NODE_BY_NODE_RESULTS: _rdown_select_node, # --- select by vector (5 variants share same function) --- ActionKind.SELECT_BY_VECTOR_INSIDE: _rdown_select_by_vector, ActionKind.SELECT_BY_VECTOR_OUTSIDE: _rdown_select_by_vector, ActionKind.SELECT_BY_VECTOR_ALONG: _rdown_select_by_vector, ActionKind.SELECT_BY_TMP_VECTOR_INSIDE: _rdown_select_by_vector, ActionKind.SELECT_BY_TMP_VECTOR_ALONG: _rdown_select_by_vector, # --- misc geometry --- ActionKind.LAZ_TMP_VECTOR: _rdown_laz_tmp_vector, ActionKind.CREATE_POLYGON_TILES: _rdown_create_polygon_tiles, ActionKind.CAPTURE_VERTICES: _rdown_capture_vertices, ActionKind.DYNAMIC_PARALLEL: _rdown_dynamic_parallel, # --- image / vertex editing --- ActionKind.OFFSET_SCALE_IMAGE: _rdown_offset_scale_image, ActionKind.MODIFY_VERTICES: _rdown_modify_vertices, ActionKind.INSERT_VERTICES: _rdown_insert_vertices, }
#: Maps ``ActionKind`` → motion handler. #: To register a new action, add one entry here — no changes to the #: monolithic ``On_Mouse_Motion`` body are needed.
[docs] ACTION_MOTION_HANDLERS: dict[ActionKind, _RightDownHandler] = { ActionKind.MOVE_VECTOR: _motion_move_vector, ActionKind.ROTATE_VECTOR: _motion_rotate_vector, ActionKind.MOVE_TRIANGLES: _motion_move_triangles, ActionKind.ROTATE_TRIANGLES: _motion_rotate_triangles, ActionKind.MOVE_ZONE: _motion_move_zone, ActionKind.ROTATE_ZONE: _motion_rotate_zone, ActionKind.MOVE_POINT_IN_CLOUD: _motion_move_point_in_cloud, ActionKind.DYNAMIC_PARALLEL: _motion_dynamic_parallel, }