Plugin overload — replacing and restoring an existing handler

This notebook demonstrates the overload=True parameter of register_action().

Scenario

overload

Result

New action

False (default)

Silent registration

Existing action — permanent replacement

False

WARNING — previous handler lost

Existing action — temporary replacement

True

WARNING — previous handler saved, restored on unregister_action()

The concrete example here: we install three successive versions of an rdown handler on the same action, stacking and unstacking them with overload=True.

1 — wx startup

Always use %gui wx in a notebook — never wx.App().

[1]:
import logging, sys
# Show WARNING/INFO in the cell output
logging.basicConfig(level=logging.DEBUG, format='%(levelname)-8s %(message)s')
%gui wx

2 — Viewer

[2]:
from wolfhece.PyDraw import WolfMapViewer

viewer = WolfMapViewer(None, title="Overload demo", w=1200, h=800)
viewer.Show()
DEBUG    Searching MKL in c:\Users\pierre\Documents\Gitlab\python311\Scripts\Library\bin
DEBUG    Searching MKL in c:\Users\pierre\Documents\Gitlab\python311\Library\bin
DEBUG    Found MKL in c:\Users\pierre\Documents\Gitlab\python311\Library\bin
DEBUG    Searching FORTRAN in c:\Users\pierre\Documents\Gitlab\python311\Scripts\Library\bin
DEBUG    Searching FORTRAN in c:\Users\pierre\Documents\Gitlab\python311\Library\bin
DEBUG    Found FORTRAN in c:\Users\pierre\Documents\Gitlab\python311\Library\bin
INFO     Executable paths loaded from cache
DEBUG    Searching MKL in c:\Users\pierre\Documents\Gitlab\python311\Scripts\Library\bin
DEBUG    Searching MKL in c:\Users\pierre\Documents\Gitlab\python311\Library\bin
DEBUG    Found MKL in c:\Users\pierre\Documents\Gitlab\python311\Library\bin
DEBUG    Searching FORTRAN in c:\Users\pierre\Documents\Gitlab\python311\Scripts\Library\bin
DEBUG    Searching FORTRAN in c:\Users\pierre\Documents\Gitlab\python311\Library\bin
DEBUG    Found FORTRAN in c:\Users\pierre\Documents\Gitlab\python311\Library\bin
DEBUG    OpenGL_accelerate module loaded
DEBUG    Using accelerated ArrayDatatype
INFO     Using NT-specific GLUT calls with exit callbacks
INFO     Importing wolfhece modules
INFO     wolfhece modules imported
INFO     WinTab : contexte ouvert sur HWND=0x2870A3A (pressure_max=32767)
[2]:
False

3 — Case 1: simple registration (new action)

No conflict → no warning.

[3]:
from wolfhece._viewer_plugin_handlers import MouseContext

def _rdown_v1(v: WolfMapViewer, ctx: MouseContext) -> None:
    print(f"[v1]  X={ctx.x_snap:.3f}  Y={ctx.y_snap:.3f}")

# First registration — no WARNING expected
viewer.register_action('my demo', rdown_handler=_rdown_v1)
viewer.start_action('my demo', 'Right-click → version 1')

print("Active handler:", viewer._custom_rdown_handlers.get('my demo').__name__)
print("_saved_handlers:", viewer._saved_handlers)

INFO     ACTION : Right-click → version 1
Active handler: _rdown_v1
_saved_handlers: {}

4 — Case 2: replacement without saving (overload=False)

A WARNING is emitted — _rdown_v1 will be lost after replacement.

[ ]:
def _rdown_v2(v: WolfMapViewer, ctx: MouseContext) -> None:
    print(f"[v2]  snap=({ctx.x_snap:.3f}, {ctx.y_snap:.3f})")

# overload=False (default) → WARNING, _rdown_v1 lost
viewer.register_action('my demo', rdown_handler=_rdown_v2)

print("Active handler:", viewer._custom_rdown_handlers.get('my demo').__name__)
print("_saved_handlers:", viewer._saved_handlers)   # still empty

WARNING  register_action: 'my demo' — rdown handler already registered (previous '_rdown_v1' will be lost; pass overload=True to save it).
Active handler: _rdown_v2
_saved_handlers: {}
[v2]  snap=(21.491, 24.980)
[v2]  snap=(13.449, 22.264)
[v2]  snap=(10.466, 17.949)
[v2]  snap=(17.816, 14.967)

5 — Case 3: overload with saving (overload=True)

We install _rdown_v3 on top of _rdown_v2.
A WARNING is emitted and _rdown_v2 is saved in _saved_handlers.
On unregister_action, _rdown_v2 will be automatically restored.
[ ]:
def _rdown_v3(v: WolfMapViewer, ctx: MouseContext) -> None:
    """Temporary diagnostic handler — logs pixel and world coordinates."""
    print(f"[v3 DIAG]  pixel=({ctx.x_pixel},{ctx.y_pixel})  "
          f"world=({ctx.x:.3f},{ctx.y:.3f})")

# overload=True → WARNING + save _rdown_v2
viewer.register_action('my demo', rdown_handler=_rdown_v3, overload=True)

print("\nActive handler:", viewer._custom_rdown_handlers.get('my demo').__name__)
saved = viewer._saved_handlers.get('my demo', {})
saved_rdown = saved.get('rdown')
print("Saved handler (rdown):",
      saved_rdown.__name__ if saved_rdown else '<none>')

WARNING  register_action: 'my demo' — rdown handler overloaded (previous '_rdown_v2' saved and will be restored on unregister).

Active handler: _rdown_v3
Saved handler (rdown): _rdown_v2
[v3 DIAG]  pixel=(749,402)  world=(27.936,19.281)
[v3 DIAG]  pixel=(597,440)  world=(19.840,17.257)
[v3 DIAG]  pixel=(442,432)  world=(11.585,17.683)
[v3 DIAG]  pixel=(449,326)  world=(11.958,23.329)

6 — Live check

Do a few right-clicks in the viewer window.
You should see [v3 DIAG] messages — the diagnostic version is active.
[6]:
print("Current action:", viewer.action)
print("→ Right-click in the viewer window to see [v3 DIAG]")

Current action: my demo
→ Right-click in the viewer window to see [v3 DIAG]

7 — Unregister: automatic restoration of v2

[7]:
viewer.unregister_action('my demo')

current = viewer._custom_rdown_handlers.get('my demo')
print("Active handler after unregister:",
      current.__name__ if current else '<removed>')
print("_saved_handlers:", viewer._saved_handlers)   # must be empty

# _rdown_v2 is active again — verifiable by restarting the action
viewer.start_action('my demo', 'Back to v2 — right-click to verify')

INFO     unregister_action: 'my demo' — rdown handler restored to '_rdown_v2'.
INFO     ACTION : Back to v2 — right-click to verify
Active handler after unregister: _rdown_v2
_saved_handlers: {}
[v2]  snap=(21.864, 19.387)
[v2]  snap=(14.780, 15.819)
[v2]  snap=(10.093, 18.056)

8 — Cascaded overloads

Multiple overloads can be stacked, but the save only happens once
(the first overload=True call wins), so restoration always unwinds back
to the handler that was present before the first overload.
[8]:
def _rdown_v4(v, ctx): print(f"[v4]  X={ctx.x:.1f}")
def _rdown_v5(v, ctx): print(f"[v5]  X={ctx.x:.1f}")

# v2 is active
viewer.register_action('my demo', rdown_handler=_rdown_v4, overload=True)
# → saves v2, installs v4

viewer.register_action('my demo', rdown_handler=_rdown_v5, overload=True)
# → slot 'rdown' already saved (v2); v4 is lost (WARNING, no additional save)

print("Active handler:",
      viewer._custom_rdown_handlers.get('my demo').__name__)
saved_rdown = viewer._saved_handlers.get('my demo', {}).get('rdown')
print("Saved (rdown):",
      saved_rdown.__name__ if saved_rdown else '<none>')

# Restoration: returns to v2 (not v4)
viewer.unregister_action('my demo')
current = viewer._custom_rdown_handlers.get('my demo')
print("After unregister:",
      current.__name__ if current else '<removed>')

WARNING  register_action: 'my demo' — rdown handler overloaded (previous '_rdown_v2' saved and will be restored on unregister).
WARNING  register_action: 'my demo' — rdown handler overloaded (previous '_rdown_v4' saved and will be restored on unregister).
INFO     unregister_action: 'my demo' — rdown handler restored to '_rdown_v2'.
Active handler: _rdown_v5
Saved (rdown): _rdown_v2
After unregister: _rdown_v2
[v2]  snap=(18.775, 17.151)
[v2]  snap=(10.306, 16.884)
[v2]  snap=(8.922, 20.399)

9 — Final cleanup

[ ]:
viewer.end_action('End overload demo')
viewer.unregister_action('my demo')   # idempotent
print("Cleanup done.")
print("_custom_rdown_handlers:", viewer._custom_rdown_handlers)
print("_saved_handlers:", viewer._saved_handlers)

INFO     ACTION : End overload demo
Cleanup done.
_custom_rdown_handlers: {}
_saved_handlers: {}
The Kernel crashed while executing code in the current cell or a previous cell.

Please review the code in the cell(s) to identify a possible cause of the failure.

Click <a href='https://aka.ms/vscodeJupyterKernelCrash'>here</a> for more info.

View Jupyter <a href='command:jupyter.viewOutput'>log</a> for further details.

API summary

# Initial registration (no conflict)
viewer.register_action('my action', rdown_handler=fn_v1)

# Permanent replacement — WARNING if slot already occupied, no save
viewer.register_action('my action', rdown_handler=fn_v2)

# Temporary overload — WARNING + saves the previous handler
viewer.register_action('my action', rdown_handler=fn_v3, overload=True)

# Unregister — restores fn_v2 if overload=True was used
viewer.unregister_action('my action')

Rules

  • The save happens only once per slot (first overload=True call wins).

  • A second overload=True on the same slot emits a WARNING but does not replace the existing save → restoration always unwinds back to the handler that was present before the first overload.

  • unregister_action is idempotent: no-op if the action is unknown.

  • overload=False on an occupied slot → WARNING only, previous handler permanently lost.