Skip to content

0.3.0 — Screen capture and dialog results

Choose a tag to compare

@github-actions github-actions released this 11 Aug 16:05
· 262 commits to main since this release

Added

  • Every widget can now save a picture of itself. capture(path) writes the area a widget occupies on screen to an image file and returns the path it wrote — call it on the app for the whole window, or on any single widget for just that part of it. The file extension picks the format, so .png, .jpg, and .pdf all work, and missing folders in the path are created for you. Pair it with ask_save_file() to let the user choose where the picture goes. The window is raised before the picture is taken, and an always-on-top setting the window already had is left exactly as it was found. Capturing a hidden or detached widget raises an error rather than silently saving whatever happened to be behind it. (#427)

Changed

  • A dialog button's command can now refuse its own press by returning False. The dialog then records no result and stays open, where previously the return value was ignored and the press completed regardless — recording the button's result and closing the window even when the command had decided to do nothing. This is how a button rejects the input it was given: validate in the command, return False to keep the dialog open, return anything else to let it close. If you have a button command that returns False for some other reason, it will now suppress that button rather than being ignored; return None to keep the previous behavior. FormDialog already treated False this way for the commands you give it, so this brings the underlying Dialog in line with it — and Form's own button row, the other place these specifications are used, honors it too, where it previously recorded a result for a press its command had just declined. A form stays on screen after a press, so a refused press there also clears any result an earlier press recorded: form.result is the most recent press that completed, never an older one made against data you have since edited. (#437)

Removed

  • DialogButton.closes is gone. It was meant to say whether a button dismisses the dialog, but it could not be honored: FormDialog set the same flag internally on every non-cancel button — to stop the window closing before the form had been validated — and could not then tell its own value apart from one you had set. So the same declaration did three different things depending on the button's role and whether it had a command, and both uses of it inside bootstack were really reaching for something else. What they wanted was a way to say "not this press" rather than "not this button", which is what returning False from the command now does — per press, and without the button having to close the window itself. If you passed closes=False to keep a dialog open after a press, return False from that button's command instead. A footer button that never dismisses the dialog is not really a footer button; put it in the dialog body, where it needs no flag at all. (#438)

Fixed

  • Deleting a record from a DataTable no longer requires the record to be valid. The dialog validated the form for every button except Cancel, so a Delete button — which never reads the form — was refused whenever the record failed validation, and pressing it simply did nothing. This hit exactly the records most likely to need deleting: validation happens in the editors, which only exist while the dialog is open, so it has no say over what is already in the table. A record with a required field left blank, or one missing that field entirely, is accepted by DataTable(rows=...) without complaint and then cannot be deleted. The same applied to any custom action button you added to a FormDialog, provided it carried a result= of its own — which is what marks a button as an action rather than a submission. Validation now runs only for the buttons that submit the form: the standard 'ok', 'submit' and 'save', plus any button with no result=, whose result is the entered data. (#437)

  • Cancelling a FormDialog after a refused button press no longer performs that press. A press the dialog declined still recorded its button's result, and Cancel could not clear it — so backing out of the dialog handed the caller the refused button's result as though it had been pressed successfully. On a DataTable this meant that pressing Delete on an invalid record (which did nothing, per the fix above), and then cancelling because the form could not be satisfied, deleted the record at the moment you asked for nothing. A refused press now leaves nothing behind. (#437)

  • The keypad Enter key now submits a dialog you are typing in. In ask_string, ask_integer and any other dialog that puts the cursor straight into a field, only the main Enter key finished the dialog — the keypad one did nothing, so a value typed on the number pad had to be committed with the other hand or with the mouse. Both keys now submit, through the same command and the same refusal path as a click. Once you have clicked or tabbed to a button, that button answers both keys itself. (#437)

  • Enter now presses the button you tabbed to, and nothing else. A dialog bound Enter to its default button for every key press in the window, including one already delivered to a button — buttons answer Enter themselves — so one press ran two commands: the focused button's, then the default button's on top of it. It only surfaced on a dialog that stayed open after a press, since otherwise the closing window took the second command with it, which meant a footer button declared closes=False. That declaration is gone in this release, and returning False from a command replaces it, so the same press would have started refusing and running the default button instead. Enter on a focused button now presses that button alone; with the cursor in a field it still presses the default button. (#437)

  • A FormDialog no longer modifies the DialogButton you pass it. It rewrote command directly on your object, so a button specification reused across two dialogs came back altered — and the second dialog then wrapped the first one's wrapper, leaving the press running against a dialog that was already gone. It works on its own copy now. (#438)

  • FormDialog.result now gives you the values you put in, not the text shown on screen. A select built from [('One', 1), ('Two', 2)] returned 'One' where a plain Select and the same field in a Form both returned 1 — so the three disagreed, and the dialog was the odd one out. It affected every editor whose displayed text differs from its underlying value, not only select: the result was read back after the dialog had already closed, at which point the only thing left to read was the on-screen text, and that arrives as a string whatever the value's real type was. A date field, for instance, handed back its formatted text rather than a date. The entries are now taken when you press the button, while the form is still on screen, so what you get back is what was entered — same values, same types, matching Form and Select. Cancelling still returns None, and re-using a dialog no longer reports the previous run's entries — including for a button declared with the 'cancel' role but an 'ok' result, the one combination where the dialog took no entries yet still tried to hand some back. (#428)

What's Changed

  • fix(dialogs): FormDialog returns entered values, and a button press means one thing by @israel-dryer in #442
  • feat(capture): save a widget, window, or app view as an image file by @israel-dryer in #443

Full Changelog: v0.2.3...v0.3.0