GUI: Add "Copy in Python" button to module dialogs#7237
GUI: Add "Copy in Python" button to module dialogs#7237polucifier wants to merge 5 commits intoOSGeo:mainfrom
Conversation
|
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) |
439daef to
d2e00d3
Compare
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:
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. |
|
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. |
|
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: The dropdown menu offers various formats, including the new Tools API and JSON: 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: v.distance -p -s --overwrite from=durham@user1 to=bridges@PERMANENT upload=dist format=plainPython (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: |
|
Cool. Just for clarification: does |
|
I now see the JSON structure in your comment above... Background for my question has been: #6697 |
|
The functionality is great. I really like it. I would have some doubts about the usability of the I tested the functionality and it seems fine to me. However, I don't see the arrow on the dropout of the
|
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.
a11ca0b to
57dbba3
Compare
|
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:
Here is the final result across different environments: |
…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.
57dbba3 to
b0dc770
Compare
| gc.DrawLine(4, 8 + y_offset, 8, 13 + y_offset) | ||
| gc.DrawLine(8, 13 + y_offset, 12, 8 + y_offset) | ||
|
|
||
| del gc |
There was a problem hiding this comment.
Why is this needed? Is it not handled by the common garbage collector?
There was a problem hiding this comment.
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.
| val | ||
| if isinstance(val, bool) | ||
| else str(val).lower() in ("true", "1", "yes") | ||
| else str(val).lower() in {"true", "1", "yes"} |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Are these accepted values for the flags?
There was a problem hiding this comment.
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.



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.scriptlibrary (standardgsalias).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
gs.run_command()pattern, which is the most common entry point for GRASS scripting.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.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.--overwrite,--verbose, and--quietinto their PythonicTrue/Falsearguments.--) 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:Testing