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

Added tabbingIdentifier to Window in Cocoa #2311

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 8 additions & 0 deletions android/tests_backend/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,11 @@ def rotate(self):
self.native.findViewById(
R.id.content
).getViewTreeObserver().dispatchOnGlobalLayout()

@property
def tabbing_enabled(self):
xfail("Tabbed windows not implemented for this backend.")

@tabbing_enabled.setter
def tabbing_enabled(self, value):
xfail("Tabbed windows not implemented for this backend.")
4 changes: 4 additions & 0 deletions android/tests_backend/window.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,7 @@ def assert_toolbar_item(self, index, label, tooltip, has_icon, enabled):

def press_toolbar_button(self, index):
self.native.onOptionsItemSelected(self._toolbar_items()[index])

@property
def tabs(self):
pytest.xfail("Tabbed windows not implemented for this backend.")
1 change: 1 addition & 0 deletions changes/2311.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
On macOS, windows now only tab with others of the same class.
6 changes: 6 additions & 0 deletions cocoa/src/toga_cocoa/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,12 @@ def _create_app_commands(self):
shortcut=toga.Key.MOD_1 + "m",
group=toga.Group.WINDOW,
),
toga.Command(
NativeHandler(SEL("mergeAllWindows:")),
"Merge All Windows",
group=toga.Group.WINDOW,
section=10,
),
# ---- Help menu ----------------------------------
toga.Command(
self._menu_visit_homepage,
Expand Down
5 changes: 4 additions & 1 deletion cocoa/src/toga_cocoa/window.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,9 @@ def __init__(self, interface, title, position, size):
self.native.interface = self.interface
self.native.impl = self

# This causes windows to only tab together with others of the same class.
self.native.tabbingIdentifier = str(self.interface.__class__)

# Cocoa releases windows when they are closed; this causes havoc with
# Toga's widget cleanup because the ObjC runtime thinks there's no
# references to the object left. Add a reference that can be released
Expand All @@ -163,7 +166,7 @@ def __init__(self, interface, title, position, size):
self.container = Container(on_refresh=self.content_refreshed)
self.native.contentView = self.container.native

# Ensure that the container renders it's background in the same color as the window.
# Ensure that the container renders its background in the same color as the window.
self.native.wantsLayer = True
self.container.native.backgroundColor = self.native.backgroundColor

Expand Down
10 changes: 9 additions & 1 deletion cocoa/tests_backend/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def __init__(self, app):
super().__init__()
self.app = app
# Prevents erroneous test fails from secondary windows opening as tabs
NSWindow.allowsAutomaticWindowTabbing = False
self.tabbing_enabled = False
assert isinstance(self.app._impl.native, NSApplication)

@property
Expand Down Expand Up @@ -175,3 +175,11 @@ def keystroke(self, combination):
keyCode=key_code,
)
return toga_key(event)

@property
def tabbing_enabled(self):
return NSWindow.allowsAutomaticWindowTabbing

@tabbing_enabled.setter
def tabbing_enabled(self, value):
NSWindow.allowsAutomaticWindowTabbing = value
4 changes: 4 additions & 0 deletions cocoa/tests_backend/window.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,3 +261,7 @@ def press_toolbar_button(self, index):
restype=None,
argtypes=[objc_id],
)

@property
def tabs(self):
return self.native.tabbedWindows
8 changes: 8 additions & 0 deletions gtk/tests_backend/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,3 +154,11 @@ def keystroke(self, combination):
event.state = state

return toga_key(event)

@property
def tabbing_enabled(self):
pytest.xfail("Tabbed windows not implemented for this backend.")

@tabbing_enabled.setter
def tabbing_enabled(self, value):
pytest.xfail("Tabbed windows not implemented for this backend.")
6 changes: 6 additions & 0 deletions gtk/tests_backend/window.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
from pathlib import Path
from unittest.mock import Mock

from pytest import xfail

from toga_gtk.libs import Gdk, Gtk

from .probe import BaseProbe
Expand Down Expand Up @@ -251,3 +253,7 @@ def assert_toolbar_item(self, index, label, tooltip, has_icon, enabled):
def press_toolbar_button(self, index):
item = self.impl.native_toolbar.get_nth_item(index)
item.emit("clicked")

@property
def tabs(self):
xfail("Tabbed windows not implemented for this backend.")
8 changes: 8 additions & 0 deletions iOS/tests_backend/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,11 @@ def terminate(self):
def rotate(self):
self.native = self.app._impl.native
self.native.delegate.application(self.native, didChangeStatusBarOrientation=0)

@property
def tabbing_enabled(self):
pytest.xfail("Tabbed windows not implemented for this backend.")

@tabbing_enabled.setter
def tabbing_enabled(self, value):
pytest.xfail("Tabbed windows not implemented for this backend.")
4 changes: 4 additions & 0 deletions iOS/tests_backend/window.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,7 @@ async def close_select_folder_dialog(self, dialog, result, multiple_select):

def has_toolbar(self):
pytest.skip("Toolbars not implemented on iOS")

@property
def tabs(self):
pytest.xfail("Tabbed windows not implemented for this backend.")
61 changes: 60 additions & 1 deletion testbed/tests/test_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

def window_probe(app, window):
module = import_module("tests_backend.window")
return getattr(module, "WindowProbe")(app, window)
return module.WindowProbe(app, window)


@pytest.fixture
Expand Down Expand Up @@ -216,6 +216,65 @@ async def test_secondary_window_with_args(app, second_window, second_window_prob

assert second_window not in app.windows

async def test_window_tabbing(app, app_probe, main_window, main_window_probe):
"""Windows tab only with others of the same class (only implemented on macOS)"""

class WindowSubclass(toga.Window):
pass

base_windows = [toga.Window() for _ in range(2)]
subclass_windows = [WindowSubclass() for _ in range(2)]
for window in [*base_windows, *subclass_windows]:
window.show()

try:
base_probe = window_probe(app, base_windows[0])
subclass_probe = window_probe(app, subclass_windows[0])

app_probe.tabbing_enabled = True

# Double check that nothing's tabbed initially.
assert not main_window_probe.tabs
assert not base_probe.tabs
assert not subclass_probe.tabs

# Merge All Windows command operates based on which window is active.
app.current_window = main_window
await main_window_probe.wait_for_window("Switched to MainWindow")
app_probe._activate_menu_item(["Window", "Merge All Windows"])
await main_window_probe.wait_for_window(
"Merge All Windows called on MainWindow"
)
# There's only one MainWindow, so nothing should have changed.
assert not main_window_probe.tabs
assert not base_probe.tabs
assert not subclass_probe.tabs

app.current_window = base_probe.window
await main_window_probe.wait_for_window("Switched to base Window")
app_probe._activate_menu_item(["Window", "Merge All Windows"])
await main_window_probe.wait_for_window(
"Merge All Windows called on base Window"
)
assert not main_window_probe.tabs
assert len(base_probe.tabs) == 2
assert not subclass_probe.tabs

app.current_window = subclass_probe.window
await main_window_probe.wait_for_window("Switched to subclassed Window")
app_probe._activate_menu_item(["Window", "Merge All Windows"])
await main_window_probe.wait_for_window(
"Merge All Windows called on Window subclass"
)
assert not main_window_probe.tabs
assert len(base_probe.tabs) == 2
assert len(subclass_probe.tabs) == 2

finally:
app_probe.tabbing_enabled = False
for window in [*base_windows, *subclass_windows]:
window.close()

async def test_secondary_window_cleanup(app_probe):
"""Memory for windows is cleaned up when windows are deleted."""
# Create and show a window with content. We can't use the second_window fixture
Expand Down
8 changes: 8 additions & 0 deletions winforms/tests_backend/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,3 +154,11 @@ def activate_menu_minimize(self):

def keystroke(self, combination):
return winforms_to_toga_key(toga_to_winforms_key(combination))

@property
def tabbing_enabled(self):
pytest.xfail("Tabbed windows not implemented for this backend.")

@tabbing_enabled.setter
def tabbing_enabled(self, value):
pytest.xfail("Tabbed windows not implemented for this backend.")
5 changes: 5 additions & 0 deletions winforms/tests_backend/window.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import asyncio
from unittest.mock import Mock

from pytest import xfail
from System import EventArgs
from System.Windows.Forms import (
Form,
Expand Down Expand Up @@ -148,3 +149,7 @@ def assert_toolbar_item(self, index, label, tooltip, has_icon, enabled):

def press_toolbar_button(self, index):
self._native_toolbar_item(index).OnClick(EventArgs.Empty)

@property
def tabs(self):
xfail("Tabbed windows not implemented for this backend.")
Loading