-
-
Notifications
You must be signed in to change notification settings - Fork 673
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fixed App.current_window
on macOS, for when a dialog is in focus
#2926
base: main
Are you sure you want to change the base?
Conversation
A bit of an off-topic, but is this comment still valid: toga/testbed/tests/app/test_desktop.py Lines 355 to 358 in 75e1fd9
I thought I had corrected this behaviour and that GTK also returned None, when all windows were hidden: Lines 237 to 239 in 75e1fd9
EDIT: It seems that GTK does return |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Broadly makes sense; a couple of questions about the testing strategy inline.
testbed/tests/app/test_desktop.py
Outdated
if app_probe.supports_current_window_assignment: | ||
assert app.current_window == window3 | ||
await app_probe.redraw("select 'OK'") | ||
# Cancel the task to avoid dangling | ||
window3_dialog_task.cancel() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need to do the "in the presence of a dialog" test multiple times. We should still be testing the "normal" case of "just assigning a new window"; we only need to do one additional check of "...and if there's a current dialog, that doesn't get in the way".
We should, however, confirm that when a dialog is dismissed, but the current window hasn't been modified that the current window is still what it should be.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, I have made the "in presence of dialog test" to be tested only on window1.
testbed/tests/app/test_desktop.py
Outdated
if app_probe.supports_current_window_assignment: | ||
assert app.current_window == window2 | ||
await app_probe.redraw("select 'OK'") | ||
# Cancel the task to avoid dangling | ||
window2_dialog_task.cancel() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm unclear why this cancel is needed. What sort of "dangling" can occur? Why is this style of cancel needed here when it isn't needed on any other dialog test?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was getting error for the app still having dangling tasks when exiting the test. On deeper inspection, the error was probably telling me that the dialog is not dismissed, even after calling await app_probe.redraw("select 'OK'")
93d0932
to
1e04814
Compare
It seems like there are bugs in setting up dialogs. When the Dialog setup uses
|
info_dialog = toga.InfoDialog("Info", "Some info") | |
window1_probe.setup_info_dialog_result(info_dialog) |
Test Run 1:c667c3e
For dialog setup | For showing the dialog | Works? |
---|---|---|
window1_probe.setup_info_dialog_result(info_dialog) |
await window1.dialog(info_dialog) |
❌ |
toga/testbed/tests/app/test_desktop.py
Lines 387 to 409 in c667c3e
# Test current window when dialog is in focus | |
window1_probe = window_probe(app, window1) | |
app.current_window = window1 | |
await main_window_probe.wait_for_window("Window 1 is current") | |
assert app.current_window == window1 | |
info_dialog = toga.InfoDialog("Info", "Some info") | |
window1_probe.setup_info_dialog_result(info_dialog) | |
await window1_probe.redraw("Display window1 modal info dialog") | |
await window1.dialog(info_dialog) | |
if app_probe.supports_current_window_assignment: | |
assert app.current_window == window1 | |
# On the native backend, the dialog should be in focus, instead of the window | |
if toga.platform.current_platform == "macOS": | |
assert "_NSAlertPanel" in str( | |
app._impl.native.keyWindow | |
), "The dialog is not in focus" | |
await window1_probe.redraw("select 'OK'") |
Test Result:
FAILED tests/app/test_desktop.py::test_current_window - AssertionError: The dialog is not in focus
assert '_NSAlertPanel' in '<TogaWindow: 0x7fe515f4de50>'
+ where '<TogaWindow: 0x7fe515f4de50>' = str(<ObjCInstance: TogaWindow at 0x10eb7ab30: <TogaWindow: 0x7fe515f4de50>>)
+ where <ObjCInstance: TogaWindow at 0x10eb7ab30: <TogaWindow: 0x7fe515f4de50>> = <ObjCInstance: NSApplication at 0x10df9d640: <NSApplication: 0x7fe513748e70>>.keyWindow
+ where <ObjCInstance: NSApplication at 0x10df9d640: <NSApplication: 0x7fe513748e70>> = <toga_cocoa.app.App object at 0x10e2daa90>.native
+ where <toga_cocoa.app.App object at 0x10e2daa90> = <testbed.app.Testbed object at 0x10db7e770>._impl
The test fails as the dialog gets dismissed as soon as it is displayed. Hence, it fails the dialog focus test.
When the Dialog setup uses app_probe
:
The dialog remains open and the tests do not continue
Test Run 2:75f6e41
For dialog setup | For showing the dialog | Works? |
---|---|---|
app_probe.setup_info_dialog_result(info_dialog) |
await window1.dialog(info_dialog) |
❌ |
toga/testbed/tests/app/test_desktop.py
Lines 387 to 409 in 75f6e41
# Test current window when dialog is in focus | |
window1_probe = window_probe(app, window1) | |
app.current_window = window1 | |
await main_window_probe.wait_for_window("Window 1 is current") | |
assert app.current_window == window1 | |
info_dialog = toga.InfoDialog("Info", "Some info") | |
app_probe.setup_info_dialog_result(info_dialog) | |
await window1_probe.redraw("Display window1 modal info dialog") | |
await window1.dialog(info_dialog) | |
if app_probe.supports_current_window_assignment: | |
assert app.current_window == window1 | |
# On the native backend, the dialog should be in focus, instead of the window | |
if toga.platform.current_platform == "macOS": | |
assert "_NSAlertPanel" in str( | |
app._impl.native.keyWindow | |
), "The dialog is not in focus" | |
await window1_probe.redraw("select 'OK'") |
Test Result:
The dialog is shown, but the tests do not continue and the app gets into a deadlock state(i.e, the app doesn't respond while the dialog is showing). Also, the dialog cannot be dismissed.
When the Dialog setup uses app_probe
and create_task()
:
The dialog is shown and the tests continue.
Test Run 3:1e04814
For dialog setup | For showing the dialog | Works? |
---|---|---|
app_probe.setup_info_dialog_result(info_dialog) |
app.loop.create_task(window1.dialog(info_dialog)) |
✅ |
toga/testbed/tests/app/test_desktop.py
Lines 390 to 402 in 1e04814
app.current_window = window1 | |
await main_window_probe.wait_for_window("Window 1 is current") | |
dialog_task = app.loop.create_task(window1.dialog(info_dialog)) | |
await main_window_probe.wait_for_window("Displayed window1 modal info dialog") | |
if app_probe.supports_current_window_assignment: | |
assert app.current_window == window1 | |
if toga.platform.current_platform == "macOS": | |
assert "_NSAlertPanel" in str( | |
app._impl.native.keyWindow | |
), "The dialog is not in focus" | |
await app_probe.redraw("select 'OK'") | |
# Cancel the task to avoid dangling | |
dialog_task.cancel() |
Test Result:
The test passes and the dialog is in focus on the native backend.
Problem with this approach:
But, the dialog still cannot be dismissed and remains hanging. So, it will complain about the app having dangling task when exiting the test, hence we have to use dialog_task.cancel()
testbed/tests/app/test_desktop.py
Outdated
# But, the backend should be reporting that the current window is the dialog | ||
if toga.platform.current_platform == "macOS": | ||
assert "_NSAlertPanel" in str( | ||
app._impl.native.keyWindow | ||
), "The dialog is not in focus" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is only for confirming that the test is actually testing the situation of "in presence of dialog" and not testing the situation where the dialog immediately exits.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but as I've indicated on other PRs - the testbed shouldn't have if <platform>
logic. Capture what the abstract "thing" is that is being tested here, and put a method on the app/window probe. If the test is a no-op on most platforms, that's fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fix looks good; the issues are entirely related to testing.
cocoa/src/toga_cocoa/app.py
Outdated
@@ -34,6 +35,8 @@ | |||
) | |||
from .screens import Screen as ScreenImpl | |||
|
|||
NSPanel = ObjCClass("NSPanel") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We try to keep these definitions in the libs
folder
testbed/tests/app/test_desktop.py
Outdated
# But, the backend should be reporting that the current window is the dialog | ||
if toga.platform.current_platform == "macOS": | ||
assert "_NSAlertPanel" in str( | ||
app._impl.native.keyWindow | ||
), "The dialog is not in focus" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but as I've indicated on other PRs - the testbed shouldn't have if <platform>
logic. Capture what the abstract "thing" is that is being tested here, and put a method on the app/window probe. If the test is a no-op on most platforms, that's fine.
testbed/tests/app/test_desktop.py
Outdated
app.current_window = window1 | ||
await main_window_probe.wait_for_window("Window 1 is current") | ||
dialog_task = app.loop.create_task(window1.dialog(info_dialog)) | ||
await main_window_probe.wait_for_window("Displayed window1 modal info dialog") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not convinced this is a good test.
Firstly, the assignment to window1 is redundant - there's nothing special about window 1, so that's a step that isn't required.
But, more importantly, this entirely depends on the exact sequencing of events. It's using create_task()
to schedule a coroutine on the event loop - and then hoping that all the checks about window assignment are performed before that task completes.
In practice, that seems highly likely... but it's not guaranteed. And it also leads to the "dangling task" issue, as there's no guarantee that the task will have ended by the time the test is cleaning up.
A better approach would be to modify the setup_info_dialog_result
probe to accept an optional method that contains "pre-close" test logic, and invoke that method as required before closing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Apologies for the delayed response; I got caught up with a few things. Anyway, Thank you for suggesting the test strategy.
037f773
to
a373f26
Compare
On macOS, when a dialog is dismissed, the window from which the dialog was initiated becomes the current_window, regardless of whether the current_window is modified. But, this is not the behavior on other backends(gtk, winforms). Should I modify this behavior?(If yes, on which backend?), or should I just document this as a native platform dependent behavior? |
I'd argue that's a platform quirk. We shouldn't be overriding default platform behavior unless there's a very good reason to do so. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The mechanics of the test you've added all look good. However, you've gone down the "make the interpreter be quiet" path, rather than finishing the job. If we're adding a feature, we add the feature. We don't partially add the feature in a way that is a bug in waiting for future users who assume that the API behaves the same everywhere.
I will implement the probe features across all the required backends. But it would take some time. I know you have told me to work on one PR at one time, but in the meantime, can you please review #2473, when you are free? This PR isn't related to #2473, so #2473 can be reviewed independently of this PR. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So - this seems to have introduced a bunch of new behavior into the GTK and Winforms backend, and I'm not entirely clear why. This isn't just "adding tests to verify that the dialog is visible", which was the state of the PR last time I reviewed it - it seems to have dramatically changed what "current window" means on those platforms.
Was the old implementation incorrect? Or is this new behavior that then also required tests? It seems like the latter, because the new behavior seems to be aimed at catching cases where current window being re-assigned while a dialog is visible.
Even then, it's not clear to me why this is required. If a dialog is window-modal, that doesn't prevent you from giving another window focus; if a dialog is app-modal, you can't change the current window, because only the dialog can have focus.
So - what's the motivation here?
android/src/toga_android/dialogs.py
Outdated
@@ -22,7 +22,7 @@ def show(self, host_window, future): | |||
|
|||
if self.native: | |||
# Show the dialog. Don't differentiate between app and window modal dialogs. | |||
self.native.show() | |||
self.native_alert_dialog = self.native.show() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the purpose of this assignment? Firstly, self.native
should be a reference to the native object; secondly Dialog.show()
returns void, so this value should always be None`.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually self.native
is a AlertDialog.Builder
not a AlertDialog
toga/android/src/toga_android/dialogs.py
Lines 32 to 46 in 3376c06
class TextDialog(BaseDialog): | |
def __init__( | |
self, | |
title, | |
message, | |
positive_text, | |
negative_text=None, | |
icon=None, | |
): | |
super().__init__() | |
self.native = AlertDialog.Builder(toga.App.app.current_window._impl.app.native) | |
self.native.setCancelable(False) | |
self.native.setTitle(title) | |
self.native.setMessage(message) |
self.native = AlertDialog.Builder(toga.App.app.current_window._impl.app.native)
returns an AlertDialog.Builder
: https://developer.android.com/reference/android/app/AlertDialog.Builder#Builder(android.content.Context)
So, doing AlertDialog.Builder.show()
: https://developer.android.com/reference/android/app/AlertDialog.Builder#show()
self.native_alert_dialog = self.native.show()
returns an AlertDialog
And AlertDialog
has the isShowing()
method: https://developer.android.com/reference/android/app/Dialog#isShowing()
which is used in the probe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah - that makes more sense. I misread the API for Builder
as being a factory for AlertDialog
objects, rather than a constructor for AlertDialog.Builder
instances.
To that end, I'd argue that self.native
is misnamed in the Android backend. On every other platform, self.native
is the dialog itself. In the Android code, it's the factory that produces the dialog instance. I'd say that what is currently self.native
should be self.native_builder
, and what you've called self.native_alert_dialog
should be self.native
.
gtk/src/toga_gtk/app.py
Outdated
# will have its dialog visible, as all dialogs are shown | ||
# in modal style. | ||
return window._impl | ||
return current_window |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not clear what's going on with this change - is this something that was revealed as a result of the extra testing? It seems to be trying to protecting against a change of current window while a dialog is visible... but it's not clear to me how that would be possible.
winforms/src/toga_winforms/app.py
Outdated
@@ -251,6 +251,20 @@ def get_current_window(self): | |||
for window in self.interface.windows: | |||
if WinForms.Form.ActiveForm == window._impl.native: | |||
return window._impl | |||
|
|||
# If the focus is on a dialog, then return its host window |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As with the GTK backend, it's not clear why this change is required.
I agree I should have discussed the behavior of
File "/Users/proneon267/toga/testbed/tests/app/test_desktop.py", line 390, in test_current_window
assert app.current_window == window2
File "/Users/proneon267/toga/core/src/toga/app.py", line 834, in current_window
return window.interface
^^^^^^^^^^^^^^^^
File "/Users/proneon267/venv/lib/python3.12/site-packages/rubicon/objc/api.py", line 1097, in __getattr__
raise AttributeError(
AttributeError: rubicon.objc.api.ObjCInstance _NSAlertPanel has no attribute interface
Currently, I have changed the behavior of But, what is actually the desired behavior and what should |
I can't say I have any strong opinions, other than "it definitely should not raise an error" and "the implementation shouldn't be exceptionally complex". Returning the window that the dialog is modal to would make sense - but that then leaves the question of what happens to app-modal windows. But if the price for that is an exceptionally complex implementation that involves iterating over every window... that API becomes a lot less desirable. |
I think the macOS implementation is simple, but only the winforms and gtk implementations are complex. So, for winforms and gtk, we could be to have a list of currently showing dialogs with each dialog tracking whether it has the active focus. Then in However, the winforms implementation might still be a bit complex, as native dialogs like Since, I have implemented one form of complex implementation, it's only fair that I implement the other :) , before we take the final decision. |
Currently on all desktop platforms, the behavior for:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few comments inline about some minor details; but more importantly, I'm not convinced the "current window is None when an app-modal dialog is shown" interpretation makes sense.
Toga doesn't consider a dialog to be a window; and a window can be current when a dialog is shown. I'd argue it doesn't matter what type of dialog has been displayed - whatever window was the current window should still be the current window. That might be nothing (if there are no open windows); or it might be an actual window.
And, as far as I can tell, this is also the easiest implementation - this is what falls out by default from both GTK and Winforms (removing the need for all the extra dialog tracking on those platforms; and on macOS, adding the "pre-display current window" as something that is tracked on app-modal dialogs seems like it should be reasonably straightforward (and something that can be stored on the dialog)
The other detail worth noting is that if we've identified there's a clear difference of behavior between app-modal and window-modal dialogs... that is something that needs to be tested.
android/src/toga_android/dialogs.py
Outdated
@@ -22,7 +22,7 @@ def show(self, host_window, future): | |||
|
|||
if self.native: | |||
# Show the dialog. Don't differentiate between app and window modal dialogs. | |||
self.native.show() | |||
self.native_alert_dialog = self.native.show() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah - that makes more sense. I misread the API for Builder
as being a factory for AlertDialog
objects, rather than a constructor for AlertDialog.Builder
instances.
To that end, I'd argue that self.native
is misnamed in the Android backend. On every other platform, self.native
is the dialog itself. In the Android code, it's the factory that produces the dialog instance. I'd say that what is currently self.native
should be self.native_builder
, and what you've called self.native_alert_dialog
should be self.native
.
gtk/src/toga_gtk/dialogs.py
Outdated
@@ -10,8 +12,10 @@ def show(self, host_window, future): | |||
# If this is a modal dialog, set the window as transient to the host window. | |||
if host_window: | |||
self.native.set_transient_for(host_window._impl.native) | |||
host_window._impl.native.present() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like a fairly substantial change - and the documentation for the method says "This function should not be used". Why is it needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Previously, I was using it to make the API behavior consistent.
But, I will remove it since the new API behavior doesn't require this.
On a related note, the documentation for Gtk.Window.present()
is a bit ambiguous. The complete explanation is at: https://discourse.gnome.org/t/how-to-raise-a-window-in-gtk3-i-e-unminimize-it-bring-it-on-top-of-other-windows-and-focus-it/17100
We use Gtk.Window.present()
for App._impl.set_current_window()
gtk/tests_backend/app.py
Outdated
if IS_WAYLAND: | ||
assert dialog._impl.native.is_visible(), "The dialog is not in focus" | ||
else: | ||
# Gtk.Dialog.get_transient_for() doesn't work on wayland and will crash. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you sure it's crashing? I've tested this locally and it's not crashing, but failing the assertion (because get_transient_for != get_active_window), causing the test to lock up because the dialog never closes.
On a related note - this highlights that the pre_close_test_method()
invocation needs to be wrapped in a try: finally:
block - if the assertion fails, we still want to close the dialog, or else the test is left in a locked-up state.
gtk/tests_backend/dialogs.py
Outdated
await self.redraw("Running pre-close test", delay=0.05) | ||
|
||
if pre_close_test_method: | ||
pre_close_test_method() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As noted earlier - this block (and its analogs on other platforms) needs to be re-worked so that the close logic is contained in a finally
block, ensuring that the dialog always closes if the assertion fails.
I think the iOS test failure is unrelated to this PR, since I haven't made any unusual changes to the iOS backend. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A couple of issues flagged inline - mostly associated with the request from the previous review to keep the stateful data on the dialog.
cocoa/src/toga_cocoa/app.py
Outdated
@@ -109,6 +109,7 @@ def __init__(self, interface): | |||
|
|||
# Create the lookup table for commands and menu items | |||
self._menu_items = {} | |||
self._active_window_before_dialog = None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Quoting from my previous review:
... on macOS, adding the "pre-display current window" as something that is tracked on app-modal dialogs seems like it should be reasonably straightforward (and something that can be stored on the dialog)
(emphasis added)
Storing the state on the app means there's a circular dependency on setting the variable as soon as you have a one window with a dialog.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I didn't understand what you meant. Did you meant to store the state on the native dialog object(i.e, on the NSAlert object itself?) or on the dialog._impl
? When we get the key window in presence of a dialog with NSApplication.keyWindow
, it returns the associated private NSWindow
instance(i.e., _NSAlertPanel
). So, how do we get to the _impl
or to the NSAlert
object from a _NSAlertPanel
?
Also, I now realize that storing the previous active window may not be the correct thing to do here. For example:
- There are 2 windows open(
Window A
,Window B
) andWindow A
has the current focus. Now, anapp modal dialog
opens and it storesWindow A
as the previously active window. - Since, the dialog is app modal, the user can manually change focus to other window, so the user manually changes focus to
window B
and then again changes focus on to theapp modal dialog
. - Now, if the app were to query the current window then it would return the cached
Window A
as the previously active window, when in realityWindow C
was the previously active window. Hence, giving incorrect results.
This is not a problem on winforms, as only 1 dialog can be shown at any given time. So, focus can't be changed to other windows of the app(Until the dialog is not dismissed). But on winforms, there is no way of knowing whether the focus is on a dialog or on something outside the app (Form.ActiveForm
returns None
in both cases). And even if the focus cannot be changed to other windows of the app(while the dialog is not dismissed), but the focus can be changed to other apps. So, returning the cached previously active window would be incorrect.
The more I look at the problem and the solution that this PR implements, the more I realize that I am going against the native behavior of the platform. I am trying to bend something that is not meant to be bent in the way I want. And the result is fragile and complicated.
Maybe the correct solution is to just prevent the error on macOS, leave the other platforms with their native behavior and just document the native platform quirks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I didn't understand what you meant. Did you meant to store the state on the native dialog object(i.e, on the NSAlert object itself?) or on the
dialog._impl
?
Either - just not the app.
Also, I now realize that storing the previous active window may not be the correct thing to do here. For example:
Yes - this is exactly why I was flagging the state variable on the app as a problem - dialogs are separate from the app, and there's complex interactions at play here.
The more I look at the problem and the solution that this PR implements, the more I realize that I am going against the native behavior of the platform. I am trying to bend something that is not meant to be bent in the way I want. And the result is fragile and complicated.
Maybe the correct solution is to just prevent the error on macOS, leave the other platforms with their native behavior and just document the native platform quirks.
... which is what I was suggesting before we went down this rabbit hole :-) The most recent iterations here are adding a lot of complex logic to handle a specific edge case that doesn't really have a clear and obvious answer. If a dialog is showing, the "current window" is a vague concept.
Not crashing is the primary requirement. Beyond that, I don't see any problem with returning as much of an answer as any given platform will allow, which means that there may not be a current window on some platforms when a dialog is showing - and then documenting that as a platform quirk.
testbed/tests/app/test_desktop.py
Outdated
window_modal_info_dialog = toga.InfoDialog("Window Modal Dialog Info", "Some info") | ||
main_window_probe.setup_info_dialog_result( | ||
window_modal_info_dialog, | ||
pre_close_test_method=partial( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given that the dialog is available at the point the test method is being invoked (and would be of general utility to any dialog test method), and the previous_active_window is a constant - why not make dialog
a required first argument of the test method. and make the usage of previous_active_window
a closure? (i.e., the test method referencing a variable that is outside the context of the method).
winforms/src/toga_winforms/app.py
Outdated
if WinForms.Form.ActiveForm == window._impl.native: | ||
return window._impl | ||
return None | ||
if self._active_window_before_dialog: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this even needed? Do dialogs appear as ActiveForm?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
when a dialog is in focus, Form.ActiveForm
returns None
and since there is no other way to know whether any dialog is in focus instead of a window, hence this is required.
App.current_window
on desktop backends, for when a dialog is in focusApp.current_window
on macOS, for when a dialog is in focus
I have removed any behavioral changes from platforms other than macOS. But, I have kept the "pre-close test" mechanisms, and modified the test in order to make sure |
# Accessing current_window in presence of dialog shouldn't raise any exceptions. | ||
_ = app.current_window | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should I wrap this around a try...except...pytest.fail() block? I have found that just invoking the property without the fix, makes the test to fail automatically. So, I am not explicitly failing the test. Is this correct?
Also, I have been wanting to cut down on the number of commits in my PR. Would there be any problem if I squash all of my previous commits in this PR into a single commit? |
App._impl.current_window
returned theNSPanel
object instead of theNSWindow
object, causing an AttributeError.Error:
This PR corrects this behaviour and now
App.current_window
returns the host window of the dialog. This bug was identified while working on #2473PR Checklist: