import signal
import sys
from builtins import id as identifier
from email.message import Message

try:
    from importlib import metadata as importlib_metadata
except ImportError:
    # Backwards compatibility - imporlib.metadata was added in Python 3.8
    import importlib_metadata

from toga.command import CommandSet
from toga.handlers import wrapped_handler
from toga.icons import Icon
from toga.platform import get_platform_factory
from toga.window import Window


class MainWindow(Window):
    _WINDOW_CLASS = 'MainWindow'

    def __init__(self, id=None, title=None, position=(100, 100), size=(640, 480),
                 toolbar=None, resizeable=True, minimizable=True,
                 factory=None):
        super().__init__(
            id=id, title=title, position=position, size=size, toolbar=toolbar,
            resizeable=resizeable, closeable=True, minimizable=minimizable,
            factory=factory
        )


class App:
    """
    The App is the top level of any GUI program. It is the manager of all the
    other bits of the GUI app: the main window and events that window generates
    like user input.

    When you create an App you need to provide it a name, an id for uniqueness
    (by convention, the identifier is a reversed domain name.) and an
    optional startup function which should run once the App has initialised.
    The startup function typically constructs some initial user interface.

    If the name and app_id are *not* provided, the application will attempt
    to find application metadata. This process will determine the module in
    which the App class is defined, and look for a ``.dist-info`` file
    matching that name.

    Once the app is created you should invoke the main_loop() method, which
    will hand over execution of your program to Toga to make the App interface
    do its thing.

    The absolute minimum App would be::

        >>> app = toga.App(name='Empty App', app_id='org.beeware.empty')
        >>> app.main_loop()

    :param formal_name: The formal name of the application. Will be derived from
        packaging metadata if not provided.
    :param app_id: The unique application identifier. This will usually be a
        reversed domain name, e.g. 'org.beeware.myapp'. Will be derived from
        packaging metadata if not provided.
    :param app_name: The name of the Python module containing the app.
        Will be derived from the module defining the instance of the App class
        if not provided.
    :param id: The DOM identifier for the app (optional)
    :param icon: Identifier for the application's icon.
    :param author: The person or organization to be credited as the author
        of the application. Will be derived from application metadata if not
        provided.
    :param version: The version number of the app. Will be derived from
        packaging metadata if not provided.
    :param home_page: A URL for a home page for the app. Used in autogenerated
        help menu items. Will be derived from packaging metadata if not
        provided.
    :param description: A brief (one line) description of the app. Will be
        derived from packaging metadata if not provided.
    :param startup: The callback method before starting the app, typically to
        add the components. Must be a ``callable`` that expects a single
        argument of :class:`toga.App`.
    :param factory: A python module that is capable to return a implementation
        of this class with the same name. (optional & normally not needed)
    """
    app = None

    def __init__(
        self,
        formal_name=None,
        app_id=None,
        app_name=None,
        id=None,
        icon=None,
        author=None,
        version=None,
        home_page=None,
        description=None,
        startup=None,
        on_exit=None,
        factory=None,
    ):
        # Keep an accessible copy of the app instance
        App.app = self

        # We need a module name to load app metadata. If an app_name has been
        # provided, we can set the app name now, and derive the module name
        # from there.
        if app_name:
            self._app_name = app_name
        else:
            # If the code is contained in appname.py, and you start the app
            # using `python -m appname`, the main module package will report
            # as ''. Set the initial app name as None.
            # If the code is contained in appname.py, and you start the app
            # using `python appname.py`, the main module will report as None.
            # If the code is contained in a folder, and you start the app
            # using `python -m appname`, the main module will report as the
            # name of the folder.
            main_module_pkg = sys.modules['__main__'].__package__
            if main_module_pkg == '':
                self._app_name = None
            else:
                self._app_name = main_module_pkg

            # During tests, and when running from a prompt, there won't be
            # a __main__ module.

            # Try deconstructing the app name from the app ID
            if self._app_name is None and app_id:
                self._app_name = app_id.split('.')[-1]

        # Load the app metdata (if it is available)
        # Apps packaged with Briefcase will have this metadata.
        try:
            self.metadata = importlib_metadata.metadata(self.module_name)
        except importlib_metadata.PackageNotFoundError:
            self.metadata = Message()

        # Now that we have metadata, we can fix the app name (in the case
        # where the app name and the module name differ - e.g., an app name
        # of ``hello-world`` will have a module name of ``hello_world``).
        # We use the PEP566-compliant key ``Name```, rather than the internally
        # consistent key ``App-Name```.
        if self.metadata['Name'] is not None:
            self._app_name = self.metadata['Name']

        # Whatever app name has been given, speculatively attempt to import
        # the app module. Single-file apps won't have an app folder; apps with
        # misleading or misconfigured app names haven't given us enough
        # metadata to determine the app folder. In those cases, fall back to
        # an app name that *will* exist (``toga```)
        try:
            sys.modules[self.module_name]
        except KeyError:
            # Well that didn't work...
            self._app_name = 'toga'

        # If a name has been provided, use it; otherwise, look to
        # the module metadata. However, a name *must* be provided.
        if formal_name:
            self._formal_name = formal_name
        else:
            self._formal_name = self.metadata['Formal-Name']

        if self._formal_name is None:
            raise RuntimeError('Toga application must have a formal name')

        # If an app_id has been provided, use it; otherwise, look to
        # the module metadata. However, an app_id *must* be provied
        if app_id:
            self._app_id = app_id
        else:
            self._app_id = self.metadata['App-ID']

        if self._app_id is None:
            raise RuntimeError('Toga application must have an App ID')

        # If an author has been provided, use it; otherwise, look to
        # the module metadata.
        if author:
            self._author = author
        elif self.metadata['Author']:
            self._author = self.metadata['Author']

        # If a version has been provided, use it; otherwise, look to
        # the module metadata.
        if version:
            self._version = version
        elif self.metadata['Version']:
            self._version = self.metadata['Version']

        # If a home_page has been provided, use it; otherwise, look to
        # the module metadata.
        if home_page:
            self._home_page = home_page
        elif self.metadata['Home-page']:
            self._home_page = self.metadata['home_page']

        # If a description has been provided, use it; otherwise, look to
        # the module metadata.
        if description:
            self._description = description
        elif self.metadata['description']:
            self._description = self.metadata['Summary']

        # Set the application DOM ID; create an ID if one hasn't been provided.
        self._id = id if id else identifier(self)

        # Get a platform factory, and a paths instance from the factory.
        self.factory = get_platform_factory(factory)
        self.paths = self.factory.paths

        # If an icon (or icon name) has been explicitly provided, use it;
        # otherwise, the icon will be based on the app name.
        if icon:
            self.icon = icon
        else:
            self.icon = 'resources/{app_name}'.format(app_name=self.app_name)

        self.commands = CommandSet(factory=self.factory)

        self._startup_method = startup

        self._main_window = None
        self._on_exit = None

        self._full_screen_windows = None

        self._impl = self._create_impl()
        self.on_exit = on_exit

    def _create_impl(self):
        return self.factory.App(interface=self)

    @property
    def name(self):
        """
        The formal name of the app.

        :returns: The formal name of the app, as a ``str``.
        """
        return self._formal_name

    @property
    def formal_name(self):
        """
        The formal name of the app.

        :returns: The formal name of the app, as a ``str``.
        """
        return self._formal_name

    @property
    def app_name(self):
        """
        The machine-readable, PEP508-compliant name of the app.

        :returns: The machine-readable app name, as a ``str``.
        """
        return self._app_name

    @property
    def module_name(self):
        """
        The module name for the app

        :returns: The module name for the app, as a ``str``.
        """
        try:
            return self._app_name.replace('-', '_')
        except AttributeError:
            # If the app was created from an interactive prompt,
            # there won't be a module name.
            return None

    @property
    def app_id(self):
        """
        The identifier for the app.

        This is a reversed domain name, often used for targetting resources,
        etc.

        :returns: The identifier as a ``str``.
        """
        return self._app_id

    @property
    def author(self):
        """
        The author of the app. This may be an organization name

        :returns: The author of the app, as a ``str``.
        """
        return self._author

    @property
    def version(self):
        """
        The version number of the app.

        :returns: The version numberof the app, as a ``str``.
        """
        return self._version

    @property
    def home_page(self):
        """
        The URL of a web page for the app.

        :returns: The URL of the app's home page, as a ``str``.
        """
        return self._home_page

    @property
    def description(self):
        """
        A brief description of the app.

        :returns: A brief description of the app, as a ``str``.
        """
        return self._description

    @property
    def id(self):
        """
        The DOM identifier for the app.

        This id can be used to target CSS directives.

        :returns: A DOM identifier for the app.
        """
        return self._id

    @property
    def icon(self):
        """
        The Icon for the app.

        :returns: A ``toga.Icon`` instance for the app's icon.
        """
        return self._icon

    @icon.setter
    def icon(self, icon_or_name):
        if isinstance(icon_or_name, Icon):
            self._icon = icon_or_name
        else:
            self._icon = Icon(icon_or_name)

        self._icon.bind(self.factory)

    @property
    def main_window(self):
        """
        The main windows for the app.

        :returns: The main Window of the app.
        """
        return self._main_window

    @main_window.setter
    def main_window(self, window):
        self._main_window = window
        window.app = self
        self._impl.set_main_window(window)

    @property
    def current_window(self):
        """Return the currently active content window"""
        return self._impl.current_window().interface

    @property
    def is_full_screen(self):
        """Is the app currently in full screen mode?"""
        return self._full_screen_windows is not None

    def set_full_screen(self, *windows):
        """Make one or more windows full screen.

        Full screen is not the same as "maximized"; full screen mode
        is when all window borders and other chrome is no longer
        visible.

        Args:
            windows: The list of windows to go full screen,
                in order of allocation to screens. If the number of
                windows exceeds the number of available displays,
                those windows will not be visible. If no windows
                are specified, the app will exit full screen mode.
        """
        if not windows:
            self.exit_full_screen()
        else:
            self._impl.enter_full_screen(windows)
            self._full_screen_windows = windows

    def exit_full_screen(self):
        """Exit full screen mode."""
        if self.is_full_screen:
            self._impl.exit_full_screen(self._full_screen_windows)
            self._full_screen_windows = None

    def show_cursor(self):
        """Show cursor."""
        self._impl.show_cursor()

    def hide_cursor(self):
        """Hide cursor from view."""
        self._impl.hide_cursor()

    def startup(self):
        """ Create and show the main window for the application
        """
        self.main_window = MainWindow(title=self.formal_name, factory=self.factory)

        if self._startup_method:
            self.main_window.content = self._startup_method(self)

        self.main_window.show()

    def main_loop(self):
        """ Invoke the application to handle user input.
        This method typically only returns once the application is exiting.
        """
        # Modify signal handlers to make sure Ctrl-C is caught and handled.
        signal.signal(signal.SIGINT, signal.SIG_DFL)

        self._impl.main_loop()

    def exit(self):
        """ Quit the application gracefully.
        """
        self._impl.exit()

    @property
    def on_exit(self):
        """The handler to invoke before the application exits.

        Returns:
            The function ``callable`` that is called on application exit.
        """
        return self._on_exit

    @on_exit.setter
    def on_exit(self, handler):
        """Set the handler to invoke before the app exits.

        Args:
            handler (:obj:`callable`): The handler to invoke before the app exits.
        """
        self._on_exit = wrapped_handler(self, handler)
        self._impl.set_on_exit(self._on_exit)

    def add_background_task(self, handler):
        self._impl.add_background_task(handler)


class DocumentApp(App):
    """
    A document-based application.

    Definition and arguments are the same as a base App, plus the following:

    Args:
        document_types (:obj:`list` of :obj:`str`): Document types.

    """

    def __init__(
        self,
        formal_name=None,
        app_id=None,
        app_name=None,
        id=None,
        icon=None,
        author=None,
        version=None,
        home_page=None,
        description=None,
        startup=None,
        document_types=None,
        on_exit=None,
        factory=None,
    ):

        self.document_types = document_types
        self._documents = []

        super().__init__(
            formal_name=formal_name,
            app_id=app_id,
            app_name=app_name,
            id=id,
            icon=icon,
            author=author,
            version=version,
            home_page=home_page,
            description=description,
            startup=startup,
            on_exit=on_exit,
            factory=factory,
        )

    def _create_impl(self):
        return self.factory.DocumentApp(interface=self)

    @property
    def documents(self):
        """
        Return the list of documents associated with this app.

        Returns:
            A ``list`` of ``str``.
        """
        return self._documents

src/core/toga/app.py
import signal
import sys
from builtins import id as identifier
from email.message import Message

try:
    from importlib import metadata as importlib_metadata
except ImportError:
    # Backwards compatibility - imporlib.metadata was added in Python 3.8
    import importlib_metadata

from toga.command import CommandSet
from toga.handlers import wrapped_handler
from toga.icons import Icon
from toga.platform import get_platform_factory
from toga.window import Window


class MainWindow(Window):
    _WINDOW_CLASS = 'MainWindow'

    def __init__(self, id=None, title=None, position=(100, 100), size=(640, 480),
                 toolbar=None, resizeable=True, minimizable=True,
                 factory=None):
        super().__init__(
            id=id, title=title, position=position, size=size, toolbar=toolbar,
            resizeable=resizeable, closeable=True, minimizable=minimizable,
            factory=factory
        )


class App:
    """
    The App is the top level of any GUI program. It is the manager of all the
    other bits of the GUI app: the main window and events that window generates
    like user input.

    When you create an App you need to provide it a name, an id for uniqueness
    (by convention, the identifier is a reversed domain name.) and an
    optional startup function which should run once the App has initialised.
    The startup function typically constructs some initial user interface.

    If the name and app_id are *not* provided, the application will attempt
    to find application metadata. This process will determine the module in
    which the App class is defined, and look for a ``.dist-info`` file
    matching that name.

    Once the app is created you should invoke the main_loop() method, which
    will hand over execution of your program to Toga to make the App interface
    do its thing.

    The absolute minimum App would be::

        >>> app = toga.App(formal_name='Empty App', app_id='org.beeware.empty')
        >>> app.main_loop()

    :param formal_name: The formal name of the application. Will be derived from
        packaging metadata if not provided.
    :param app_id: The unique application identifier. This will usually be a
        reversed domain name, e.g. 'org.beeware.myapp'. Will be derived from
        packaging metadata if not provided.
    :param app_name: The name of the Python module containing the app.
        Will be derived from the module defining the instance of the App class
        if not provided.
    :param id: The DOM identifier for the app (optional)
    :param icon: Identifier for the application's icon.
    :param author: The person or organization to be credited as the author
        of the application. Will be derived from application metadata if not
        provided.
    :param version: The version number of the app. Will be derived from
        packaging metadata if not provided.
    :param home_page: A URL for a home page for the app. Used in autogenerated
        help menu items. Will be derived from packaging metadata if not
        provided.
    :param description: A brief (one line) description of the app. Will be
        derived from packaging metadata if not provided.
    :param startup: The callback method before starting the app, typically to
        add the components. Must be a ``callable`` that expects a single
        argument of :class:`toga.App`.
    :param factory: A python module that is capable to return a implementation
        of this class with the same name. (optional & normally not needed)
    """
    app = None

    def __init__(
        self,
        formal_name=None,
        app_id=None,
        app_name=None,
        id=None,
        icon=None,
        author=None,
        version=None,
        home_page=None,
        description=None,
        startup=None,
        on_exit=None,
        factory=None,
    ):
        # Keep an accessible copy of the app instance
        App.app = self

        # We need a module name to load app metadata. If an app_name has been
        # provided, we can set the app name now, and derive the module name
        # from there.
        if app_name:
            self._app_name = app_name
        else:
            # If the code is contained in appname.py, and you start the app
            # using `python -m appname`, the main module package will report
            # as ''. Set the initial app name as None.
            # If the code is contained in appname.py, and you start the app
            # using `python appname.py`, the main module will report as None.
            # If the code is contained in a folder, and you start the app
            # using `python -m appname`, the main module will report as the
            # name of the folder.
            main_module_pkg = sys.modules['__main__'].__package__
            if main_module_pkg == '':
                self._app_name = None
            else:
                self._app_name = main_module_pkg

            # During tests, and when running from a prompt, there won't be
            # a __main__ module.

            # Try deconstructing the app name from the app ID
            if self._app_name is None and app_id:
                self._app_name = app_id.split('.')[-1]

        # Load the app metdata (if it is available)
        # Apps packaged with Briefcase will have this metadata.
        try:
            self.metadata = importlib_metadata.metadata(self.module_name)
        except importlib_metadata.PackageNotFoundError:
            self.metadata = Message()

        # Now that we have metadata, we can fix the app name (in the case
        # where the app name and the module name differ - e.g., an app name
        # of ``hello-world`` will have a module name of ``hello_world``).
        # We use the PEP566-compliant key ``Name```, rather than the internally
        # consistent key ``App-Name```.
        if self.metadata['Name'] is not None:
            self._app_name = self.metadata['Name']

        # Whatever app name has been given, speculatively attempt to import
        # the app module. Single-file apps won't have an app folder; apps with
        # misleading or misconfigured app names haven't given us enough
        # metadata to determine the app folder. In those cases, fall back to
        # an app name that *will* exist (``toga```)
        try:
            sys.modules[self.module_name]
        except KeyError:
            # Well that didn't work...
            self._app_name = 'toga'

        # If a name has been provided, use it; otherwise, look to
        # the module metadata. However, a name *must* be provided.
        if formal_name:
            self._formal_name = formal_name
        else:
            self._formal_name = self.metadata['Formal-Name']

        if self._formal_name is None:
            raise RuntimeError('Toga application must have a formal name')

        # If an app_id has been provided, use it; otherwise, look to
        # the module metadata. However, an app_id *must* be provied
        if app_id:
            self._app_id = app_id
        else:
            self._app_id = self.metadata['App-ID']

        if self._app_id is None:
            raise RuntimeError('Toga application must have an App ID')

        # If an author has been provided, use it; otherwise, look to
        # the module metadata.
        if author:
            self._author = author
        elif self.metadata['Author']:
            self._author = self.metadata['Author']

        # If a version has been provided, use it; otherwise, look to
        # the module metadata.
        if version:
            self._version = version
        elif self.metadata['Version']:
            self._version = self.metadata['Version']

        # If a home_page has been provided, use it; otherwise, look to
        # the module metadata.
        if home_page:
            self._home_page = home_page
        elif self.metadata['Home-page']:
            self._home_page = self.metadata['home_page']

        # If a description has been provided, use it; otherwise, look to
        # the module metadata.
        if description:
            self._description = description
        elif self.metadata['description']:
            self._description = self.metadata['Summary']

        # Set the application DOM ID; create an ID if one hasn't been provided.
        self._id = id if id else identifier(self)

        # Get a platform factory, and a paths instance from the factory.
        self.factory = get_platform_factory(factory)
        self.paths = self.factory.paths

        # If an icon (or icon name) has been explicitly provided, use it;
        # otherwise, the icon will be based on the app name.
        if icon:
            self.icon = icon
        else:
            self.icon = 'resources/{app_name}'.format(app_name=self.app_name)

        self.commands = CommandSet(factory=self.factory)

        self._startup_method = startup

        self._main_window = None
        self._on_exit = None

        self._full_screen_windows = None

        self._impl = self._create_impl()
        self.on_exit = on_exit

    def _create_impl(self):
        return self.factory.App(interface=self)

    @property
    def name(self):
        """
        The formal name of the app.

        :returns: The formal name of the app, as a ``str``.
        """
        return self._formal_name

    @property
    def formal_name(self):
        """
        The formal name of the app.

        :returns: The formal name of the app, as a ``str``.
        """
        return self._formal_name

    @property
    def app_name(self):
        """
        The machine-readable, PEP508-compliant name of the app.

        :returns: The machine-readable app name, as a ``str``.
        """
        return self._app_name

    @property
    def module_name(self):
        """
        The module name for the app

        :returns: The module name for the app, as a ``str``.
        """
        try:
            return self._app_name.replace('-', '_')
        except AttributeError:
            # If the app was created from an interactive prompt,
            # there won't be a module name.
            return None

    @property
    def app_id(self):
        """
        The identifier for the app.

        This is a reversed domain name, often used for targetting resources,
        etc.

        :returns: The identifier as a ``str``.
        """
        return self._app_id

    @property
    def author(self):
        """
        The author of the app. This may be an organization name

        :returns: The author of the app, as a ``str``.
        """
        return self._author

    @property
    def version(self):
        """
        The version number of the app.

        :returns: The version numberof the app, as a ``str``.
        """
        return self._version

    @property
    def home_page(self):
        """
        The URL of a web page for the app.

        :returns: The URL of the app's home page, as a ``str``.
        """
        return self._home_page

    @property
    def description(self):
        """
        A brief description of the app.

        :returns: A brief description of the app, as a ``str``.
        """
        return self._description

    @property
    def id(self):
        """
        The DOM identifier for the app.

        This id can be used to target CSS directives.

        :returns: A DOM identifier for the app.
        """
        return self._id

    @property
    def icon(self):
        """
        The Icon for the app.

        :returns: A ``toga.Icon`` instance for the app's icon.
        """
        return self._icon

    @icon.setter
    def icon(self, icon_or_name):
        if isinstance(icon_or_name, Icon):
            self._icon = icon_or_name
        else:
            self._icon = Icon(icon_or_name)

        self._icon.bind(self.factory)

    @property
    def main_window(self):
        """
        The main windows for the app.

        :returns: The main Window of the app.
        """
        return self._main_window

    @main_window.setter
    def main_window(self, window):
        self._main_window = window
        window.app = self
        self._impl.set_main_window(window)

    @property
    def current_window(self):
        """Return the currently active content window"""
        return self._impl.current_window().interface

    @property
    def is_full_screen(self):
        """Is the app currently in full screen mode?"""
        return self._full_screen_windows is not None

    def set_full_screen(self, *windows):
        """Make one or more windows full screen.

        Full screen is not the same as "maximized"; full screen mode
        is when all window borders and other chrome is no longer
        visible.

        Args:
            windows: The list of windows to go full screen,
                in order of allocation to screens. If the number of
                windows exceeds the number of available displays,
                those windows will not be visible. If no windows
                are specified, the app will exit full screen mode.
        """
        if not windows:
            self.exit_full_screen()
        else:
            self._impl.enter_full_screen(windows)
            self._full_screen_windows = windows

    def exit_full_screen(self):
        """Exit full screen mode."""
        if self.is_full_screen:
            self._impl.exit_full_screen(self._full_screen_windows)
            self._full_screen_windows = None

    def show_cursor(self):
        """Show cursor."""
        self._impl.show_cursor()

    def hide_cursor(self):
        """Hide cursor from view."""
        self._impl.hide_cursor()

    def startup(self):
        """ Create and show the main window for the application
        """
        self.main_window = MainWindow(title=self.formal_name, factory=self.factory)

        if self._startup_method:
            self.main_window.content = self._startup_method(self)

        self.main_window.show()

    def main_loop(self):
        """ Invoke the application to handle user input.
        This method typically only returns once the application is exiting.
        """
        # Modify signal handlers to make sure Ctrl-C is caught and handled.
        signal.signal(signal.SIGINT, signal.SIG_DFL)

        self._impl.main_loop()

    def exit(self):
        """ Quit the application gracefully.
        """
        self._impl.exit()

    @property
    def on_exit(self):
        """The handler to invoke before the application exits.

        Returns:
            The function ``callable`` that is called on application exit.
        """
        return self._on_exit

    @on_exit.setter
    def on_exit(self, handler):
        """Set the handler to invoke before the app exits.

        Args:
            handler (:obj:`callable`): The handler to invoke before the app exits.
        """
        self._on_exit = wrapped_handler(self, handler)
        self._impl.set_on_exit(self._on_exit)

    def add_background_task(self, handler):
        self._impl.add_background_task(handler)


class DocumentApp(App):
    """
    A document-based application.

    Definition and arguments are the same as a base App, plus the following:

    Args:
        document_types (:obj:`list` of :obj:`str`): Document types.

    """

    def __init__(
        self,
        formal_name=None,
        app_id=None,
        app_name=None,
        id=None,
        icon=None,
        author=None,
        version=None,
        home_page=None,
        description=None,
        startup=None,
        document_types=None,
        on_exit=None,
        factory=None,
    ):

        self.document_types = document_types
        self._documents = []

        super().__init__(
            formal_name=formal_name,
            app_id=app_id,
            app_name=app_name,
            id=id,
            icon=icon,
            author=author,
            version=version,
            home_page=home_page,
            description=description,
            startup=startup,
            on_exit=on_exit,
            factory=factory,
        )

    def _create_impl(self):
        return self.factory.DocumentApp(interface=self)

    @property
    def documents(self):
        """
        Return the list of documents associated with this app.

        Returns:
            A ``list`` of ``str``.
        """
        return self._documents

src/core/toga/app.py
diff --git a/src/core/tests/test_image.py b/src/core/tests/test_image.py
index 1748625908..455fc8459f 100644
--- a/src/core/tests/test_image.py
+++ b/src/core/tests/test_image.py
@@ -8,6 +8,11 @@ class ImageTests(TestCase):
     def setUp(self):
         super().setUp()
 
+        # an App must have been created before calling image.bind
+        # because it tries to resolve the image path against the app path.
+        toga.App(formal_name='Image Test App',
+                 app_id='org.beeware.test_image',
+                 factory=toga_dummy.factory)
         self.path = 'path/to/image.jpg'
         self.image = toga.Image(path=self.path)
 
diff --git a/src/core/toga/app.py b/src/core/toga/app.py
index 25961e2ab1..9e38ed6dc6 100644
--- a/src/core/toga/app.py
+++ b/src/core/toga/app.py
@@ -51,7 +51,7 @@ class App:
 
     The absolute minimum App would be::
 
-        >>> app = toga.App(name='Empty App', app_id='org.beeware.empty')
+        >>> app = toga.App(formal_name='Empty App', app_id='org.beeware.empty')
         >>> app.main_loop()
 
     :param formal_name: The formal name of the application. Will be derived from

tests.test_image.ImageTests cannot pass
## Expected Behavior

all tests pass

## Current Behavior

```
======================================================================
ERROR: test_object_created (tests.test_image.ImageTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/elelay/devel/gpodder-toga/toga/src/core/tests/test_image.py", line 20, in test_object_created
    self.image.bind(factory=toga_dummy.factory)
  File "/home/elelay/devel/gpodder-toga/toga/src/core/toga/images.py", line 29, in bind
    full_path = factory.paths.app / factory.paths.Path(self.path)
  File "/home/elelay/devel/gpodder-toga/toga/src/dummy/toga_dummy/paths.py", line 14, in app
    return Path(sys.modules[App.app.module_name].__file__).parent
AttributeError: 'NoneType' object has no attribute 'module_name'
```

## Steps to reproduce
follow contribute steps


## Your Environment

* Python 3.8.2

* Operating System and Version 

  - [ ] macOS - version:
  - [x] Linux - ArchLinux
  - [ ] Windows - version:
  - [ ] Other - name:  - version:

* Toga Version (list the specific version number or git hash)
c940d61c

* Toga Target (the type of app you are trying to generate)

  - [ ] android
  - [ ] cocoa
  - [ ] django
  - [x] gtk
  - [ ] iOS
  - [ ] tvOS
  - [ ] watchOS
  - [ ] winforms
  - [ ] win32
  - [ ] Other (please specify)


## Resolution

This testcase doesn't create an App, so `paths.Paths.app()` allways encounters a None in `App.app.module_name` in [toga_dummy/paths.py](https://github.com/beeware/toga/blob/master/src/dummy/toga_dummy/paths.py#L14).

This could be fixed in different ways:

 - change `Paths.app()` in dummy/paths.py to return `Path.cwd()` when not `App.app`

 - or change toga/images.py for bind to not go for `Paths.app` if the image's path is absolute.
   Also change the test case to use an absolute path (won't exist anyway)
tests.test_image.ImageTests cannot pass
## Expected Behavior

all tests pass

## Current Behavior

```
======================================================================
ERROR: test_object_created (tests.test_image.ImageTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/elelay/devel/gpodder-toga/toga/src/core/tests/test_image.py", line 20, in test_object_created
    self.image.bind(factory=toga_dummy.factory)
  File "/home/elelay/devel/gpodder-toga/toga/src/core/toga/images.py", line 29, in bind
    full_path = factory.paths.app / factory.paths.Path(self.path)
  File "/home/elelay/devel/gpodder-toga/toga/src/dummy/toga_dummy/paths.py", line 14, in app
    return Path(sys.modules[App.app.module_name].__file__).parent
AttributeError: 'NoneType' object has no attribute 'module_name'
```

## Steps to reproduce
follow contribute steps


## Your Environment

* Python 3.8.2

* Operating System and Version 

  - [ ] macOS - version:
  - [x] Linux - ArchLinux
  - [ ] Windows - version:
  - [ ] Other - name:  - version:

* Toga Version (list the specific version number or git hash)
c940d61c

* Toga Target (the type of app you are trying to generate)

  - [ ] android
  - [ ] cocoa
  - [ ] django
  - [x] gtk
  - [ ] iOS
  - [ ] tvOS
  - [ ] watchOS
  - [ ] winforms
  - [ ] win32
  - [ ] Other (please specify)


## Resolution

This testcase doesn't create an App, so `paths.Paths.app()` allways encounters a None in `App.app.module_name` in [toga_dummy/paths.py](https://github.com/beeware/toga/blob/master/src/dummy/toga_dummy/paths.py#L14).

This could be fixed in different ways:

 - change `Paths.app()` in dummy/paths.py to return `Path.cwd()` when not `App.app`

 - or change toga/images.py for bind to not go for `Paths.app` if the image's path is absolute.
   Also change the test case to use an absolute path (won't exist anyway)
