Пример #1
0
class Command:
    """
    Args:
        action: a function to invoke when the command is activated.
        label: a name for the command.
        shortcut: (optional) a key combination that can be used to invoke the
            command.
        tooltip: (optional) a short description for what the command will do.
        icon: (optional) a path to an icon resource to decorate the command.
        group: (optional) a Group object describing a collection of similar
            commands. If no group is specified, a default "Command" group will
            be used.
        section: (optional) an integer providing a sub-grouping. If no section
            is specified, the command will be allocated to section 0 within the
            group.
        order: (optional) an integer indicating where a command falls within a
            section. If a Command doesn't have an order, it will be sorted
            alphabetically by label within its section.
        enabled: whether to enable the command or not.
    """
    def __init__(self, action, label,
                 shortcut=None, tooltip=None, icon=None,
                 group=None, section=None, order=None, enabled=True, factory=None):
        self.factory = factory

        self.action = wrapped_handler(self, action)
        self.label = label

        self.shortcut = shortcut
        self.tooltip = tooltip
        self.icon = icon

        self.group = group if group else Group.COMMANDS
        self.section = section if section else 0
        self.order = order if order else 0

        self._impl = None

        self.enabled = enabled and self.action is not None

    def bind(self, factory):
        self.factory = factory

        if self._impl is None:
            self._impl = self.factory.Command(interface=self)

        if self._icon:
            self._icon.bind(self.factory)

        return self._impl

    @property
    def enabled(self):
        return self._enabled

    @enabled.setter
    def enabled(self, value):
        self._enabled = value
        if self._impl is not None:
            self._impl.set_enabled(value)

    @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) or icon_or_name is None:
            self._icon = icon_or_name
        else:
            self._icon = Icon(icon_or_name)

        if self._icon and self.factory:
            self._icon.bind(self.factory)
Пример #2
0
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:
            self._app_name = sys.modules['__main__'].__package__
            # During tests, and when running from a prompt, there won't be
            # a __main__ module. Fall back to a module that we know *does*
            # exist.
            if self._app_name is None:
                self._app_name = 'toga'

        # 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 key "Name", rather than "App-Name".
        if app_name is None and self.metadata['Name'] is not None:
            self._app_name = self.metadata['Name']

        # 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)