Skip to content

Commit

Permalink
Add backend implementations for background and session apps.
Browse files Browse the repository at this point in the history
  • Loading branch information
freakboy3742 committed Jun 15, 2024
1 parent 9f04bb1 commit b50b2c3
Show file tree
Hide file tree
Showing 7 changed files with 80 additions and 37 deletions.
17 changes: 13 additions & 4 deletions android/src/toga_android/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from java import dynamic_proxy
from org.beeware.android import IPythonApp, MainActivity

import toga
from toga.command import Command, Group, Separator
from toga.handlers import simple_handler

Expand Down Expand Up @@ -182,6 +183,9 @@ def onPrepareOptionsMenu(self, menu):


class App:
# Android apps exit when the last window is closed
CLOSE_ON_LAST_WINDOW = True

def __init__(self, interface):
self.interface = interface
self.interface._impl = self
Expand Down Expand Up @@ -245,10 +249,15 @@ def set_icon(self, icon):
pass # pragma: no cover

def set_main_window(self, window):
# The default layout of an Android app includes a titlebar; a simple App then
# hides that titlebar. We know what type of app we have when the main window is
# set.
self.interface.main_window._impl.configure_titlebar()
if window is None:
raise RuntimeError("Session-based apps are not supported on Android")
elif window == toga.App.BACKGROUND:
raise RuntimeError("Background apps are not supported on Android")
else:
# The default layout of an Android app includes a titlebar; a simple App
# then hides that titlebar. We know what type of app we have when the main
# window is set.
self.interface.main_window._impl.configure_titlebar()

######################################################################
# App resources
Expand Down
21 changes: 10 additions & 11 deletions cocoa/src/toga_cocoa/app.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import asyncio
import inspect
import os
import sys
from pathlib import Path
from urllib.parse import unquote, urlparse
Expand Down Expand Up @@ -28,6 +27,7 @@
NSAboutPanelOptionApplicationVersion,
NSAboutPanelOptionVersion,
NSApplication,
NSApplicationActivationPolicyAccessory,
NSApplicationActivationPolicyRegular,
NSBeep,
NSBundle,
Expand Down Expand Up @@ -108,6 +108,9 @@ def validateMenuItem_(self, sender) -> bool:


class App:
# macOS apps persist when there are no windows open
CLOSE_ON_LAST_WINDOW = False

def __init__(self, interface):
self.interface = interface
self.interface._impl = self
Expand All @@ -117,28 +120,21 @@ def __init__(self, interface):
asyncio.set_event_loop_policy(EventLoopPolicy())
self.loop = asyncio.new_event_loop()

# Stimulate the build of the app
self.create()

def create(self):
self.native = NSApplication.sharedApplication
self.native.setActivationPolicy(NSApplicationActivationPolicyRegular)

# The app icon been set *before* the app instance is created. However, we only
# need to set the icon on the app if it has been explicitly defined; the default
# icon is... the default. We can't test this branch in the testbed.
if self.interface.icon._impl.path:
self.set_icon(self.interface.icon) # pragma: no cover

self.resource_path = os.path.dirname(
os.path.dirname(NSBundle.mainBundle.bundlePath)
)
self.resource_path = Path(NSBundle.mainBundle.bundlePath).parent.parent

self.appDelegate = AppDelegate.alloc().init()
self.appDelegate.impl = self
self.appDelegate.interface = self.interface
self.appDelegate.native = self.native
self.native.setDelegate_(self.appDelegate)
self.native.setDelegate(self.appDelegate)

# Create the lookup table for menu items
self._menu_groups = {}
Expand Down Expand Up @@ -422,7 +418,10 @@ def set_icon(self, icon):
self.native.setApplicationIconImage(None)

def set_main_window(self, window):
pass
if window == toga.App.BACKGROUND:
self.native.setActivationPolicy(NSApplicationActivationPolicyAccessory)
else:
self.native.setActivationPolicy(NSApplicationActivationPolicyRegular)

######################################################################
# App resources
Expand Down
32 changes: 20 additions & 12 deletions gtk/src/toga_gtk/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,16 @@


class App:
# GTK apps exit when the last window is closed
CLOSE_ON_LAST_WINDOW = True

def __init__(self, interface):
self.interface = interface
self.interface._impl = self

gbulb.install(gtk=True)
self.loop = asyncio.new_event_loop()

self.create()

def create(self):
# Stimulate the build of the app
self.native = Gtk.Application(
application_id=self.interface.app_id,
Expand All @@ -36,6 +36,7 @@ def create(self):

# Connect the GTK signal that will cause app startup to occur
self.native.connect("startup", self.gtk_startup)
# Activate is a no-op, but GTK complains if you don't implement it
self.native.connect("activate", self.gtk_activate)

self.actions = None
Expand All @@ -46,14 +47,6 @@ def gtk_activate(self, data=None):
def gtk_startup(self, data=None):
self.interface._startup()

# Now that we have menus, make the app take responsibility for
# showing the menubar.
# This is required because of inconsistencies in how the Gnome
# shell operates on different windowing environments;
# see #872 for details.
settings = Gtk.Settings.get_default()
settings.set_property("gtk-shell-shows-menubar", False)

# Set any custom styles
css_provider = Gtk.CssProvider()
css_provider.load_from_data(TOGA_DEFAULT_STYLES)
Expand Down Expand Up @@ -178,6 +171,14 @@ def create_menus(self):
# Set the menu for the app.
self.native.set_menubar(menubar)

# Now that we have menus, make the app take responsibility for
# showing the menubar.
# This is required because of inconsistencies in how the Gnome
# shell operates on different windowing environments;
# see #872 for details.
settings = Gtk.Settings.get_default()
settings.set_property("gtk-shell-shows-menubar", False)

######################################################################
# App lifecycle
######################################################################
Expand All @@ -190,14 +191,21 @@ def main_loop(self):
# Modify signal handlers to make sure Ctrl-C is caught and handled.
signal.signal(signal.SIGINT, signal.SIG_DFL)

# Retain a reference to the app so that no-window apps can exist
self.native.hold()

self.loop.run_forever(application=self.native)

# Release the reference to the app
self.native.release()

def set_icon(self, icon):
for window in self.interface.windows:
window._impl.native.set_icon(icon._impl.native(72))

def set_main_window(self, window):
pass
if isinstance(window, toga.Window):
window._impl.native.set_role("MainWindow")

######################################################################
# App resources
Expand Down
9 changes: 8 additions & 1 deletion iOS/src/toga_iOS/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from rubicon.objc import objc_method
from rubicon.objc.eventloop import EventLoopPolicy, iOSLifecycle

import toga
from toga.command import Command
from toga.handlers import simple_handler
from toga_iOS.libs import UIResponder, UIScreen, av_foundation
Expand Down Expand Up @@ -51,6 +52,9 @@ def application_didChangeStatusBarOrientation_(


class App:
# iOS apps exit when the last window is closed
CLOSE_ON_LAST_WINDOW = True

def __init__(self, interface):
self.interface = interface
self.interface._impl = self
Expand Down Expand Up @@ -110,7 +114,10 @@ def set_icon(self, icon):
pass # pragma: no cover

def set_main_window(self, window):
pass
if window is None:
raise RuntimeError("Session-based apps are not supported on Android")
elif window == toga.App.BACKGROUND:
raise RuntimeError("Background apps are not supported on Android")

######################################################################
# App resources
Expand Down
11 changes: 10 additions & 1 deletion textual/src/toga_textual/app.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import asyncio

import toga
from textual.app import App as TextualApp

from .screens import Screen as ScreenImpl
Expand All @@ -16,6 +17,9 @@ def on_mount(self) -> None:


class App:
# Textual apps exit when the last window is closed
CLOSE_ON_LAST_WINDOW = True

def __init__(self, interface):
self.interface = interface
self.interface._impl = self
Expand Down Expand Up @@ -54,7 +58,12 @@ def set_icon(self, icon):
pass

def set_main_window(self, window):
self.native.push_screen(self.interface.main_window.id)
if window is None:
raise RuntimeError("Session-based apps are not supported on Textual")
elif window == toga.App.BACKGROUND:
raise RuntimeError("Background apps are not supported on Textual")
else:
self.native.push_screen(self.interface.main_window.id)

######################################################################
# App resources
Expand Down
11 changes: 9 additions & 2 deletions web/src/toga_web/app.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import toga
from toga.app import overridden
from toga.command import Command, Group
from toga.handlers import simple_handler
Expand All @@ -7,13 +8,16 @@


class App:
# Web apps exit when the last window is closed
CLOSE_ON_LAST_WINDOW = True

def __init__(self, interface):
self.interface = interface
self.interface._impl = self

def create(self):
# self.resource_path = os.path.dirname(os.path.dirname(NSBundle.mainBundle.bundlePath))
self.native = js.document.getElementById("app-placeholder")
# self.resource_path = ???

# Call user code to populate the main window
self.interface._startup()
Expand Down Expand Up @@ -70,7 +74,10 @@ def set_icon(self, icon):
pass

def set_main_window(self, window):
pass
if window is None:
raise RuntimeError("Session-based apps are not supported on Web")
elif window == toga.App.BACKGROUND:
raise RuntimeError("Background apps are not supported on Web")

######################################################################
# App resources
Expand Down
16 changes: 10 additions & 6 deletions winforms/src/toga_winforms/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from System.Net import SecurityProtocolType, ServicePointManager
from System.Windows.Threading import Dispatcher

from toga import Key
import toga
from toga.app import overridden
from toga.command import Command, Group
from toga.handlers import simple_handler
Expand Down Expand Up @@ -55,6 +55,9 @@ def print_stack_trace(stack_trace_line): # pragma: no cover


class App:
# Winforms apps exit when the last window is closed
CLOSE_ON_LAST_WINDOW = True

def __init__(self, interface):
self.interface = interface
self.interface._impl = self
Expand Down Expand Up @@ -126,8 +129,6 @@ def create(self):
# Call user code to populate the main window
self.interface._startup()

self.interface.main_window._impl.set_app(self)

######################################################################
# Commands and menus
######################################################################
Expand All @@ -154,7 +155,7 @@ def create_standard_app_commands(self):
Command(
self.interface.on_exit,
"Exit",
shortcut=Key.MOD_1 + "q",
shortcut=toga.Key.MOD_1 + "q",
group=Group.FILE,
section=sys.maxsize,
id=Command.EXIT,
Expand Down Expand Up @@ -233,7 +234,10 @@ def set_icon(self, icon):
window._impl.native.Icon = icon._impl.native

def set_main_window(self, window):
self.app_context.MainForm = window._impl.native
if isinstance(window, toga.Window):
self.app_context.MainForm = window._impl.native
else:
self.app_context.MainForm = None

######################################################################
# App resources
Expand Down Expand Up @@ -319,7 +323,7 @@ def create_app_commands(self):
Command(
lambda w: self.open_file,
text="Open...",
shortcut=Key.MOD_1 + "o",
shortcut=toga.Key.MOD_1 + "o",
group=Group.FILE,
section=0,
),
Expand Down

0 comments on commit b50b2c3

Please sign in to comment.