Skip to content

Commit

Permalink
Merge pull request #2643 from freakboy3742/close-handling
Browse files Browse the repository at this point in the history
Ensure that programmatically closing the main window triggers on_exit handling
  • Loading branch information
mhsmith authored Jun 18, 2024
2 parents b4c0c91 + fc7723f commit f90ab92
Show file tree
Hide file tree
Showing 37 changed files with 465 additions and 386 deletions.
5 changes: 0 additions & 5 deletions android/src/toga_android/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,6 @@

from .libs import events
from .screens import Screen as ScreenImpl
from .window import Window


class MainWindow(Window):
_is_main_window = True


class TogaApp(dynamic_proxy(IPythonApp)):
Expand Down
7 changes: 4 additions & 3 deletions android/src/toga_android/factory.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from toga import NotImplementedWarning

from . import dialogs
from .app import App, MainWindow
from .app import App
from .command import Command
from .fonts import Font
from .hardware.camera import Camera
Expand Down Expand Up @@ -31,7 +31,7 @@
from .widgets.textinput import TextInput
from .widgets.timeinput import TimeInput
from .widgets.webview import WebView
from .window import Window
from .window import MainWindow, Window


def not_implemented(feature):
Expand All @@ -41,7 +41,6 @@ def not_implemented(feature):
__all__ = [
"App",
"Command",
"MainWindow",
"not_implemented",
# Resources
"dialogs",
Expand Down Expand Up @@ -78,6 +77,8 @@ def not_implemented(feature):
"TimeInput",
# "Tree",
"WebView",
# Windows
"MainWindow",
"Window",
]

Expand Down
8 changes: 7 additions & 1 deletion android/src/toga_android/window.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ def set_title(self, title):
# Window lifecycle
######################################################################

def close(self):
def close(self): # pragma: no cover
# An Android app only ever contains a main window, and that window *can't* be
# closed, so the platform-specific close handling is never triggered.
pass

def create_toolbar(self):
Expand Down Expand Up @@ -157,3 +159,7 @@ def get_image_data(self):
stream = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.PNG, 0, stream)
return bytes(stream.toByteArray())


class MainWindow(Window):
_is_main_window = True
1 change: 1 addition & 0 deletions changes/2643.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Programmatically invoking ``close()`` on the main window will now trigger ``on_exit`` handling. Previously ``on_exit`` handling would only be triggered if the close was initiated by a user action.
1 change: 1 addition & 0 deletions changes/2643.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
A MainWindow can now have an ``on_close`` handler. If a request is made to close the main window, the ``on_close`` handler will be evaluated; app exit handling will only be processed if the close handler allows the close to continue.
28 changes: 8 additions & 20 deletions cocoa/src/toga_cocoa/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@
from pathlib import Path
from urllib.parse import unquote, urlparse

from rubicon.objc import (
SEL,
NSMutableArray,
NSMutableDictionary,
NSObject,
objc_method,
objc_property,
)
from rubicon.objc.eventloop import CocoaLifecycle, EventLoopPolicy

import toga
Expand All @@ -15,7 +23,6 @@
from .keys import cocoa_key
from .libs import (
NSURL,
SEL,
NSAboutPanelOptionApplicationIcon,
NSAboutPanelOptionApplicationName,
NSAboutPanelOptionApplicationVersion,
Expand All @@ -28,29 +35,12 @@
NSDocumentController,
NSMenu,
NSMenuItem,
NSMutableArray,
NSMutableDictionary,
NSNumber,
NSObject,
NSOpenPanel,
NSScreen,
NSString,
objc_method,
objc_property,
)
from .screens import Screen as ScreenImpl
from .window import Window


class MainWindow(Window):
def cocoa_windowShouldClose(self):
# Main Window close is a proxy for "Exit app".
# Defer all handling to the app's on_exit handler.
# As a result of calling that method, the app will either
# exit, or the user will cancel the exit; in which case
# the main window shouldn't close, either.
self.interface.app.on_exit()
return False


class AppDelegate(NSObject):
Expand Down Expand Up @@ -118,8 +108,6 @@ def validateMenuItem_(self, sender) -> bool:


class App:
_MAIN_WINDOW_CLASS = MainWindow

def __init__(self, interface):
self.interface = interface
self.interface._impl = self
Expand Down
7 changes: 4 additions & 3 deletions cocoa/src/toga_cocoa/factory.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from toga import NotImplementedWarning

from . import dialogs
from .app import App, DocumentApp, MainWindow
from .app import App, DocumentApp
from .command import Command
from .documents import Document
from .fonts import Font
Expand Down Expand Up @@ -35,7 +35,7 @@
from .widgets.textinput import TextInput
from .widgets.tree import Tree
from .widgets.webview import WebView
from .window import Window
from .window import MainWindow, Window


def not_implemented(feature):
Expand All @@ -46,7 +46,6 @@ def not_implemented(feature):
"not_implemented",
"App",
"DocumentApp",
"MainWindow",
"Command",
"Document",
# Resources
Expand Down Expand Up @@ -82,6 +81,8 @@ def not_implemented(feature):
"TextInput",
"Tree",
"WebView",
# Windows,
"MainWindow",
"Window",
]

Expand Down
2 changes: 2 additions & 0 deletions cocoa/src/toga_cocoa/hardware/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,8 @@ def change_camera(self, widget=None, **kwargs):
self._update_flash_mode()

def close_window(self, widget, **kwargs):
# If the user actually takes a photo, the window will be programmatically closed.
# This handler is only triggered if the user manually closes the window.
# Stop the camera session
self.camera_session.stopRunning()

Expand Down
37 changes: 18 additions & 19 deletions cocoa/src/toga_cocoa/window.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
from rubicon.objc import CGSize
from rubicon.objc import (
SEL,
CGSize,
NSMakeRect,
NSPoint,
NSSize,
objc_method,
objc_property,
)

from toga.command import Command, Separator
from toga.types import Position, Size
from toga.window import _initial_position
from toga_cocoa.container import Container
from toga_cocoa.libs import (
SEL,
NSBackingStoreBuffered,
NSImage,
NSMakeRect,
NSMutableArray,
NSPoint,
NSScreen,
NSSize,
NSToolbar,
NSToolbarItem,
NSWindow,
NSWindowStyleMask,
core_graphics,
objc_method,
objc_property,
)

from .screens import Screen as ScreenImpl
Expand All @@ -35,7 +37,11 @@ class TogaWindow(NSWindow):

@objc_method
def windowShouldClose_(self, notification) -> bool:
return self.impl.cocoa_windowShouldClose()
# The on_close handler has a cleanup method that will enforce
# the close if the on_close handler requests it; this initial
# "should close" request always returns False.
self.interface.on_close()
return False

@objc_method
def windowDidResize_(self, notification) -> None:
Expand Down Expand Up @@ -181,17 +187,6 @@ def __del__(self):
self.purge_toolbar()
self.native.release()

######################################################################
# Native event handlers
######################################################################

def cocoa_windowShouldClose(self):
# The on_close handler has a cleanup method that will enforce
# the close if the on_close handler requests it; this initial
# "should close" request can always return False.
self.interface.on_close()
return False

######################################################################
# Window properties
######################################################################
Expand Down Expand Up @@ -367,3 +362,7 @@ def get_image_data(self):
)
ns_image = NSImage.alloc().initWithCGImage(cg_image, size=target_size)
return ns_image


class MainWindow(Window):
pass
6 changes: 3 additions & 3 deletions cocoa/tests_backend/hardware/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ def _removeInput(input):
def cleanup(self):
# Ensure there are no open camrea preview windows at the end of a test.
for window in self.app.camera._impl.preview_windows:
window.cocoa_windowShouldClose()
window.interface.close()

def known_cameras(self):
return {
Expand Down Expand Up @@ -203,8 +203,8 @@ async def press_shutter_button(self, photo):
async def cancel_photo(self, photo):
window = self.app.camera._impl.preview_windows[0]

# Close the camera window.
window._impl.cocoa_windowShouldClose()
# Trigger a user close of the camera window
window.on_close()
await self.redraw("Photo cancelled")

# The window has been closed and the session ended
Expand Down
9 changes: 5 additions & 4 deletions core/src/toga/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import warnings
from pathlib import Path

from .app import App, DocumentApp, DocumentMainWindow, MainWindow
from .app import App, DocumentApp

# Resources
from .colors import hsl, hsla, rgb, rgba
Expand Down Expand Up @@ -44,7 +44,7 @@
from .widgets.timeinput import TimeInput, TimePicker
from .widgets.tree import Tree
from .widgets.webview import WebView
from .window import Window
from .window import DocumentMainWindow, MainWindow, Window


class NotImplementedWarning(RuntimeWarning):
Expand All @@ -62,8 +62,6 @@ def warn(cls, platform: str, feature: str) -> None:
# Applications
"App",
"DocumentApp",
"MainWindow",
"DocumentMainWindow",
# Commands
"Command",
"Group",
Expand Down Expand Up @@ -112,6 +110,9 @@ def warn(cls, platform: str, feature: str) -> None:
"Tree",
"WebView",
"Widget",
# Windows
"DocumentMainWindow",
"MainWindow",
"Window",
# Deprecated widget names
"DatePicker",
Expand Down
Loading

0 comments on commit f90ab92

Please sign in to comment.