Skip to content
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

[widget Audit] toga.Window #2058

Merged
merged 47 commits into from
Oct 17, 2023
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
d3fd16d
Update docstrings and documentation for Window and MainWindow.
freakboy3742 Jul 31, 2023
8c09ef1
Add changenote.
freakboy3742 Jul 31, 2023
d2eec81
Migrated core tests to pytest.
freakboy3742 Aug 1, 2023
7ee6369
Complete coverage of core dialogs.
freakboy3742 Aug 1, 2023
3e6bf86
Remove GTK window tests.
freakboy3742 Aug 1, 2023
cec300d
Correct a spelling issue, and document an API rename.
freakboy3742 Aug 1, 2023
a13683b
Exclude some clases from interface tests.
freakboy3742 Aug 1, 2023
32097ca
Cocoa GUI tests for Window (excluding dialogs and toolbars.
freakboy3742 Aug 1, 2023
212fb89
Add menu items and keyboard shortcuts for close/minimize on macOS.
freakboy3742 Aug 1, 2023
f3bffde
Add GUI test of full screen mode.
freakboy3742 Aug 2, 2023
86e91ae
Converge on US spelling for resizable, closable
freakboy3742 Aug 2, 2023
9f719f6
Cocoa dialogs 100% coverage.
freakboy3742 Aug 2, 2023
e3613f2
100% coverage for Window on iOS.
freakboy3742 Aug 2, 2023
7cd3fde
Minor coverage tweaks.
freakboy3742 Aug 2, 2023
c867585
GTK Window and Dialogs to 100%.
freakboy3742 Aug 3, 2023
066a384
Tweak visibility handling to ensure complete coverage in testbed cond…
freakboy3742 Aug 3, 2023
3bd0b05
Use blackbox as a testing WM.
freakboy3742 Aug 4, 2023
054658d
Rework file dialog setup handling to avoid CI failure.
freakboy3742 Aug 4, 2023
e18c5b1
More test updates for CI's benefit.
freakboy3742 Aug 4, 2023
59cfb50
Restructure menu creation for cocoa.
freakboy3742 Aug 5, 2023
4fffdbe
Rework default title handling so MainWindow defaults to the formal na…
freakboy3742 Aug 5, 2023
ce4130d
Add a test of closing window explicitly.
freakboy3742 Aug 11, 2023
a04ad63
Merge branch 'main' into audit-window
freakboy3742 Aug 16, 2023
cc2df8b
Insert a pause on app exit to make sure Briefcase gets all the app logs.
freakboy3742 Aug 16, 2023
85f537f
Merge branch 'main' into audit-window
freakboy3742 Aug 23, 2023
34d40c5
Merge branch 'main' into audit-window
freakboy3742 Aug 24, 2023
f370c31
Merge branch 'main' into audit-window
freakboy3742 Sep 10, 2023
d63b462
Merge branch 'main' into audit-window
freakboy3742 Oct 5, 2023
5f126da
Slow down test suite startup until the main window is visible.
freakboy3742 Oct 6, 2023
a44043e
Window documentation cleanups
mhsmith Oct 14, 2023
c9d0e1e
Move common Android test methods to BaseProbe
mhsmith Oct 15, 2023
916ab25
Android Window at 100% coverage (excluding toolbar)
mhsmith Oct 15, 2023
9266dea
Merge remote-tracking branch 'origin/main' into audit-window
mhsmith Oct 15, 2023
5c55dea
Make core tests expect Window.app to be assigned in Window constructor
mhsmith Oct 15, 2023
01e71f0
Update other Android BaseProbe subclasses
mhsmith Oct 15, 2023
1a27d71
WinForms Window passing all tests except dialogs
mhsmith Oct 15, 2023
dda9a5f
WinForms info, question, confirm and error dialogs passing
mhsmith Oct 16, 2023
a293a89
WinForms stack trace dialog passing
mhsmith Oct 16, 2023
117f5d2
Fix expected OptionContainer sizes
mhsmith Oct 16, 2023
030c775
WinForms save file and select folder dialogs passing
mhsmith Oct 16, 2023
ff59e95
WinForms open file dialog passing
mhsmith Oct 17, 2023
4cbb49f
Fix WinForms select folder dialog title
mhsmith Oct 17, 2023
432e7c9
Empty multiple selection is not actually supported by any backend
mhsmith Oct 17, 2023
8d66d76
WinForms Window at 100% coverage (excluding toolbar)
mhsmith Oct 17, 2023
480580b
file_types should not include leading dots
mhsmith Oct 17, 2023
2ff3835
Remove unreachable code
mhsmith Oct 17, 2023
270d009
Update support table; run readthedocs build on Python 3.11
mhsmith Oct 17, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -207,10 +207,15 @@ jobs:
- backend: linux
runs-on: ubuntu-22.04
# The package list should be the same as in tutorial-0.rst, and the BeeWare
# tutorial, plus flwm to provide a window manager
# tutorial, plus blackbox to provide a window manager. We need a window
# manager that is reasonably lightweight, honors full screen mode, and
# treats the window position as the top-left corner of the *window*, not the
# top-left corner of the window *content*. The default GNOME window managers of
# most distros meet these requirementt, but they're heavyweight; flwm doesn't
# work either. Blackbox is the lightest WM we've found that works.
pre-command: |
sudo apt update -y
sudo apt install -y flwm pkg-config python3-dev libgirepository1.0-dev libcairo2-dev gir1.2-webkit2-4.0
sudo apt install -y blackbox pkg-config python3-dev libgirepository1.0-dev libcairo2-dev gir1.2-webkit2-4.0

# Start Virtual X server
echo "Start X server..."
Expand All @@ -219,7 +224,7 @@ jobs:

# Start Window manager
echo "Start window manager..."
DISPLAY=:99 flwm &
DISPLAY=:99 blackbox &
sleep 1

briefcase-run-prefix: 'DISPLAY=:99'
Expand Down
1 change: 1 addition & 0 deletions changes/2058.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Window and MainWindow now have 100% test coverage, and complete API documentation.
1 change: 1 addition & 0 deletions changes/2058.removal.1.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Windows no longer need to be explicitly added to the app's window list. When a window is shown, it will be automatically added to the windows for the currently running app.
1 change: 1 addition & 0 deletions changes/2058.removal.2.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The ``multiselect`` argument to Open File and Select Folder dialogs has been renamed ``multiple_select``, for consistency with other widgets that have multiple selection capability.
1 change: 1 addition & 0 deletions changes/2058.removal.3.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
``Window.resizeable`` and ``Window.closeable`` have been renamed ``Window.resizable`` and ``Window.closable``, to adhere to US spelling conventions.
72 changes: 59 additions & 13 deletions cocoa/src/toga_cocoa/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,12 +135,21 @@ def create(self):
self.appDelegate.native = self.native
self.native.setDelegate_(self.appDelegate)

formal_name = self.interface.formal_name
self._create_app_commands()

# Call user code to populate the main window
self.interface._startup()

# Create the lookup table of menu items,
# then force the creation of the menus.
self.create_menus()

def _create_app_commands(self):
formal_name = self.interface.formal_name
self.interface.commands.add(
# ---- App menu -----------------------------------
toga.Command(
lambda _: self.interface.about(),
self._menu_about,
"About " + formal_name,
group=toga.Group.APP,
),
Expand Down Expand Up @@ -176,12 +185,35 @@ def create(self):
),
# Quit should always be the last item, in a section on its own
toga.Command(
lambda _: self.interface.exit(),
self._menu_exit,
"Quit " + formal_name,
shortcut=toga.Key.MOD_1 + "q",
group=toga.Group.APP,
section=sys.maxsize,
),
# ---- File menu ----------------------------------
# This is a bit of an oddity. Safari has 2 distinct "Close Window" and
# "Close All Windows" menu items (partially to differentiate from "Close
# Tab"). Most other Apple HIG apps have a "Close" item that becomes
# "Close All" when you press Option (MOD_2). That behavior isn't something
# we're currently set up to implement, so we live with a separate menu item
# for now.
toga.Command(
self._menu_close_window,
"Close Window",
shortcut=toga.Key.MOD_1 + "W",
group=toga.Group.FILE,
order=1,
section=50,
),
toga.Command(
self._menu_close_all_windows,
"Close All Windows",
shortcut=toga.Key.MOD_2 + toga.Key.MOD_1 + "W",
group=toga.Group.FILE,
order=2,
section=50,
),
samschott marked this conversation as resolved.
Show resolved Hide resolved
# ---- Edit menu ----------------------------------
toga.Command(
NativeHandler(SEL("undo:")),
Expand Down Expand Up @@ -244,26 +276,39 @@ def create(self):
section=10,
order=60,
),
# ---- Window menu ----------------------------------
toga.Command(
self._menu_minimize,
"Minimize",
shortcut=toga.Key.MOD_1 + "m",
group=toga.Group.WINDOW,
),
samschott marked this conversation as resolved.
Show resolved Hide resolved
# ---- Help menu ----------------------------------
toga.Command(
lambda _: self.interface.visit_homepage(),
lambda _, **kwargs: self.interface.visit_homepage(),
"Visit homepage",
enabled=self.interface.home_page is not None,
group=toga.Group.HELP,
),
)
self._create_app_commands()

# Call user code to populate the main window
self.interface._startup()
def _menu_about(self, app, **kwargs):
self.interface.about()

# Create the lookup table of menu items,
# then force the creation of the menus.
self.create_menus()
def _menu_exit(self, app, **kwargs):
self.interface.exit()

def _create_app_commands(self):
# No extra commands
pass
def _menu_close_window(self, app, **kwargs):
if self.interface.current_window:
self.interface.current_window._impl.native.performClose(None)

def _menu_close_all_windows(self, app, **kwargs):
for window in self.interface.windows:
window._impl.native.performClose(None)

def _menu_minimize(self, app, **kwargs):
if self.interface.current_window:
self.interface.current_window._impl.native.miniaturize(None)

def create_menus(self):
# Recreate the menu
Expand Down Expand Up @@ -427,6 +472,7 @@ def select_file(self, **kwargs):

class DocumentApp(App):
def _create_app_commands(self):
super()._create_app_commands()
self.interface.commands.add(
toga.Command(
lambda _: self.select_file(),
Expand Down
3 changes: 1 addition & 2 deletions cocoa/src/toga_cocoa/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,7 @@ def content(self, widget):
widget.container = self

def refreshed(self):
if self.on_refresh:
self.on_refresh(self)
self.on_refresh(self)

@property
def width(self):
Expand Down
90 changes: 49 additions & 41 deletions cocoa/src/toga_cocoa/dialogs.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
NSAlertFirstButtonReturn,
NSAlertStyle,
NSBezelBorder,
NSFileHandlingPanelOKButton,
NSFont,
NSMakeRect,
NSModalResponseOK,
NSOpenPanel,
NSSavePanel,
NSScrollView,
Expand All @@ -20,7 +20,7 @@
class BaseDialog(ABC):
def __init__(self, interface):
self.interface = interface
self.interface.impl = self
self.interface._impl = self


class NSAlertDialog(BaseDialog):
Expand All @@ -46,21 +46,22 @@ def __init__(
self.build_dialog(**kwargs)

self.native.beginSheetModalForWindow(
interface.window._impl.native, completionHandler=completion_handler
interface.window._impl.native,
completionHandler=completion_handler,
)

def build_dialog(self):
pass

def completion_handler(self, return_value: int) -> None:
self.on_result(self, None)
self.on_result(None, None)
samschott marked this conversation as resolved.
Show resolved Hide resolved

self.interface.future.set_result(None)

def bool_completion_handler(self, return_value: int) -> None:
result = return_value == NSAlertFirstButtonReturn

self.on_result(self, result)
self.on_result(None, result)

self.interface.future.set_result(result)

Expand Down Expand Up @@ -169,55 +170,62 @@ def __init__(
filename,
initial_directory,
file_types,
multiselect,
multiple_select,
on_result=None,
):
super().__init__(interface=interface)
self.on_result = on_result

# Create the panel
self.create_panel(multiselect)
self.create_panel(multiple_select)

# Set all the
self.panel.title = title
# Set the title of the panel
self.native.title = title

if filename:
self.panel.nameFieldStringValue = filename
self.native.nameFieldStringValue = filename

if initial_directory:
self.panel.directoryURL = NSURL.URLWithString(
self.native.directoryURL = NSURL.URLWithString(
str(initial_directory.as_uri())
)

self.panel.allowedFileTypes = file_types
self.native.allowedFileTypes = file_types

if multiselect:
if multiple_select:
handler = self.multi_path_completion_handler
else:
handler = self.single_path_completion_handler

self.panel.beginSheetModalForWindow(
self.native.beginSheetModalForWindow(
interface.window._impl.native,
completionHandler=handler,
)

# Provided as a stub that can be mocked in test conditions
def selected_path(self):
return self.native.URL

# Provided as a stub that can be mocked in test conditions
def selected_paths(self):
return self.native.URLs

def single_path_completion_handler(self, return_value: int) -> None:
if return_value == NSFileHandlingPanelOKButton:
result = Path(self.panel.URL.path)
if return_value == NSModalResponseOK:
result = Path(str(self.selected_path().path))
else:
result = None

self.on_result(self, result)

self.on_result(None, result)
self.interface.future.set_result(result)

def multi_path_completion_handler(self, return_value: int) -> None:
if return_value == NSFileHandlingPanelOKButton:
result = [Path(url.path) for url in self.panel.URLs]
if return_value == NSModalResponseOK:
result = [Path(url.path) for url in self.selected_paths()]
else:
result = None

self.on_result(self, result)
self.on_result(None, result)

self.interface.future.set_result(result)

Expand All @@ -238,12 +246,12 @@ def __init__(
filename=filename,
initial_directory=initial_directory,
file_types=None, # File types aren't offered by Cocoa save panels.
multiselect=False,
multiple_select=False,
on_result=on_result,
)

def create_panel(self, multiselect):
self.panel = NSSavePanel.alloc().init()
def create_panel(self, multiple_select):
self.native = NSSavePanel.alloc().init()


class OpenFileDialog(FileDialog):
Expand All @@ -253,7 +261,7 @@ def __init__(
title,
initial_directory,
file_types,
multiselect,
multiple_select,
on_result=None,
):
super().__init__(
Expand All @@ -262,16 +270,16 @@ def __init__(
filename=None,
initial_directory=initial_directory,
file_types=file_types,
multiselect=multiselect,
multiple_select=multiple_select,
on_result=on_result,
)

def create_panel(self, multiselect):
self.panel = NSOpenPanel.alloc().init()
self.panel.allowsMultipleSelection = multiselect
self.panel.canChooseDirectories = False
self.panel.canCreateDirectories = False
self.panel.canChooseFiles = True
def create_panel(self, multiple_select):
self.native = NSOpenPanel.alloc().init()
self.native.allowsMultipleSelection = multiple_select
self.native.canChooseDirectories = False
self.native.canCreateDirectories = False
self.native.canChooseFiles = True


class SelectFolderDialog(FileDialog):
Expand All @@ -280,7 +288,7 @@ def __init__(
interface,
title,
initial_directory,
multiselect,
multiple_select,
on_result=None,
):
super().__init__(
Expand All @@ -289,14 +297,14 @@ def __init__(
filename=None,
initial_directory=initial_directory,
file_types=None,
multiselect=multiselect,
multiple_select=multiple_select,
on_result=on_result,
)

def create_panel(self, multiselect):
self.panel = NSOpenPanel.alloc().init()
self.panel.allowsMultipleSelection = multiselect
self.panel.canChooseDirectories = True
self.panel.canCreateDirectories = True
self.panel.canChooseFiles = False
self.panel.resolvesAliases = True
def create_panel(self, multiple_select):
self.native = NSOpenPanel.alloc().init()
self.native.allowsMultipleSelection = multiple_select
self.native.canChooseDirectories = True
self.native.canCreateDirectories = True
self.native.canChooseFiles = False
self.native.resolvesAliases = True
Loading
Loading