Skip to content

Commit

Permalink
Merge pull request #2866 from proneon267/macos_fix_document_mode
Browse files Browse the repository at this point in the history
Fix document mode on macOS
  • Loading branch information
freakboy3742 authored Sep 23, 2024
2 parents 1988328 + bc683c2 commit efea361
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 28 deletions.
1 change: 1 addition & 0 deletions changes/2860.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
On macOS, apps that specify both `document_types` and a `main_window` no longer display the document selection dialog on startup.
5 changes: 4 additions & 1 deletion cocoa/src/toga_cocoa/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,10 @@ def applicationOpenUntitledFile_(self, sender) -> bool:

@objc_method
def applicationShouldOpenUntitledFile_(self, sender) -> bool:
return bool(self.interface.documents.types)
if self.interface._main_window == toga.App._UNDEFINED:
return False
else:
return bool(self.interface.documents.types)

@objc_method
def application_openFiles_(self, app, filenames) -> None:
Expand Down
50 changes: 26 additions & 24 deletions core/src/toga/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -493,14 +493,14 @@ def _install_task_factory_wrapper(self):

def factory(loop, coro, context=None):
if platform_task_factory is not None:
if sys.version_info < (3, 11):
if sys.version_info < (3, 11): # pragma: no-cover-if-gte-py311
task = platform_task_factory(loop, coro)
else:
else: # pragma: no-cover-if-lt-py311
task = platform_task_factory(loop, coro, context=context)
else:
if sys.version_info < (3, 11):
if sys.version_info < (3, 11): # pragma: no-cover-if-gte-py311
task = asyncio.Task(coro, loop=loop)
else:
else: # pragma: no-cover-if-lt-py311
task = asyncio.Task(coro, loop=loop, context=context)

self._running_tasks.add(task)
Expand Down Expand Up @@ -608,28 +608,30 @@ def _create_standard_commands(self):

def _create_initial_windows(self):
"""Internal utility method for creating initial windows based on command line
arguments. This method is used when the platform doesn't provide its own
command-line handling interface.
arguments.
If document types are defined, try to open every argument on the command line as
a document (unless the backend manages the command line arguments).
"""
# If the backend handles the command line, don't do any command line processing.
if self._impl.HANDLES_COMMAND_LINE:
return
doc_count = len(self.windows)
if self.documents.types:
for filename in sys.argv[1:]:
if self._open_initial_document(filename):
doc_count += 1
If document types are defined, and the backend doesn't have native command line
handling, try to open every argument on the command line as a document (unless
the backend manages the command line arguments).
# Safety check: Do we have at least one document?
if self.main_window is None and doc_count == 0:
try:
# Pass in the first document type as the default
default_doc_type = self.documents.types[0]
self.documents.new(default_doc_type)
except IndexError:
If, after processing all command line arguments, the app doesn't have at least
one window, the app's default initial document handling will be triggered.
"""
# Process command line arguments if the backend doesn't handle them
if not self._impl.HANDLES_COMMAND_LINE:
if self.documents.types:
for filename in sys.argv[1:]:
self._open_initial_document(filename)

# Ensure there is at least one window
if self.main_window is None and len(self.windows) == 0:
if self.documents.types:
if self._impl.CLOSE_ON_LAST_WINDOW:
# Pass in the first document type as the default
self.documents.new(self.documents.types[0])
else:
self.loop.run_until_complete(self.documents.request_open())
else:
# No document types defined.
raise RuntimeError(
"App didn't create any windows, or register any document types."
Expand Down
45 changes: 42 additions & 3 deletions core/tests/app/test_document_app.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import sys
from unittest.mock import Mock
from unittest.mock import AsyncMock, Mock

import pytest

import toga
from toga.documents import DocumentSet
from toga_dummy.app import App as DummyApp
from toga_dummy.command import Command as DummyCommand
from toga_dummy.utils import (
Expand Down Expand Up @@ -151,7 +152,7 @@ def test_create_no_cmdline(monkeypatch):
assert toga.Command.SAVE_ALL in app.commands


def test_create_no_cmdline_default_handling(monkeypatch):
def test_create_no_cmdline_default_handling_close_on_last_window(monkeypatch):
"""If the backend uses the app's command line handling, no error is raised for an
empty command line."""
monkeypatch.setattr(sys, "argv", ["app-exe"])
Expand All @@ -170,10 +171,48 @@ def test_create_no_cmdline_default_handling(monkeypatch):

assert app.documents.types == [ExampleDocument]

# No documents or windows exist
# A default document has been created
assert len(app.documents) == 1
assert len(app.windows) == 1


def test_create_no_cmdline_default_handling_no_close_on_last_window(monkeypatch):
"""If the backend handles the app's command line and the app doesn't exit when
the last window closes, no error is raised for an empty command line and the
app shows a document selection dialog on startup."""

monkeypatch.setattr(sys, "argv", ["app-exe"])

# Monkeypatch the property that makes the backend handle command line arguments
# and not close the app when the last window closes.
monkeypatch.setattr(DummyApp, "HANDLES_COMMAND_LINE", True)
monkeypatch.setattr(DummyApp, "CLOSE_ON_LAST_WINDOW", False)

# Mock request_open() because OpenFileDialog's response can't be set before the
# app creation. Since request_open() is called during app initialization, we can't
# set the dialog response in time, leading to an unexpected dialog response error.
mock_request_open = AsyncMock()
monkeypatch.setattr(DocumentSet, "request_open", mock_request_open)

# Create the app instance
app = ExampleDocumentApp(
"Test App",
"org.beeware.document-app",
document_types=[ExampleDocument],
)

assert app._impl.interface == app
assert_action_performed(app, "create App")

assert app.documents.types == [ExampleDocument]

# No documents have been created
assert len(app.documents) == 0
assert len(app.windows) == 0

# ...but request_open was called
mock_request_open.assert_called_once()


def test_create_with_cmdline(monkeypatch, example_file):
"""If a document is specified at the command line, it is opened."""
Expand Down
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ exclude_lines = [
no-cover-if-missing-setuptools_scm = "not is_installed('setuptools_scm')"
no-cover-if-missing-PIL = "not is_installed('PIL')"
no-cover-if-PIL-installed = "is_installed('PIL')"
no-cover-if-lt-py311 = "sys_version_info < (3, 11) and os_environ.get('COVERAGE_EXCLUDE_PYTHON_VERSION') != 'disable'"
no-cover-if-gte-py311 = "sys_version_info > (3, 11) and os_environ.get('COVERAGE_EXCLUDE_PYTHON_VERSION') != 'disable'"
no-cover-if-lt-py310 = "sys_version_info < (3, 10) and os_environ.get('COVERAGE_EXCLUDE_PYTHON_VERSION') != 'disable'"
no-cover-if-gte-py310 = "sys_version_info > (3, 10) and os_environ.get('COVERAGE_EXCLUDE_PYTHON_VERSION') != 'disable'"
no-cover-if-lt-py39 = "sys_version_info < (3, 9) and os_environ.get('COVERAGE_EXCLUDE_PYTHON_VERSION') != 'disable'"
Expand Down

0 comments on commit efea361

Please sign in to comment.