{ "cells": [ { "cell_type": "markdown", "id": "c584bf92", "metadata": {}, "source": [ "# Plugin overload — replacing and restoring an existing handler\n", "\n", "This notebook demonstrates the `overload=True` parameter of `register_action()`.\n", "\n", "| Scenario | `overload` | Result |\n", "|---|---|---|\n", "| **New** action | `False` (default) | Silent registration |\n", "| **Existing** action — permanent replacement | `False` | `WARNING` — previous handler lost |\n", "| **Existing** action — temporary replacement | `True` | `WARNING` — previous handler saved, restored on `unregister_action()` |\n", "\n", "The concrete example here: we install three successive versions of an `rdown`\n", "handler on the same action, stacking and unstacking them with `overload=True`.\n" ] }, { "cell_type": "markdown", "id": "3633e219", "metadata": {}, "source": [ "## 1 — wx startup\n", "\n", "> Always use `%gui wx` in a notebook — never `wx.App()`.\n" ] }, { "cell_type": "code", "execution_count": 1, "id": "994b1050", "metadata": {}, "outputs": [], "source": [ "import logging, sys\n", "# Show WARNING/INFO in the cell output\n", "logging.basicConfig(level=logging.DEBUG, format='%(levelname)-8s %(message)s')\n", "%gui wx\n" ] }, { "cell_type": "markdown", "id": "d9d302d7", "metadata": {}, "source": [ "## 2 — Viewer" ] }, { "cell_type": "code", "execution_count": 2, "id": "237af748", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "DEBUG Searching MKL in c:\\Users\\pierre\\Documents\\Gitlab\\python311\\Scripts\\Library\\bin\n", "DEBUG Searching MKL in c:\\Users\\pierre\\Documents\\Gitlab\\python311\\Library\\bin\n", "DEBUG Found MKL in c:\\Users\\pierre\\Documents\\Gitlab\\python311\\Library\\bin\n", "DEBUG Searching FORTRAN in c:\\Users\\pierre\\Documents\\Gitlab\\python311\\Scripts\\Library\\bin\n", "DEBUG Searching FORTRAN in c:\\Users\\pierre\\Documents\\Gitlab\\python311\\Library\\bin\n", "DEBUG Found FORTRAN in c:\\Users\\pierre\\Documents\\Gitlab\\python311\\Library\\bin\n", "INFO Executable paths loaded from cache\n", "DEBUG Searching MKL in c:\\Users\\pierre\\Documents\\Gitlab\\python311\\Scripts\\Library\\bin\n", "DEBUG Searching MKL in c:\\Users\\pierre\\Documents\\Gitlab\\python311\\Library\\bin\n", "DEBUG Found MKL in c:\\Users\\pierre\\Documents\\Gitlab\\python311\\Library\\bin\n", "DEBUG Searching FORTRAN in c:\\Users\\pierre\\Documents\\Gitlab\\python311\\Scripts\\Library\\bin\n", "DEBUG Searching FORTRAN in c:\\Users\\pierre\\Documents\\Gitlab\\python311\\Library\\bin\n", "DEBUG Found FORTRAN in c:\\Users\\pierre\\Documents\\Gitlab\\python311\\Library\\bin\n", "DEBUG OpenGL_accelerate module loaded\n", "DEBUG Using accelerated ArrayDatatype\n", "INFO Using NT-specific GLUT calls with exit callbacks\n", "INFO Importing wolfhece modules\n", "INFO wolfhece modules imported\n", "INFO WinTab : contexte ouvert sur HWND=0x2870A3A (pressure_max=32767)\n" ] }, { "data": { "text/plain": [ "False" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from wolfhece.PyDraw import WolfMapViewer\n", "\n", "viewer = WolfMapViewer(None, title=\"Overload demo\", w=1200, h=800)\n", "viewer.Show()" ] }, { "cell_type": "markdown", "id": "f4f908e0", "metadata": {}, "source": [ "## 3 — Case 1: simple registration (new action)\n", "\n", "No conflict → no warning.\n" ] }, { "cell_type": "code", "execution_count": 3, "id": "7aa408e9", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "INFO ACTION : Right-click → version 1\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Active handler: _rdown_v1\n", "_saved_handlers: {}\n" ] } ], "source": [ "from wolfhece._viewer_plugin_handlers import MouseContext\n", "\n", "def _rdown_v1(v: WolfMapViewer, ctx: MouseContext) -> None:\n", " print(f\"[v1] X={ctx.x_snap:.3f} Y={ctx.y_snap:.3f}\")\n", "\n", "# First registration — no WARNING expected\n", "viewer.register_action('my demo', rdown_handler=_rdown_v1)\n", "viewer.start_action('my demo', 'Right-click → version 1')\n", "\n", "print(\"Active handler:\", viewer._custom_rdown_handlers.get('my demo').__name__)\n", "print(\"_saved_handlers:\", viewer._saved_handlers)\n" ] }, { "cell_type": "markdown", "id": "05606b98", "metadata": {}, "source": [ "## 4 — Case 2: replacement without saving (`overload=False`)\n", "\n", "A `WARNING` is emitted — `_rdown_v1` will be lost after replacement.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "f2ad2637", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "WARNING register_action: 'my demo' — rdown handler already registered (previous '_rdown_v1' will be lost; pass overload=True to save it).\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Active handler: _rdown_v2\n", "_saved_handlers: {}\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[v2] snap=(21.491, 24.980)\n", "[v2] snap=(13.449, 22.264)\n", "[v2] snap=(10.466, 17.949)\n", "[v2] snap=(17.816, 14.967)\n" ] } ], "source": [ "def _rdown_v2(v: WolfMapViewer, ctx: MouseContext) -> None:\n", " print(f\"[v2] snap=({ctx.x_snap:.3f}, {ctx.y_snap:.3f})\")\n", "\n", "# overload=False (default) → WARNING, _rdown_v1 lost\n", "viewer.register_action('my demo', rdown_handler=_rdown_v2)\n", "\n", "print(\"Active handler:\", viewer._custom_rdown_handlers.get('my demo').__name__)\n", "print(\"_saved_handlers:\", viewer._saved_handlers) # still empty\n" ] }, { "cell_type": "markdown", "id": "baf81863", "metadata": {}, "source": [ "## 5 — Case 3: overload with saving (`overload=True`)\n", "\n", "We install `_rdown_v3` on top of `_rdown_v2`. \n", "A `WARNING` is emitted **and** `_rdown_v2` is saved in `_saved_handlers`. \n", "On `unregister_action`, `_rdown_v2` will be **automatically restored**.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "5ed0f445", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "WARNING register_action: 'my demo' — rdown handler overloaded (previous '_rdown_v2' saved and will be restored on unregister).\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\n", "Active handler: _rdown_v3\n", "Saved handler (rdown): _rdown_v2\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[v3 DIAG] pixel=(749,402) world=(27.936,19.281)\n", "[v3 DIAG] pixel=(597,440) world=(19.840,17.257)\n", "[v3 DIAG] pixel=(442,432) world=(11.585,17.683)\n", "[v3 DIAG] pixel=(449,326) world=(11.958,23.329)\n" ] } ], "source": [ "def _rdown_v3(v: WolfMapViewer, ctx: MouseContext) -> None:\n", " \"\"\"Temporary diagnostic handler — logs pixel and world coordinates.\"\"\"\n", " print(f\"[v3 DIAG] pixel=({ctx.x_pixel},{ctx.y_pixel}) \"\n", " f\"world=({ctx.x:.3f},{ctx.y:.3f})\")\n", "\n", "# overload=True → WARNING + save _rdown_v2\n", "viewer.register_action('my demo', rdown_handler=_rdown_v3, overload=True)\n", "\n", "print(\"\\nActive handler:\", viewer._custom_rdown_handlers.get('my demo').__name__)\n", "saved = viewer._saved_handlers.get('my demo', {})\n", "saved_rdown = saved.get('rdown')\n", "print(\"Saved handler (rdown):\",\n", " saved_rdown.__name__ if saved_rdown else '')\n" ] }, { "cell_type": "markdown", "id": "eb3f4db9", "metadata": {}, "source": [ "## 6 — Live check\n", "\n", "Do a few **right-clicks** in the viewer window. \n", "You should see `[v3 DIAG]` messages — the diagnostic version is active.\n" ] }, { "cell_type": "code", "execution_count": 6, "id": "0c513834", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Current action: my demo\n", "→ Right-click in the viewer window to see [v3 DIAG]\n" ] } ], "source": [ "print(\"Current action:\", viewer.action)\n", "print(\"→ Right-click in the viewer window to see [v3 DIAG]\")\n" ] }, { "cell_type": "markdown", "id": "a6394b2a", "metadata": {}, "source": [ "## 7 — Unregister: automatic restoration of v2\n" ] }, { "cell_type": "code", "execution_count": 7, "id": "f1af860b", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "INFO unregister_action: 'my demo' — rdown handler restored to '_rdown_v2'.\n", "INFO ACTION : Back to v2 — right-click to verify\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Active handler after unregister: _rdown_v2\n", "_saved_handlers: {}\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[v2] snap=(21.864, 19.387)\n", "[v2] snap=(14.780, 15.819)\n", "[v2] snap=(10.093, 18.056)\n" ] } ], "source": [ "viewer.unregister_action('my demo')\n", "\n", "current = viewer._custom_rdown_handlers.get('my demo')\n", "print(\"Active handler after unregister:\",\n", " current.__name__ if current else '')\n", "print(\"_saved_handlers:\", viewer._saved_handlers) # must be empty\n", "\n", "# _rdown_v2 is active again — verifiable by restarting the action\n", "viewer.start_action('my demo', 'Back to v2 — right-click to verify')\n" ] }, { "cell_type": "markdown", "id": "f89315b4", "metadata": {}, "source": [ "## 8 — Cascaded overloads\n", "\n", "Multiple overloads can be stacked, but the save only happens once \n", "(the first `overload=True` call wins), so restoration always unwinds back \n", "to the handler that was present **before the first overload**.\n" ] }, { "cell_type": "code", "execution_count": 8, "id": "2b02c6ab", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "WARNING register_action: 'my demo' — rdown handler overloaded (previous '_rdown_v2' saved and will be restored on unregister).\n", "WARNING register_action: 'my demo' — rdown handler overloaded (previous '_rdown_v4' saved and will be restored on unregister).\n", "INFO unregister_action: 'my demo' — rdown handler restored to '_rdown_v2'.\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Active handler: _rdown_v5\n", "Saved (rdown): _rdown_v2\n", "After unregister: _rdown_v2\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[v2] snap=(18.775, 17.151)\n", "[v2] snap=(10.306, 16.884)\n", "[v2] snap=(8.922, 20.399)\n" ] } ], "source": [ "def _rdown_v4(v, ctx): print(f\"[v4] X={ctx.x:.1f}\")\n", "def _rdown_v5(v, ctx): print(f\"[v5] X={ctx.x:.1f}\")\n", "\n", "# v2 is active\n", "viewer.register_action('my demo', rdown_handler=_rdown_v4, overload=True)\n", "# → saves v2, installs v4\n", "\n", "viewer.register_action('my demo', rdown_handler=_rdown_v5, overload=True)\n", "# → slot 'rdown' already saved (v2); v4 is lost (WARNING, no additional save)\n", "\n", "print(\"Active handler:\",\n", " viewer._custom_rdown_handlers.get('my demo').__name__)\n", "saved_rdown = viewer._saved_handlers.get('my demo', {}).get('rdown')\n", "print(\"Saved (rdown):\",\n", " saved_rdown.__name__ if saved_rdown else '')\n", "\n", "# Restoration: returns to v2 (not v4)\n", "viewer.unregister_action('my demo')\n", "current = viewer._custom_rdown_handlers.get('my demo')\n", "print(\"After unregister:\",\n", " current.__name__ if current else '')\n" ] }, { "cell_type": "markdown", "id": "914ca02b", "metadata": {}, "source": [ "## 9 — Final cleanup\n" ] }, { "cell_type": "code", "execution_count": null, "id": "9c17167c", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "INFO ACTION : End overload demo\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Cleanup done.\n", "_custom_rdown_handlers: {}\n", "_saved_handlers: {}\n" ] }, { "ename": "", "evalue": "", "output_type": "error", "traceback": [ "\u001b[1;31mThe Kernel crashed while executing code in the current cell or a previous cell. \n", "\u001b[1;31mPlease review the code in the cell(s) to identify a possible cause of the failure. \n", "\u001b[1;31mClick here for more info. \n", "\u001b[1;31mView Jupyter log for further details." ] } ], "source": [ "viewer.end_action('End overload demo')\n", "viewer.unregister_action('my demo') # idempotent\n", "print(\"Cleanup done.\")\n", "print(\"_custom_rdown_handlers:\", viewer._custom_rdown_handlers)\n", "print(\"_saved_handlers:\", viewer._saved_handlers)\n" ] }, { "cell_type": "markdown", "id": "b65daba6", "metadata": {}, "source": [ "## API summary\n", "\n", "```python\n", "# Initial registration (no conflict)\n", "viewer.register_action('my action', rdown_handler=fn_v1)\n", "\n", "# Permanent replacement — WARNING if slot already occupied, no save\n", "viewer.register_action('my action', rdown_handler=fn_v2)\n", "\n", "# Temporary overload — WARNING + saves the previous handler\n", "viewer.register_action('my action', rdown_handler=fn_v3, overload=True)\n", "\n", "# Unregister — restores fn_v2 if overload=True was used\n", "viewer.unregister_action('my action')\n", "```\n", "\n", "### Rules\n", "\n", "- The save happens only **once per slot** (first `overload=True` call wins).\n", "- A second `overload=True` on the same slot emits a `WARNING` but\n", " **does not replace** the existing save → restoration always unwinds back\n", " to the handler that was present before the first overload.\n", "- `unregister_action` is **idempotent**: no-op if the action is unknown.\n", "- `overload=False` on an occupied slot → `WARNING` only, previous handler permanently lost.\n" ] } ], "metadata": { "kernelspec": { "display_name": "python311", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.9" } }, "nbformat": 4, "nbformat_minor": 5 }