Skip to content

Commit

Permalink
Merge branch 'main' into patch-20
Browse files Browse the repository at this point in the history
  • Loading branch information
proneon267 committed Jul 10, 2024
2 parents 37e1add + e343028 commit 9d199f5
Show file tree
Hide file tree
Showing 168 changed files with 6,211 additions and 3,478 deletions.
38 changes: 32 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ jobs:
strategy:
fail-fast: false
matrix:
backend: [ "macOS-x86_64", "macOS-arm64", "windows", "linux", "android", "iOS" ]
backend: [ "macOS-x86_64", "macOS-arm64", "windows", "linux-x11", "linux-wayland","android", "iOS" ]
include:
- pre-command: ""
briefcase-run-prefix: ""
Expand All @@ -208,7 +208,7 @@ jobs:
# We use a fixed Ubuntu version rather than `-latest` because at some point,
# `-latest` will be updated, but it will be a soft changeover, which would cause
# the system Python version to become inconsistent from run to run.
- backend: "linux"
- backend: "linux-x11"
platform: "linux"
runs-on: "ubuntu-22.04"
# The package list should be the same as in tutorial-0.rst, and the BeeWare
Expand All @@ -223,19 +223,45 @@ jobs:
sudo apt install -y --no-install-recommends \
blackbox pkg-config python3-dev libgirepository1.0-dev libcairo2-dev gir1.2-webkit2-4.1
# Start Virtual X server
# Start Virtual X Server
echo "Start X server..."
Xvfb :99 -screen 0 2048x1536x24 &
sleep 1
# Start Window manager
# Start Window Mmanager
echo "Start window manager..."
DISPLAY=:99 blackbox &
sleep 1
briefcase-run-prefix: 'DISPLAY=:99'
setup-python: false # Use the system Python packages
app-user-data-path: "$HOME/.local/share/testbed"

- backend: "linux-wayland"
platform: "linux"
runs-on: "ubuntu-22.04"
# The package list should be the same as in tutorial-0.rst, and the BeeWare
# tutorial, plus mutter to provide a window manager, and libjpeg-dev for Pillow.
pre-command: |
sudo apt update -y
sudo apt install -y --no-install-recommends \
mutter pkg-config python3-dev libgirepository1.0-dev libcairo2-dev gir1.2-webkit2-4.1
# Start Virtual X Server
echo "Start X server..."
Xvfb :99 -screen 0 2048x1536x24 &
sleep 1
# Start Window Manager
echo "Start window manager..."
# mutter is being run inside a virtual X server because mutter's headless
# mode is not compatible with Gtk
DISPLAY=:99 MUTTER_DEBUG_DUMMY_MODE_SPECS=2048x1536 \
mutter --nested --wayland --no-x11 --wayland-display toga &
sleep 1
briefcase-run-prefix: "WAYLAND_DISPLAY=toga"
setup-python: false # Use the system Python packages
app-user-data-path: "$HOME/.local/share/testbed"

- backend: "windows"
platform: "windows"
runs-on: "windows-latest"
Expand All @@ -258,7 +284,7 @@ jobs:
platform: "android"
runs-on: "ubuntu-latest"
briefcase-run-prefix: JAVA_HOME=${JAVA_HOME_17_X64}
briefcase-run-args: >
briefcase-run-args: >-
--device '{"avd":"beePhone","skin":"pixel_3a"}'
--Xemulator=-no-window
--Xemulator=-no-snapshot
Expand Down Expand Up @@ -304,7 +330,7 @@ jobs:
timeout-minutes: 15
run: |
${{ matrix.briefcase-run-prefix }} \
briefcase run ${{ matrix.platform }} --log --test ${{ matrix.briefcase-run-args }}
briefcase run ${{ matrix.platform }} --log --test ${{ matrix.briefcase-run-args }} -- --ci
- name: Upload Logs
uses: actions/upload-artifact@v4.3.3
Expand Down
2 changes: 1 addition & 1 deletion android/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[build-system]
requires = [
"setuptools==70.0.0",
"setuptools==70.1.1",
"setuptools_scm==8.1.0",
"setuptools_dynamic_dependencies @ git+https://github.com/beeware/setuptools_dynamic_dependencies",
]
Expand Down
40 changes: 31 additions & 9 deletions android/src/toga_android/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,13 @@
from java import dynamic_proxy
from org.beeware.android import IPythonApp, MainActivity

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

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 Expand Up @@ -97,6 +94,12 @@ def onOptionsItemSelected(self, menuitem):
return True

def onPrepareOptionsMenu(self, menu):
# If the main window doesn't have a toolbar, there's no preparation required;
# this is a simple main window, which can't have commands. This can't be
# validated in the testbed, so it's marked no-cover.
if not hasattr(self._impl.interface.main_window, "toolbar"):
return False # pragma: no cover

menu.clear()
itemid = 1 # 0 is the same as Menu.NONE.
groupid = 1
Expand Down Expand Up @@ -181,6 +184,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 @@ -216,7 +222,9 @@ def create_app_commands(self):
)

def create_menus(self):
self.native.invalidateOptionsMenu() # Triggers onPrepareOptionsMenu
# Menu items are configured as part of onPrepareOptionsMenu; trigger that
# handler.
self.native.invalidateOptionsMenu()

######################################################################
# App lifecycle
Expand All @@ -238,7 +246,13 @@ def set_icon(self, icon):
pass # pragma: no cover

def set_main_window(self, window):
pass
if window is None or window == toga.App.BACKGROUND:
raise ValueError("Apps without main windows 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 Expand Up @@ -274,8 +288,16 @@ def show_about_dialog(self):
message_parts.append(f"Author: {self.interface.author}")
if self.interface.description is not None:
message_parts.append(f"\n{self.interface.description}")
self.interface.main_window.info_dialog(
f"About {self.interface.formal_name}", "\n".join(message_parts)

# Create and show an info dialog as the about dialog.
# We don't care about the response.
asyncio.create_task(
self.interface.dialog(
InfoDialog(
f"About {self.interface.formal_name}",
"\n".join(message_parts),
)
)
)

######################################################################
Expand Down
67 changes: 36 additions & 31 deletions android/src/toga_android/dialogs.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from abc import ABC

from android import R
from android.app import AlertDialog
from android.content import DialogInterface
from java import dynamic_proxy

import toga


class OnClickListener(dynamic_proxy(DialogInterface.OnClickListener)):
def __init__(self, fn=None, value=None):
Expand All @@ -16,25 +16,31 @@ def onClick(self, _dialog, _which):
self._fn(self._value)


class BaseDialog(ABC):
def __init__(self, interface):
self.interface = interface
self.interface._impl = self
class BaseDialog:
def show(self, host_window, future):
self.future = future

if self.native:
# Show the dialog. Don't differentiate between app and window modal dialogs.
self.native.show()
else:
# Dialog doesn't have an implementation. This can't be covered, as
# the testbed shortcuts the test before showing the dialog.
self.future.set_result(None) # pragma: no cover


class TextDialog(BaseDialog):
def __init__(
self,
interface,
title,
message,
positive_text,
negative_text=None,
icon=None,
):
super().__init__(interface=interface)
super().__init__()

self.native = AlertDialog.Builder(interface.window._impl.app.native)
self.native = AlertDialog.Builder(toga.App.app.current_window._impl.app.native)
self.native.setCancelable(False)
self.native.setTitle(title)
self.native.setMessage(message)
Expand All @@ -52,26 +58,23 @@ def __init__(
self.native.setNegativeButton(
negative_text, OnClickListener(self.completion_handler, False)
)
self.native.show()

def completion_handler(self, return_value: bool) -> None:
self.interface.set_result(return_value)
self.future.set_result(return_value)


class InfoDialog(TextDialog):
def __init__(self, interface, title, message):
def __init__(self, title, message):
super().__init__(
interface=interface,
title=title,
message=message,
positive_text="OK",
)


class QuestionDialog(TextDialog):
def __init__(self, interface, title, message):
def __init__(self, title, message):
super().__init__(
interface=interface,
title=title,
message=message,
positive_text="Yes",
Expand All @@ -80,9 +83,8 @@ def __init__(self, interface, title, message):


class ConfirmDialog(TextDialog):
def __init__(self, interface, title, message):
def __init__(self, title, message):
super().__init__(
interface=interface,
title=title,
message=message,
positive_text="OK",
Expand All @@ -91,9 +93,8 @@ def __init__(self, interface, title, message):


class ErrorDialog(TextDialog):
def __init__(self, interface, title, message):
def __init__(self, title, message):
super().__init__(
interface=interface,
title=title,
message=message,
positive_text="OK",
Expand All @@ -104,48 +105,52 @@ def __init__(self, interface, title, message):
class StackTraceDialog(BaseDialog):
def __init__(
self,
interface,
title,
message,
**kwargs,
):
super().__init__(interface=interface)
interface.window.factory.not_implemented("Window.stack_trace_dialog()")
super().__init__()

toga.App.app.factory.not_implemented("dialogs.StackTraceDialog()")
self.native = None


class SaveFileDialog(BaseDialog):
def __init__(
self,
interface,
title,
filename,
initial_directory,
file_types=None,
):
super().__init__(interface=interface)
interface.window.factory.not_implemented("Window.save_file_dialog()")
super().__init__()

toga.App.app.factory.not_implemented("dialogs.SaveFileDialog()")
self.native = None


class OpenFileDialog(BaseDialog):
def __init__(
self,
interface,
title,
initial_directory,
file_types,
multiple_select,
):
super().__init__(interface=interface)
interface.window.factory.not_implemented("Window.open_file_dialog()")
super().__init__()

toga.App.app.factory.not_implemented("dialogs.OpenFileDialog()")
self.native = None


class SelectFolderDialog(BaseDialog):
def __init__(
self,
interface,
title,
initial_directory,
multiple_select,
):
super().__init__(interface=interface)
interface.window.factory.not_implemented("Window.select_folder_dialog()")
super().__init__()

toga.App.app.factory.not_implemented("dialogs.SelectFolderDialog()")
self.native = None
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
Loading

0 comments on commit 9d199f5

Please sign in to comment.