readymade 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- readymade-0.0.1/.gitattributes +1 -0
- readymade-0.0.1/.gitignore +7 -0
- readymade-0.0.1/LICENSE +21 -0
- readymade-0.0.1/PKG-INFO +99 -0
- readymade-0.0.1/README.md +69 -0
- readymade-0.0.1/TODO.md +47 -0
- readymade-0.0.1/pyproject.toml +44 -0
- readymade-0.0.1/readymade/__init__.py +11 -0
- readymade-0.0.1/readymade/appearance.py +96 -0
- readymade-0.0.1/readymade/codegen.py +104 -0
- readymade-0.0.1/readymade/controls.py +1508 -0
- readymade-0.0.1/readymade/dialogs.py +237 -0
- readymade-0.0.1/readymade/events.py +252 -0
- readymade-0.0.1/readymade/form.py +379 -0
- readymade-0.0.1/readymade/formatting.py +120 -0
- readymade-0.0.1/readymade/formfile.py +138 -0
- readymade-0.0.1/readymade/forms.py +51 -0
- readymade-0.0.1/readymade/ide/__init__.py +1 -0
- readymade-0.0.1/readymade/ide/__main__.py +3 -0
- readymade-0.0.1/readymade/ide/app.py +1038 -0
- readymade-0.0.1/readymade/ide/arrange.py +83 -0
- readymade-0.0.1/readymade/ide/assist.py +197 -0
- readymade-0.0.1/readymade/ide/codeview.py +694 -0
- readymade-0.0.1/readymade/ide/designer.py +488 -0
- readymade-0.0.1/readymade/ide/editors.py +801 -0
- readymade-0.0.1/readymade/ide/helptext.py +143 -0
- readymade-0.0.1/readymade/ide/highlight.py +106 -0
- readymade-0.0.1/readymade/ide/linting.py +78 -0
- readymade-0.0.1/readymade/ide/menueditor.py +339 -0
- readymade-0.0.1/readymade/ide/panels.py +440 -0
- readymade-0.0.1/readymade/ide/tooldialog.py +130 -0
- readymade-0.0.1/readymade/ide/tracebacks.py +45 -0
- readymade-0.0.1/readymade/keys.py +76 -0
- readymade-0.0.1/readymade/lint.py +144 -0
- readymade-0.0.1/readymade/menus.py +202 -0
- readymade-0.0.1/readymade/package.py +119 -0
- readymade-0.0.1/readymade/pictures.py +203 -0
- readymade-0.0.1/readymade/project.py +352 -0
- readymade-0.0.1/readymade/py.typed +0 -0
- readymade-0.0.1/readymade/run.py +72 -0
- readymade-0.0.1/readymade/shapes.py +222 -0
- readymade-0.0.1/readymade/usersettings.py +57 -0
- readymade-0.0.1/tests/conftest.py +88 -0
- readymade-0.0.1/tests/test_appearance.py +386 -0
- readymade-0.0.1/tests/test_arrange.py +37 -0
- readymade-0.0.1/tests/test_assist.py +91 -0
- readymade-0.0.1/tests/test_canvas.py +239 -0
- readymade-0.0.1/tests/test_codegen.py +76 -0
- readymade-0.0.1/tests/test_codeview.py +282 -0
- readymade-0.0.1/tests/test_controls.py +232 -0
- readymade-0.0.1/tests/test_dialogs.py +125 -0
- readymade-0.0.1/tests/test_drawing.py +195 -0
- readymade-0.0.1/tests/test_events.py +249 -0
- readymade-0.0.1/tests/test_formfile.py +54 -0
- readymade-0.0.1/tests/test_highlight.py +76 -0
- readymade-0.0.1/tests/test_ide.py +1144 -0
- readymade-0.0.1/tests/test_keyboard.py +310 -0
- readymade-0.0.1/tests/test_lint.py +134 -0
- readymade-0.0.1/tests/test_menus.py +137 -0
- readymade-0.0.1/tests/test_package.py +67 -0
- readymade-0.0.1/tests/test_pictures.py +292 -0
- readymade-0.0.1/tests/test_project.py +290 -0
- readymade-0.0.1/tests/test_runtime.py +106 -0
- readymade-0.0.1/tests/test_tracebacks.py +74 -0
- readymade-0.0.1/tools/drive_ide.py +675 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
* text eol=lf
|
readymade-0.0.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Joseph E. Coleman
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
readymade-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: readymade
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Visual Basic-style forms for Python: draw a form, double-click to write its code, press F5
|
|
5
|
+
Author: Joseph E. Coleman
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Keywords: form designer,gui,ide,python,rad,tkinter,visual basic
|
|
9
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
10
|
+
Classifier: Environment :: MacOS X
|
|
11
|
+
Classifier: Environment :: Win32 (MS Windows)
|
|
12
|
+
Classifier: Environment :: X11 Applications
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
23
|
+
Classifier: Topic :: Software Development :: User Interfaces
|
|
24
|
+
Requires-Python: >=3.11
|
|
25
|
+
Provides-Extra: ide
|
|
26
|
+
Requires-Dist: jedi>=0.19; extra == 'ide'
|
|
27
|
+
Requires-Dist: pillow>=10.1; extra == 'ide'
|
|
28
|
+
Requires-Dist: pygments>=2.15; extra == 'ide'
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# readymade
|
|
32
|
+
|
|
33
|
+
**readymade** is a Python language version of a classic VB style coding experience.
|
|
34
|
+
This is for small tools that might need a user interface. You draw on a form,
|
|
35
|
+
double-click a button to write its code, press F5 to run. **readymade** aims to
|
|
36
|
+
leave almost no time between having an idea and running the program.
|
|
37
|
+
|
|
38
|
+
> Still early: readymade is a pre-alpha. It works, and it's changing quickly.
|
|
39
|
+
|
|
40
|
+
## What it does
|
|
41
|
+
|
|
42
|
+
- **A form designer.** Draw controls from the Toolbox (Label, TextBox, Button,
|
|
43
|
+
CheckBox, OptionButton, ComboBox, ListBox, Frame, Image, Canvas, Timer), then set
|
|
44
|
+
their properties in the Properties panel. Align and size them with the Format menu,
|
|
45
|
+
design menus in the Menu Editor, and set the tab order by clicking.
|
|
46
|
+
- **Double-click to code.** Double-clicking a control takes you to its event handler,
|
|
47
|
+
written for you as a method named after it: `Button1_click`, `Form_load`.
|
|
48
|
+
- **F5 to run.** While your program runs, the Immediate window runs Python inside it.
|
|
49
|
+
A run-time error takes you to the line that failed.
|
|
50
|
+
- **Plain Python.** A form is two files: a `.form` file (JSON, for the layout) and a
|
|
51
|
+
`.py` file (your code). One can run the form directly with python without starting
|
|
52
|
+
the IDE by running just the python file, `python path/to/Form.py`.
|
|
53
|
+
- **Projects.** A project has a `pyproject.toml`, its own virtual environment and
|
|
54
|
+
libraries (Project > Add Library), linting and formatting with the tools you choose,
|
|
55
|
+
and Make EXE (via PyInstaller), which turns it into a program that runs without Python.
|
|
56
|
+
- **The toolkit is Tkinter**, which comes with Python, so programs need nothing else
|
|
57
|
+
installed. Pillow, if you add it to a project, opens more picture formats and lets
|
|
58
|
+
programs draw on pictures.
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from readymade import Form, msgbox
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class Form1(Form):
|
|
65
|
+
def Button1_click(self):
|
|
66
|
+
name = self.TextBox1.text.strip() or "World"
|
|
67
|
+
msgbox(f"Hello, {name}!")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
if __name__ == "__main__":
|
|
71
|
+
Form1.run()
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Install and start
|
|
75
|
+
|
|
76
|
+
readymade needs Python 3.11 or later, with Tkinter (Python's Windows and macOS
|
|
77
|
+
installers include it; on Linux it may be a separate package, like `python3-tk`).
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
python -m pip install "readymade[ide]"
|
|
81
|
+
readymade my-first-project
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`readymade` opens the IDE, creating the project folder if it doesn't exist
|
|
85
|
+
(`python -m readymade.ide` does the same). The `ide` extra adds syntax coloring
|
|
86
|
+
(Pygments), completion with Go to Definition (Jedi), and JPEG and other pictures in
|
|
87
|
+
the designer (Pillow). The IDE works without them. A program that uses Pillow, to
|
|
88
|
+
show those pictures or draw on them, needs it in its own project too
|
|
89
|
+
(Project > Add Library), so that Make EXE includes it.
|
|
90
|
+
|
|
91
|
+
Press F1 in the IDE for help on whatever you're working on.
|
|
92
|
+
|
|
93
|
+
## Disclosure
|
|
94
|
+
|
|
95
|
+
I used Claude.
|
|
96
|
+
|
|
97
|
+
## License
|
|
98
|
+
|
|
99
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# readymade
|
|
2
|
+
|
|
3
|
+
**readymade** is a Python language version of a classic VB style coding experience.
|
|
4
|
+
This is for small tools that might need a user interface. You draw on a form,
|
|
5
|
+
double-click a button to write its code, press F5 to run. **readymade** aims to
|
|
6
|
+
leave almost no time between having an idea and running the program.
|
|
7
|
+
|
|
8
|
+
> Still early: readymade is a pre-alpha. It works, and it's changing quickly.
|
|
9
|
+
|
|
10
|
+
## What it does
|
|
11
|
+
|
|
12
|
+
- **A form designer.** Draw controls from the Toolbox (Label, TextBox, Button,
|
|
13
|
+
CheckBox, OptionButton, ComboBox, ListBox, Frame, Image, Canvas, Timer), then set
|
|
14
|
+
their properties in the Properties panel. Align and size them with the Format menu,
|
|
15
|
+
design menus in the Menu Editor, and set the tab order by clicking.
|
|
16
|
+
- **Double-click to code.** Double-clicking a control takes you to its event handler,
|
|
17
|
+
written for you as a method named after it: `Button1_click`, `Form_load`.
|
|
18
|
+
- **F5 to run.** While your program runs, the Immediate window runs Python inside it.
|
|
19
|
+
A run-time error takes you to the line that failed.
|
|
20
|
+
- **Plain Python.** A form is two files: a `.form` file (JSON, for the layout) and a
|
|
21
|
+
`.py` file (your code). One can run the form directly with python without starting
|
|
22
|
+
the IDE by running just the python file, `python path/to/Form.py`.
|
|
23
|
+
- **Projects.** A project has a `pyproject.toml`, its own virtual environment and
|
|
24
|
+
libraries (Project > Add Library), linting and formatting with the tools you choose,
|
|
25
|
+
and Make EXE (via PyInstaller), which turns it into a program that runs without Python.
|
|
26
|
+
- **The toolkit is Tkinter**, which comes with Python, so programs need nothing else
|
|
27
|
+
installed. Pillow, if you add it to a project, opens more picture formats and lets
|
|
28
|
+
programs draw on pictures.
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
from readymade import Form, msgbox
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Form1(Form):
|
|
35
|
+
def Button1_click(self):
|
|
36
|
+
name = self.TextBox1.text.strip() or "World"
|
|
37
|
+
msgbox(f"Hello, {name}!")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
if __name__ == "__main__":
|
|
41
|
+
Form1.run()
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Install and start
|
|
45
|
+
|
|
46
|
+
readymade needs Python 3.11 or later, with Tkinter (Python's Windows and macOS
|
|
47
|
+
installers include it; on Linux it may be a separate package, like `python3-tk`).
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
python -m pip install "readymade[ide]"
|
|
51
|
+
readymade my-first-project
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`readymade` opens the IDE, creating the project folder if it doesn't exist
|
|
55
|
+
(`python -m readymade.ide` does the same). The `ide` extra adds syntax coloring
|
|
56
|
+
(Pygments), completion with Go to Definition (Jedi), and JPEG and other pictures in
|
|
57
|
+
the designer (Pillow). The IDE works without them. A program that uses Pillow, to
|
|
58
|
+
show those pictures or draw on them, needs it in its own project too
|
|
59
|
+
(Project > Add Library), so that Make EXE includes it.
|
|
60
|
+
|
|
61
|
+
Press F1 in the IDE for help on whatever you're working on.
|
|
62
|
+
|
|
63
|
+
## Disclosure
|
|
64
|
+
|
|
65
|
+
I used Claude.
|
|
66
|
+
|
|
67
|
+
## License
|
|
68
|
+
|
|
69
|
+
MIT. See [LICENSE](LICENSE).
|
readymade-0.0.1/TODO.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# To do
|
|
2
|
+
|
|
3
|
+
Ideas and gaps noted along the way, roughly by area.
|
|
4
|
+
|
|
5
|
+
## Runtime
|
|
6
|
+
- **Menu item `visible`**: Tk can't hide a menu entry; supporting it means rebuilding the menu.
|
|
7
|
+
- **Break mode**: pause a running program at an error, as VB did (needs a debugger).
|
|
8
|
+
- **macOS shortcuts**: treat "Ctrl" as ⌘ (Command) and "Alt" as Option on macOS, the way Qt and Electron's CmdOrCtrl do, and show native ⌘O labels. Needs a Mac to check how Tk on macOS parses accelerator labels, and whether its native menus already fire the shortcut (which would make readymade's own binding run handlers twice). The IDE's own shortcuts (Ctrl+S, Ctrl+Z, ...) should follow the same rule.
|
|
9
|
+
- **Check keypress names on a Mac**: `readymade/keys.py` already reports ⌘ as "Ctrl" (so ⌘S is "Ctrl+S"), the Control key as "MacCtrl", and Option as "Alt" unless it types a character. The state bits (Command 0x8, Option 0x10) and the modifier keys' own names (is ⌘ alone "Meta_L"?) come from Tk's documentation, not a real Mac. To check: a form with a TextBox whose keypress handler prints `key`; ⌘S, ⌃S, ⌥S, ⌥E then E, ⌘ alone and Shift+⌘Z should print `Ctrl+S`, `MacCtrl+S`, `ß`, `é`, `Ctrl`, `Ctrl+Shift+Z`.
|
|
10
|
+
|
|
11
|
+
## IDE
|
|
12
|
+
- **Copy and paste**: optionally bring the controls' event handlers along (renamed to match), which VB never did. Also a selection box inside a Frame (today it only works on the form itself; Ctrl+click works anywhere).
|
|
13
|
+
- **Project Explorer**: remove a form, module or library.
|
|
14
|
+
- **Environments**: create conda/mamba environments (existing ones work via Choose Python Interpreter).
|
|
15
|
+
- **Add Library**: keep comments inside the `dependencies` list when it's rewritten.
|
|
16
|
+
- **Code window**: after jumping to a run-time error, name the handler in the Object/Event dropdowns.
|
|
17
|
+
- **Renaming a form**: also update references to it in other files (like `forms.OldName`).
|
|
18
|
+
|
|
19
|
+
## Code editor
|
|
20
|
+
- **Settings**: indent width (4 spaces today, a constant in `readymade/ide/codeview.py`), font, theme. Needs an IDE settings dialog.
|
|
21
|
+
- **Themes**: a dark theme; the colors are all in `readymade/ide/highlight.py`. Also mark a run-time error's line in the line numbers.
|
|
22
|
+
- **Folding** and an outline, from an error-tolerant parser (parso, MIT). The same parsing would let Enter see past the current line: today, after a call spread over several lines, the next line keeps the continuation indent instead of returning to the statement's level.
|
|
23
|
+
- **A language server option (LSP)**: completion and definitions use Jedi today. An LSP client would let people choose Pyright (or basedpyright, which pip-installs with its own Node.js), python-lsp-server, jedi-language-server or Ruff's server, configured like linting, and would also bring hover info, signature help and rename. Pylance can't be used outside Microsoft's VS Code. The form-control declarations (assist.py) carry over, as the IDE chooses the text it sends.
|
|
24
|
+
- **Completion follow-ups**: `forms.<name>` (forms are found by name at run time), signatures and docstrings beside the list, Back after Go to Definition (Alt+Left).
|
|
25
|
+
- **Help follow-ups**: show help beside the completion list for the chosen entry; help on hover; links between topics (a property's help linking to its control); format reStructuredText and Google-style docstrings instead of showing them as plain text.
|
|
26
|
+
- Color codes get stripped out via PYTHON_COLORS or NO_COLOR because that broke the "jump to error line" feature. Is there ever a situation where colors in help could be useful?
|
|
27
|
+
- **Formatting follow-ups**: format just the selection; let a project insist on no formatting (today an unset project falls back to each person's default); format in the background instead of briefly waiting with a busy cursor.
|
|
28
|
+
- **Linting follow-ups**: a Problems list next to the Immediate window (every problem in the project, click to jump); quick fixes (`ruff check --fix`); different colors for errors and warnings; pylint/mypy checking as you type via a temporary copy.
|
|
29
|
+
- These libraries would be IDE-only dependencies, like Pygments in the `ide` extra (`pip install -e ".[ide]"`), so programs never need them.
|
|
30
|
+
|
|
31
|
+
## Bigger features
|
|
32
|
+
- **Make EXE options**: an icon, one-folder instead of one-file, version details. (Extra files are bundled: pictures the forms show, and `[tool.readymade] include`; perhaps an Include list in the Project Explorer rather than editing pyproject.toml.) Perhaps a small dialog, with various build options. Options true everywhere (icon, name) belong in `pyproject.toml`'s `[tool.readymade]`; anything tied to one machine (a signing certificate, an interpreter) in `.readymade/local.json`, kept per machine and out of git.
|
|
33
|
+
- Longer term idea, executables need to be able to be signed.
|
|
34
|
+
- **More controls**: third-party controls from installed libraries appearing in the Toolbox.
|
|
35
|
+
- **Image drawing follow-ups**: flood fill (Pillow's `ImageDraw.floodfill`); mouse coordinates in the picture's pixels when it's scaled (stretch, zoom) or centered; anti-aliased shapes (Pillow draws them jagged; drawing larger and shrinking is the usual trick); undo for paint programs (keep copies of the picture).
|
|
36
|
+
- **Canvas follow-ups**: keyboard events (a Tk canvas takes no focus by default); scrolling and zooming a canvas bigger than the control; saving a canvas as a picture (Tk can only write PostScript, so this needs drawing through Pillow too); dragging shapes with less code (perhaps `shape.draggable = True`).
|
|
37
|
+
- **Appearance follow-ups**: a form `font` that new controls start with (as VB's form Font was); colors for disabled controls (Tk grays them its own way); `selectcolor` for check box and option button indicators. Known limits: macOS buttons keep the system's colors, and Windows' native theme may ignore a ComboBox's field color (both are Tk's).
|
|
38
|
+
- **More properties**: TextBox `readonly`, `password` and `maxlength`; `tooltip`; `tag`.
|
|
39
|
+
- **Keyboard follow-ups**: `tabstop` (leave a control out of the Tab order without disabling it); arrow keys moving between the OptionButtons of a group, with only the chosen one a Tab stop (as in Windows); access keys (Alt+letter from an `&` in a caption); a `keydown`/`keyup` pair for games (keypress repeats while a key is held).
|
|
40
|
+
- **Image follow-ups**: show an Image's outline in the designer when its picture is smaller than the control (sizing normal, center or zoom); offer to delete a copied picture from images/ when nothing uses it any more; animated GIFs; pictures on Buttons and Labels (Tk supports both).
|
|
41
|
+
|
|
42
|
+
## Biggest features
|
|
43
|
+
- **Form Packing**: Tkinter supports more than just absolute positioning and sizing for widgets. Supporting grid or packing schemes would really elevate this above what VB could ever do.
|
|
44
|
+
- The Format menu (align, same size, spacing, center, order) is for absolutely placed controls only; its pure geometry lives in `readymade/ide/arrange.py`. Grid/pack containers would disable it for their controls or offer their own equivalents (sticky, anchor, padx/pady, weights). Selections already stay within one container, so the container can decide which tools apply.
|
|
45
|
+
- **Backend Swappable**: Being able to design for a PyQt6 or some other GUI backend instead of Tkinter for a project. There are two optional paths to this: One is keeping Tkinter for the IDE and designing a project with a different GUI backend, or having the IDE match the target project GUI. Is a GUI target potentially a plugin? Either way, this looks like a separation of concerns type of refactor.
|
|
46
|
+
- Keep readymade's own API free of Tk: shapes are readymade objects, colors `#rrggbb`, angles in degrees, fonts `(family, size, style)`. Still Tk-specific: color names Canvas shapes accept beyond `#rrggbb`, and `.widget` / `form.root` (fine as documented escape hatches). If Qt: PySide6 (LGPL) rather than PyQt6 (GPL), for programs made with Make EXE.
|
|
47
|
+
- **Plugins**: A plugin architecture to extend features. Allow other people to create plugins like VB or VS Code allows.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "readymade"
|
|
3
|
+
version = "0.0.1"
|
|
4
|
+
description = "Visual Basic-style forms for Python: draw a form, double-click to write its code, press F5"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
8
|
+
authors = [{ name = "Joseph E. Coleman" }]
|
|
9
|
+
requires-python = ">=3.11"
|
|
10
|
+
dependencies = []
|
|
11
|
+
keywords = ["gui", "rad", "form designer", "ide", "tkinter", "python", "visual basic"]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 2 - Pre-Alpha",
|
|
14
|
+
"Environment :: Win32 (MS Windows)",
|
|
15
|
+
"Environment :: X11 Applications",
|
|
16
|
+
"Environment :: MacOS X",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Intended Audience :: End Users/Desktop",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
25
|
+
"Programming Language :: Python :: 3.14",
|
|
26
|
+
"Topic :: Software Development :: User Interfaces",
|
|
27
|
+
"Topic :: Software Development :: Code Generators",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.optional-dependencies]
|
|
31
|
+
ide = ["pygments>=2.15", "jedi>=0.19", "pillow>=10.1"] # coloring, completion and Go to Definition, pictures in the designer; the IDE works without them
|
|
32
|
+
|
|
33
|
+
[project.scripts]
|
|
34
|
+
readymade = "readymade.ide.app:main"
|
|
35
|
+
|
|
36
|
+
[dependency-groups]
|
|
37
|
+
dev = ["pytest"]
|
|
38
|
+
|
|
39
|
+
[build-system]
|
|
40
|
+
requires = ["hatchling>=1.27"] # for the license fields above
|
|
41
|
+
build-backend = "hatchling.build"
|
|
42
|
+
|
|
43
|
+
[tool.hatch.build.targets.sdist]
|
|
44
|
+
exclude = ["examples"] # they hold pictures that aren't for publishing, and each machine's interpreter choice
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""readymade: Visual Basic style forms for Python and Tkinter."""
|
|
2
|
+
|
|
3
|
+
from readymade.events import doevents, in_thread, pause, while_running
|
|
4
|
+
from readymade.dialogs import (color_dialog, folder_dialog, font_dialog, inputbox, msgbox, open_file_dialog,
|
|
5
|
+
save_file_dialog)
|
|
6
|
+
from readymade.form import Form, app_path
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"Form", "app_path", "color_dialog", "doevents", "folder_dialog", "font_dialog", "in_thread", "inputbox", "msgbox",
|
|
10
|
+
"open_file_dialog", "pause", "save_file_dialog", "while_running",
|
|
11
|
+
]
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""How controls look, in readymade's own terms: colors, fonts, alignment and mouse
|
|
2
|
+
cursors, and what they become in Tk.
|
|
3
|
+
|
|
4
|
+
Colors are "#rrggbb" strings, with "" for the usual color. A font is a string like
|
|
5
|
+
"Arial, 12, bold" (family, size in points, then any of bold, italic and underline),
|
|
6
|
+
or the same as a tuple, ("Arial", 12, "bold"); empty parts mean the usual font's.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import re
|
|
12
|
+
|
|
13
|
+
ALIGNMENTS = ("left", "center", "right")
|
|
14
|
+
VALIGNMENTS = ("top", "middle", "bottom")
|
|
15
|
+
FONT_STYLES = ("bold", "italic", "underline")
|
|
16
|
+
# Mouse cursors by readymade name, and Tk's names for them (which Tk maps to each system's own).
|
|
17
|
+
CURSORS = {
|
|
18
|
+
"default": "", # the control's usual cursor (a text box's is "text")
|
|
19
|
+
"arrow": "arrow",
|
|
20
|
+
"hand": "hand2",
|
|
21
|
+
"text": "xterm",
|
|
22
|
+
"cross": "crosshair",
|
|
23
|
+
"wait": "watch",
|
|
24
|
+
"move": "fleur",
|
|
25
|
+
"help": "question_arrow",
|
|
26
|
+
"resize_ew": "sb_h_double_arrow",
|
|
27
|
+
"resize_ns": "sb_v_double_arrow",
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
_HEX = re.compile(r"#[0-9a-fA-F]{6}")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def check_color(value):
|
|
34
|
+
"""value as a "#rrggbb" color (or "" for the usual); ValueError if it isn't one."""
|
|
35
|
+
value = "" if value is None else str(value).strip()
|
|
36
|
+
if value and not _HEX.fullmatch(value):
|
|
37
|
+
raise ValueError(f"{value!r} isn't a color; use \"#rrggbb\", like \"#ff0000\" for red (or \"\" for the usual color)")
|
|
38
|
+
return value.lower()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def lighter(color, amount=0.2):
|
|
42
|
+
"""A "#rrggbb" color mixed with some white (for a hovered button)."""
|
|
43
|
+
channels = [int(color[i:i + 2], 16) for i in (1, 3, 5)]
|
|
44
|
+
return "#" + "".join(f"{round(c + (255 - c) * amount):02x}" for c in channels)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def parse_font(value):
|
|
48
|
+
"""(family, size, styles) from a font string or tuple; "" and 0 mean the usual."""
|
|
49
|
+
if value is None or value == "":
|
|
50
|
+
return "", 0, ()
|
|
51
|
+
parts = [p.strip() for p in value.split(",")] if isinstance(value, str) else [str(p).strip() for p in value]
|
|
52
|
+
family = parts[0] if parts else ""
|
|
53
|
+
size = 0
|
|
54
|
+
if len(parts) > 1 and parts[1]:
|
|
55
|
+
try:
|
|
56
|
+
size = int(float(parts[1]))
|
|
57
|
+
except ValueError:
|
|
58
|
+
raise ValueError(f"{value!r}: the font's size, after the first comma, must be a number") from None
|
|
59
|
+
if size < 0:
|
|
60
|
+
raise ValueError(f"{value!r}: the font's size can't be negative")
|
|
61
|
+
styles = []
|
|
62
|
+
for word in " ".join(parts[2:]).lower().split():
|
|
63
|
+
if word not in FONT_STYLES:
|
|
64
|
+
raise ValueError(f"{value!r}: {word!r} isn't a font style; use bold, italic or underline")
|
|
65
|
+
if word not in styles:
|
|
66
|
+
styles.append(word)
|
|
67
|
+
return family, size, tuple(s for s in FONT_STYLES if s in styles)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def format_font(value):
|
|
71
|
+
"""The tidy string form of a font: "Arial, 12, bold"; "" for the usual font."""
|
|
72
|
+
family, size, styles = parse_font(value)
|
|
73
|
+
parts = [family, str(size) if size else "", " ".join(styles)]
|
|
74
|
+
while parts and not parts[-1]:
|
|
75
|
+
parts.pop()
|
|
76
|
+
return ", ".join(parts)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def tk_font(value, usual):
|
|
80
|
+
"""A Tk font description for a font, filling empty parts from usual (a dict like
|
|
81
|
+
tkinter.font.Font.actual() gives)."""
|
|
82
|
+
family, size, styles = parse_font(value)
|
|
83
|
+
return (family or usual["family"], size or usual["size"], *styles)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def anchor(alignment, valignment="middle"):
|
|
87
|
+
"""Tk's compass-point anchor for an alignment pair: ("left", "top") is "nw"."""
|
|
88
|
+
across = {"left": "w", "center": "", "right": "e"}[alignment]
|
|
89
|
+
down = {"top": "n", "middle": "", "bottom": "s"}[valignment]
|
|
90
|
+
return (down + across) or "center"
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def check_choice(name, value, choices):
|
|
94
|
+
if value not in choices:
|
|
95
|
+
raise ValueError(f"{name} must be one of: {', '.join(choices)}")
|
|
96
|
+
return value
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""Small source edits on a form's .py module: adding, finding and renaming handlers.
|
|
2
|
+
|
|
3
|
+
These work on lines and regular expressions rather than the ast, so they keep
|
|
4
|
+
working while the code has a syntax error in it, which is most of the time
|
|
5
|
+
during editing.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
|
|
13
|
+
INDENT = " "
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CodeGenError(Exception):
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True)
|
|
21
|
+
class Edit:
|
|
22
|
+
"""Replace lines [start, end) (0-based) with text."""
|
|
23
|
+
|
|
24
|
+
start: int
|
|
25
|
+
end: int
|
|
26
|
+
text: str
|
|
27
|
+
method_line: int # 1-based line of the new def once the edit is applied
|
|
28
|
+
|
|
29
|
+
def apply(self, source):
|
|
30
|
+
lines = source.splitlines(keepends=True)
|
|
31
|
+
return "".join(lines[: self.start]) + self.text + "".join(lines[self.end :])
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def new_module(class_name):
|
|
35
|
+
return (
|
|
36
|
+
"from readymade import Form\n"
|
|
37
|
+
"\n"
|
|
38
|
+
"\n"
|
|
39
|
+
f"class {class_name}(Form):\n"
|
|
40
|
+
f"{INDENT}pass\n"
|
|
41
|
+
"\n"
|
|
42
|
+
"\n"
|
|
43
|
+
'if __name__ == "__main__":\n'
|
|
44
|
+
f"{INDENT}{class_name}.run()\n"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def defined_methods(source):
|
|
49
|
+
return set(re.findall(r"^[ \t]+(?:async[ \t]+)?def[ \t]+(\w+)[ \t]*\(", source, re.M))
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def find_method(source, name):
|
|
53
|
+
"""The 1-based line of `def name(`, or None."""
|
|
54
|
+
m = re.search(rf"^[ \t]+(?:async[ \t]+)?def[ \t]+{re.escape(name)}[ \t]*\(", source, re.M)
|
|
55
|
+
return source.count("\n", 0, m.start()) + 1 if m else None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def add_method(source, class_name, name, params=()):
|
|
59
|
+
"""An Edit that adds an empty method at the end of the class."""
|
|
60
|
+
lines = source.splitlines(keepends=True)
|
|
61
|
+
class_re = re.compile(rf"class[ \t]+{re.escape(class_name)}\b")
|
|
62
|
+
start = next((i for i, line in enumerate(lines) if class_re.match(line)), None)
|
|
63
|
+
if start is None:
|
|
64
|
+
raise CodeGenError(f"Couldn't find 'class {class_name}' in the code.")
|
|
65
|
+
|
|
66
|
+
# The class body runs until the first non-blank, non-comment line at column 0.
|
|
67
|
+
last = start
|
|
68
|
+
for i in range(start + 1, len(lines)):
|
|
69
|
+
line = lines[i]
|
|
70
|
+
if not line.strip() or line.lstrip().startswith("#"):
|
|
71
|
+
continue
|
|
72
|
+
if line[0] not in " \t":
|
|
73
|
+
break
|
|
74
|
+
last = i
|
|
75
|
+
|
|
76
|
+
body = [i for i in range(start + 1, last + 1) if lines[i].strip() and not lines[i].lstrip().startswith("#")]
|
|
77
|
+
indent = INDENT
|
|
78
|
+
if body:
|
|
79
|
+
first = lines[body[0]]
|
|
80
|
+
indent = first[: len(first) - len(first.lstrip())]
|
|
81
|
+
signature = ", ".join(("self", *params))
|
|
82
|
+
method = f"{indent}def {name}({signature}):\n{indent}{indent}pass\n"
|
|
83
|
+
|
|
84
|
+
# A new class holds only a placeholder `pass`; replace it.
|
|
85
|
+
if len(body) == 1 and lines[body[0]].strip() == "pass":
|
|
86
|
+
return Edit(body[0], body[0] + 1, method, method_line=body[0] + 1)
|
|
87
|
+
|
|
88
|
+
insert_at = last + 1
|
|
89
|
+
prefix = "" if lines[last].endswith("\n") else "\n"
|
|
90
|
+
return Edit(insert_at, insert_at, prefix + "\n" + method, method_line=insert_at + 2)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def rename_control(source, old, new, events):
|
|
94
|
+
"""Rename a control's handlers and its `self.old` references."""
|
|
95
|
+
if events:
|
|
96
|
+
names = "|".join(re.escape(e) for e in events)
|
|
97
|
+
pattern = rf"^([ \t]+(?:async[ \t]+)?def[ \t]+){re.escape(old)}_({names})(?=[ \t]*\()"
|
|
98
|
+
source = re.sub(pattern, rf"\g<1>{new}_\g<2>", source, flags=re.M)
|
|
99
|
+
return re.sub(rf"\bself\.{re.escape(old)}\b", f"self.{new}", source)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def rename_class(source, old, new):
|
|
103
|
+
"""Rename the form class everywhere it appears as a whole word."""
|
|
104
|
+
return re.sub(rf"\b{re.escape(old)}\b", new, source)
|