Skip to content

GUI: Add "Copy in Python" button to module dialogs#7237

Draft
polucifier wants to merge 5 commits intoOSGeo:mainfrom
polucifier:gui-copy-python
Draft

GUI: Add "Copy in Python" button to module dialogs#7237
polucifier wants to merge 5 commits intoOSGeo:mainfrom
polucifier:gui-copy-python

Conversation

@polucifier
Copy link
Copy Markdown

@polucifier polucifier commented Mar 29, 2026

Description

This PR introduces a new button "Copy in Python" to all GRASS module dialogs in the wxGUI. While the existing "Copy" button provides the shell command, this new feature generates valid Python syntax using the grass.script library (standard gs alias).

image

The goal is to streamline the workflow for users transitioning from the GUI to Python scripting and to provide a quick way to prototype scripts directly from the interface.

Key Features & Improvements

  • Standard Syntax: Generates code using the gs.run_command() pattern, which is the most common entry point for GRASS scripting.
  • Robust String Escaping: Uses Python's built-in repr() function for parameter values. This ensures that Windows file paths (backslashes), quotes, and special characters are handled correctly without breaking the Python string literal.
  • Python Keyword Handling: Automatically detects reserved Python keywords (e.g., from, in, global). If a module parameter name conflicts with a keyword, the PR uses dictionary unpacking (e.g., **{'from': 'map'}) to ensure the generated code is syntactically valid.
  • Global Parameters: Correctly translates GUI-specific items like --overwrite, --verbose, and --quiet into their Pythonic True/False arguments.
  • Future-Proofing: Includes a fallback for any other double-dash flags (--) that might be introduced by third-party addons.

Example Output

For a module like v.distance -p -s --overwrite from=durham@user1 to=bridges@PERMANENT upload=dist format=plain, the button generates:

gs.run_command('v.distance', flags='ps', overwrite=True, to='bridges@PERMANENT', upload='dist', format='plain', **{'from': 'durham@user1'})

Testing

  • Manually tested with various modules
  • Verified that the generated strings are valid and executable when pasted into the GRASS Python console.
image

@echoix
Copy link
Copy Markdown
Member

echoix commented Mar 29, 2026

I like the concept, similar to what there is in QGIS, even if I never used these in qgis (only the copy as json and paste it again later on to rehave the same parameters set)

@github-actions github-actions bot added GUI wxGUI related Python Related code is in Python labels Mar 29, 2026
Comment thread gui/wxpython/gui_core/forms.py Outdated
Comment thread gui/wxpython/gui_core/forms.py Outdated
@polucifier
Copy link
Copy Markdown
Author

I like the concept, similar to what there is in QGIS, even if I never used these in qgis (only the copy as json and paste it again later on to rehave the same parameters set)

Thanks for the feedback, @echoix! I really like the QGIS workflow you mentioned. I'm going to convert this PR to a Draft for now while I refine the UI.

My plan is to replace the standalone 'Copy in Python' button with a MenuButton (dropdown) integrated into the main 'Copy' button. It will include:

  • Copy as Shell command (default)
  • Copy as Python script (using the logic from this PR)
  • Copy as JSON (as you suggested)

To complete the loop, I will also try to implement a 'Paste' button that can take a JSON string and automatically populate the dialog fields. I'll push these updates later this evening.

@polucifier polucifier marked this pull request as draft March 29, 2026 11:16
@ninsbl
Copy link
Copy Markdown
Member

ninsbl commented Mar 29, 2026

Nice. Did you consider returning tools API syntax?

@polucifier
Copy link
Copy Markdown
Author

Nice. Did you consider returning tools API syntax?

Hi @ninsbl, thank you very much for the suggestion! That's a great idea. Since I'm already reworking the 'Copy' button into a dropdown menu, I'll add two more options to it: 'Copy as Python (Tools API)' and 'Copy as Python (PyGRASS)'.

Combined with the Script API and JSON options, this should cover all the major Python workflows in GRASS. I'll push the updates later.

@polucifier
Copy link
Copy Markdown
Author

I have implemented a new Split Copy Button and a Paste functionality for the module dialogs. This update provides users with multiple export formats and a way to quickly restore or share tool parameters via JSON.

New UI layout with the Split Copy button and the Paste button:
image

The dropdown menu offers various formats, including the new Tools API and JSON:
image

The following examples demonstrate the generated output. Note that Python reserved keywords (e.g., from) are correctly handled using dictionary unpacking to ensure valid, executable code:
Shell command:

v.distance -p -s --overwrite from=durham@user1 to=bridges@PERMANENT upload=dist format=plain

Python (Script API):

gs.run_command('v.distance', flags='ps', overwrite=True, to='bridges@PERMANENT', upload='dist', format='plain', **{'from': 'durham@user1'})

Python (Tools API):

Tools().v_distance(flags='ps', overwrite=True, to='bridges@PERMANENT', upload='dist', format='plain', **{'from': 'durham@user1'})

Python (PyGRASS):

Module('v.distance', flags='ps', overwrite=True, to='bridges@PERMANENT', upload='dist', format='plain', **{'from': 'durham@user1'})

JSON format:

{
    "module": "v.distance",
    "params": {
        "flags": "ps",
        "overwrite": "True",
        "from": "durham@user1",
        "to": "bridges@PERMANENT",
        "upload": "dist",
        "format": "plain"
    }
}

Demonstration of the Paste functionality, which populates dialog fields and updates the command preview in real-time:
Paste.webm

@polucifier polucifier marked this pull request as ready for review March 30, 2026 00:48
@ninsbl
Copy link
Copy Markdown
Member

ninsbl commented Mar 30, 2026

Cool. Just for clarification: does Copy as JSON copy json equivalent to --json, or just the the parameters that were set with their values?

@ninsbl
Copy link
Copy Markdown
Member

ninsbl commented Mar 30, 2026

I now see the JSON structure in your comment above... Background for my question has been: #6697

Comment thread gui/wxpython/gui_core/forms.py Outdated
Comment thread gui/wxpython/gui_core/forms.py Outdated
Comment thread gui/wxpython/gui_core/forms.py Outdated
@polucifier polucifier requested a review from petrasovaa April 9, 2026 21:58
@landam landam added this to the 8.6.0 milestone Apr 15, 2026
@pesekon2
Copy link
Copy Markdown
Contributor

The functionality is great. I really like it. I would have some doubts about the usability of the Paste button but if @echoix says he can imagine users for that, why not having it.

I tested the functionality and it seems fine to me. However, I don't see the arrow on the dropout of the Copy button. It seems seems to me more like a wxPython issue than being connected to the PR. What is your version of wx? Mine is 4.2.3.

Screenshot from 2026-04-15 15-48-40

@landam landam added the enhancement New feature or request label Apr 15, 2026
Comment thread gui/wxpython/gui_core/forms.py Outdated
polucifier and others added 4 commits April 20, 2026 00:48
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…dialogs

This commit introduces advanced clipboard functionality to the GRASS
module dialogs, drastically improving reproducibility and workflow sharing.

New Features:
- Replaced the standard "Copy" button with a backwards-compatible split
  button (using wx.BoxSizer to support older wxPython versions).
- Added a dropdown to copy module parameters in multiple formats: Shell
  command (default), Python (Script API), Python (Tools API), PyGRASS,
  and JSON.
- Added a "Paste" button that reads a JSON payload from the system
  clipboard and automatically populates the module dialog parameters
  and flags.

Paste Logic Improvements:
- Robustly handles wx widget type coercion (e.g., wx.SpinCtrl, wx.Choice,
  ColourSelect, and multi-selection wx.CheckBox arrays).
- Accurately mimics command-line behavior by resetting any omitted
  parameters to their standard defaults and actively unchecking omitted flags.
- Safely parses shorthand flags (e.g., "flags": "ps") without triggering
  false positives on full flag names.
- Explicitly ignores "ModelParam" widgets to prevent interference with
  the Graphical Modeler.
…d formatting

- forms.py: Catch specific exceptions (json.JSONDecodeError, ValueError)
  in OnPaste instead of a bare Exception to avoid swallowing unrelated bugs.
- forms.py: Fix a formatting bug where the generated Python command
  started with a leading comma when no flags were present.
- forms.py/task.py: Move _get_formatted_params and _extract_params_dict
  from GUI forms to grass.script.task (as cmd_to_python_args and
  cmd_to_dict) so they can be reused outside the GUI.
@polucifier
Copy link
Copy Markdown
Author

Hi @pesekon2, thanks for the feedback! I'm glad the functionality works well for you.

Regarding the missing arrow: I have the same version of wxPython (4.2.3). You were right—the issue was most likely caused by the previous implementation using a text-based character for the chevron, which renders inconsistently across different platforms and themes.

To resolve this and ensure a professional look on all systems, I have completely overhauled the visual implementation in the latest commit.

Technical Improvements:

  • Custom Drawing with wx.GCDC: Instead of relying on text characters, I am now drawing the chevron icon manually. Using wx.GCDC ensures smooth, anti-aliased lines.
  • True Transparency: I switched to wx.Bitmap.FromRGBA to create a 32-bit bitmap. This eliminates "black box" artifacts and ensures the button background blends perfectly with the native UI.
  • Automatic Theme Support: The icon color is now dynamically pulled from wx.SYS_COLOUR_BTNTEXT, making it fully compatible with both light and dark system themes.
  • Pixel-Perfect Alignment: I've implemented platform-specific vertical offsets (including a -2px shift for Windows). The chevron is now mathematically and visually aligned with the baseline of the "Copy" text (specifically the letter 'C'), verified down to the pixel in GIMP.

Here is the final result across different environments:

Linux - Dark Theme
linux_dark

Linux - Light Theme
linux_bright

Windows - Light Theme
windows_bright

…t alignment

Improved the visual appearance of the Copy button's dropdown arrow by
replacing the text-based character with a custom-drawn chevron.

Key improvements:
- Switched to wx.GCDC for smooth, anti-aliased drawing of the chevron.
- Utilized 32-bit RGBA bitmaps to ensure perfect transparency and
  compatibility with both light and dark system themes.
- Implemented precise vertical offsets (-2px on Windows) to align the
  chevron's tip perfectly with the text baseline based on GIMP-verified
  measurements.
- Refined the split-button "glue" on Windows to ensure a seamless
  native-looking border.
gc.DrawLine(4, 8 + y_offset, 8, 13 + y_offset)
gc.DrawLine(8, 13 + y_offset, 12, 8 + y_offset)

del gc
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed? Is it not handled by the common garbage collector?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I know, while Python's GC eventually handles memory, wxPython objects are wrappers around C++ structures that often require deterministic destruction.

In this case, wx.GCDC holds a reference to the underlying wx.MemoryDC. According to the wx.MemoryDC documentation:

"Note that the memory DC must be deleted (or the bitmap selected out of it) before a bitmap can be reselected into another memory DC."

My understanding is that by explicitly calling del gc, we force the C++ destructor of the graphics context to run immediately, flushing the drawing and releasing the lock on the MemoryDC. Relying on the non-deterministic Python GC can sometimes lead to "DC is busy" errors or incomplete rendering because the GCDC might still be "alive" when mdc.SelectObject(wx.NullBitmap) is called.

@pesekon2
Copy link
Copy Markdown
Contributor

Thanks. Now I got the arrow there. A weird and ugly one but something is there.

Screenshot from 2026-04-21 15-06-19

But I got two more points:

  1. The button Paste is not really self-exlanatory. Could the text be clearer? Like Paste JSON settings? I don't want to make the bar too long (but cannot come with anything shorter now) but currently, the user doesn't really know what type should s/he paste there. Which comes to:
  2. It should raise a clearer error message if something wrong is pasted. Currently, I just get Paste Error: Expecting value: line 1 column 1 (char 0).

val
if isinstance(val, bool)
else str(val).lower() in ("true", "1", "yes")
else str(val).lower() in {"true", "1", "yes"}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the meaning of this change? I thought it might be the performance (although the difference would be absolutely unnoticeable) but I tried testing these two and the tuple version is slightly faster.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these accepted values for the flags?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change from a tuple to a set literal was suggested by Ruff:
#7237 (review)

While the performance difference is negligible for a collection of this size, I assume that Ruff enforces this as a best practice for membership tests (in) to maintain consistent style across the codebase.

Regarding the specific values: these are all standard ways different GRASS modules and XML descriptions represent a "true" or "active" state for flags.

@polucifier polucifier marked this pull request as draft April 21, 2026 14:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request GUI wxGUI related libraries Python Related code is in Python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants