Commit 68211daa authored by David Boddie's avatar David Boddie 💬
Browse files

Import existing content

parent b37290dd
......@@ -16,3 +16,6 @@
[submodule "Apps/Examples/Networking/NetworkState/app"]
path = Apps/Examples/Networking/NetworkState/app
url = https://source.puri.sm/Librem5/example-apps/network-state.git
[submodule "Apps/Examples/General/Treasure/app"]
path = Apps/Examples/General/Treasure/app
url = https://source.puri.sm/librem5-examples/treasure.git
.. |main-file| replace:: ``treasure.in``
.. |executable| replace:: ``treasure``
.. |desktop-entry-in| replace:: ``com.example.treasure.desktop.in``
.. |desktop-entry| replace:: ``com.example.treasure.desktop``
.. |desktop-entry-ref| replace:: desktop entry file
.. |svg-file| replace:: ``com.example.treasure.svg``
.. include:: /Apps/Examples/common/Building_the_App.txt
Implementation Details
======================
.. toctree::
Details/User_Interface
Details/Main_Program
Details/Template_Files
The application consists of a graphical user interface (GUI) and program code. Most of the GUI can be designed using a tool such as `Glade`_, yet its function needs to be implemented in code. Even for a simple application like this one, there is still a certain amount of code required to connect elements of the user interface to basic actions.
In the :ref:`examples_Treasure_user_interface` section we provide a quick overview of the GUI, showing what it looks like in the Glade tool. Online resources for Glade provide detailed instruction on the process of building user interfaces.
In the :ref:`examples_Treasure_main_program` section we tie the user interface to the program code that performs the work of the application, from initialization to game logic and handling of user interface actions.
In the :ref:`examples_Treasure_template_files` section we look at the template files that will be filled in when the project is configured. These include the executable that will run when the user launches the application.
.. include:: /links.txt
.. _`examples_Treasure_main_program`:
The Main Program
================
Most the code for the application is included in a single ``main.py`` file which contains a single ``Application`` class to manage the running of the application and a ``main`` function to start it.
Importing Modules
-----------------
We begin by importing two standard Python modules that our application will need:
.. literalinclude:: ../app/src/main.py
:language: python3
:start-at: import
:end-at: from
The ``sys`` module provides access to the command line arguments used when the application was run. We use the ``random`` module to make our game interesting. The ``config`` module is one that is generated by the build system to enable us to access information about where the application and its resources are installed -- see :ref:executable_template for details.
We also import modules that allow us to create user interfaces:
.. literalinclude:: ../app/src/main.py
:language: python3
:start-at: import gi
:end-at: Handy.init
These are standard modules for accessing GTK+ and GNOME features, plus the ``Handy`` module that helps us to create adaptive user interfaces.
The Application Class
---------------------
The application is represented by the ``Application`` class which is derived from the standard `Gtk.Application <GtkApplication_>`_ class. This class provides methods to set up the application, respond to user interface events, and implement the rules of the game it presents to the user. See `Using GtkApplication`_ in the GNOME documentation for an overview of the lifecycle of an application.
The class is defined in the normal way, beginning with the ``__init__`` method:
.. literalinclude:: ../app/src/main.py
:language: python3
:start-at: class Application
:end-at: GLib.set_application_name
This method performs two things that are necessary for the application to run correctly:
1. It uses the ``super`` built-function to call the ``__init__`` method of the base class. This associates the application with the application ID given. The application ID must have a certain format. This is described in the `Gio.GApplication documentation`_.
2. It calls the `GLib.set_application_name <g_set_application_name_>`_ function to set a user-readable application name that will be localized if translations are available.
Application Start-up
~~~~~~~~~~~~~~~~~~~~
The ``do_startup`` method is responsible for performing tasks that only need to be done at application start-up. In this example there are plenty of things that need to be done at this point.
.. literalinclude:: ../app/src/main.py
:language: python3
:start-at: def do_startup
:end-at: Gtk.Application.do_startup
After calling the ``do_startup`` method of the base class, we can begin to set up the application, beginning by reading a style sheet from the application's built-in resources:
.. literalinclude:: ../app/src/main.py
:language: python3
:start-at: Load a style
:end-at: os.path.join
We also locate and read the application's images from their installed locations on the system, as described in :ref:`examples_Treasure_data_dir`.
The application's user interface is defined using UI files which we briefly covered in :ref:`examples_Treasure_user_interface`. The interface is constructed at run-time using the `Gtk.Builder <GtkBuilder_>`_ class. We set it up for use by creating an instance of this class and set its translation domain so that the user-visible text in the UI files can be translated using message catalogs -- see :ref:`examples_Treasure_po_dir`. Then we add the UI files from the application resources so that we can create the window and menu:
.. literalinclude:: ../app/src/main.py
:language: python3
:start-at: Create a builder
:end-at: set_property
We use the builder to obtain widgets from the user interface it holds, beginning with the window itself which was bundled in the application's resources. We set the window's ``application`` property to link the window with the application. This ensures that the application runs until the window is closed -- see the `application property`_ documentation for more information.
We also obtain the menu from the builder and add it to the ``menu_button`` widget, which is part of the window, as its menu model. This allows the menu button to show the menu that we defined:
.. literalinclude:: ../app/src/main.py
:language: python3
:start-at: Get the menu
:end-at: set_menu_model(menu)
We connect the menu bundled in the application's resources to program code via actions with the same name as those in the ``menus.ui`` file, as described in the :ref:`examples_Treasure_primary_menu` section:
.. literalinclude:: ../app/src/main.py
:language: python3
:start-at: Create actions
:end-at: add_action(quit_action)
By adding these actions to the ``Application`` instance, we cause them to be triggered when the user selects the corresponding menu items. Because we connect the ``active`` signal of each action to ``Application`` methods, those methods will be called when the user starts a new game or quits the application.
There are four more widgets that we need to access: the box that holds the main window area and the grid layout and two labels that are inside it. Again, we use the builder to obtain these widgets:
.. literalinclude:: ../app/src/main.py
:language: python3
:start-at: Get widgets
:end-at: lowest_turns_label
The application uses `GSettings`_ to record the user's best score so that the next time the game is played, the score is not forgotten. This value is read by creating a ``Gio.Settings`` object and reading the appropriate integer value. This is then used to update the corresponding label:
.. literalinclude:: ../app/src/main.py
:language: python3
:start-at: Read the lowest number
:end-at: self.rows
The final few lines of the method initialize attributes that keep track of the widgets that will be placed in the window's main area.
Application Activation
~~~~~~~~~~~~~~~~~~~~~~
One of the shortest methods is the ``do_activate`` method because all of the initialization related to the application's window is performed in the ``do_startup`` method:
.. literalinclude:: ../app/src/main.py
:language: python3
:start-at: do_activate
:end-at: present
The method simply calls the window's ``present`` method to show it.
Event Handler Methods
~~~~~~~~~~~~~~~~~~~~~
In the ``do_startup`` method we connected the ``activated`` signals for the two menu actions to two methods in the ``Application`` class: ``on_quit`` and ``on_new_game``. The first of these handles the action for quitting the application:
.. literalinclude:: ../app/src/main.py
:language: python3
:start-at: def on_quit
:end-at: quit()
Note that we do not perform any checks or ask the user whether they want to quit. More complex applications should check for any unsaved data and ask the user for guidance before simply shutting down.
The ``on_new_game`` method is longer and can be split into three sections. The first of these removes any cells in the grid left over from previous games, before calculating how much space there is to allocate a new set of cells for the new game. This calculation cannot be done before the window has been shown because there would be no easy way to determine how much space there is to fill.
.. literalinclude:: ../app/src/main.py
:language: python3
:start-at: def on_new_game
:end-at: self.rows =
Using a fixed size for the cells that will be placed in the grid, we calculate the number of columns and rows that can fit into the available space.
With an empty grid to fill, we create a ``Gtk.Button`` to fill each cell and decorate it with an image based on one of the images loaded in the ``do_startup`` method.
.. literalinclude:: ../app/src/main.py
:language: python3
:start-at: Map tiles back
:end-at: show_all
The ``button-press-event`` signal of each button is connected to the application's ``on_clicked`` method so that we can handle clicks or touches on all of the cells in one place. Once all the cells have been filled, we update the window by showing the grid and all of its child widgets again.
The last part of the method involves setting up the game by hiding the treasure in one of the cells, resetting the counter holding the number of turns and its corresponding label, and resetting the flag indicating whether or not the player has won:
.. literalinclude:: ../app/src/main.py
:language: python3
:start-at: Hide the treasure
:end-at: self.won = False
A pair of methods used to update the labels in the window are implemented for convenience. They are interesting mostly because they use the convention of marking translatable strings as arguments to the underscore function:
.. literalinclude:: ../app/src/main.py
:language: python3
:start-at: def update_turns_label
:end-at: self.lowest_turns))
The underscore function ``_()`` is installed when the program executable is run -- we will see how this is done in the :ref:`executable_template` section.
Handling Clicks
~~~~~~~~~~~~~~~
The last method is the ``on_clicked`` method which handles the signals sent by buttons when they are clicked or touched by the player. If the player has already won or the cell they clicked is already revealed, we return immediately. Otherwise, we increase the number of turns and update the corresponding label.
.. literalinclude:: ../app/src/main.py
:language: python3
:start-at: on_clicked
:end-at: update_turns_label
The rest of the method handles the check for the treasure location, changing the button's image to an open treasure chest if the player guessed the location. The ``ngettext`` function is used to select the appropriate string to display for the current language. If the treasure was not in the cell that was clicked then we choose one of two images to display in the cell.
Main Function
-------------
We provide a ``main`` function to follow the convention used by GNOME Builder:
.. literalinclude:: ../app/src/main.py
:language: python3
:start-at: def main
:end-at: app.run
This is called by the executable that is run when the user launches the application. This is covered in the :ref:`executable_template` section.
.. include:: /links.txt
.. _`application property`: https://developer.gnome.org/gtk3/stable/GtkWindow.html#GtkWindow--application
.. _examples_Treasure_template_files:
Template Files
==============
When Meson configures the project, directives in the ``src/meson.build`` file cause it to read two template files, ``treasure.in`` and ``config.py.in``, fill in the placeholders inside them, and write the resulting ``treasure`` and ``config.py`` files to the build directory.
.. _executable_template:
Executable Template
-------------------
The ``treasure.in`` file contains a template that will be filled in by the build system, replacing placeholders with information about the application, its installation and its version. The resulting ``treasure`` file will be run when the user launches the application.
If you create a new application with GNOME Builder, the template will look very similar to this one. It begins with the line which determines which Python interpreter will be used to run the application:
.. literalinclude:: ../app/src/treasure.in
:language: python3
:start-at: #!@PYTHON@
:end-at: signal.signal
After importing the necessary modules, the template defines some strings that contain the version number and the locations of directories that will be needed by the application. The ``pkgdatadir`` path is used to ensure that the application's modules can be imported.
The message catalogs installed with the application -- see :ref:`examples_Treasure_po_dir` -- can be accessed using functions from the `Python gettext module`_ and are used to translate user-visible strings. We use functions from the `Python locale module`_ to set the translation domain to ensure that the UI files handled by ``Gtk.Builder`` are translated correctly:
.. literalinclude:: ../app/src/treasure.in
:language: python3
:start-at: Enable translation
:end-at: gettext.install
The ``gettext.install`` function installs the underscore ``_()`` function into the built-in namespace to make it convenient to access translations.
The standard check for the ``__main__`` namespace is used to execute code when the application is run:
.. literalinclude:: ../app/src/treasure.in
:language: python3
:start-at: if __name__
:end-at: _register
This begins by loading the application resource file and registering it with the application, making its contents available. Then, the main function in the ``main`` module is run:
.. literalinclude:: ../app/src/treasure.in
:language: python3
:start-at: from treasure
:end-at: exit
When the ``main`` function returns, the return value is used as the application's exit code.
Configuration Template
----------------------
The ``config.py.in`` file contains placeholders that will be filled in by the build system. The resulting ``config.py`` file will be installed with the other Python modules for the application and used to access application metadata, such as its installation location and version.
.. literalinclude:: ../app/src/config.py.in
:language: python3
By creating a module like this, we make it possible access this information from any other Python module in the application. This is similar to the ``config.vapi`` file that Vala applications use to obtain similar information.
.. include:: /links.txt
.. _`examples_Treasure_user_interface`:
User Interface
==============
The application's user interface is defined using two UI files: ``window.ui`` describes the application's window and ``menus.ui`` describes its app menu. The ``window.ui`` file was created using the `Glade`_ user interface design tool and ``menus.ui`` was written using a text editor.
Application Window
------------------
The window is a simple collection of widgets contained within a ``GtkApplicationWindow`` widget. At the top of the window is a HdyTitleBar_ from the `libhandy library`_ which contains a standard GtkHeaderBar_ with a title string and a GtkMenuButton_ that is used at run-time to let the user access the app menu.
.. figure:: images/window-ui.png
:alt: The application window as shown in Glade
:align: center
The application window as shown in Glade
Beneath the title bar is a GtkBox_ container that holds a GtkGrid_, where the game is played, and two GtkLabel_ widgets that are used to show scores and other messages.
The structure of the window is shown in the following figure:
.. figure:: images/window-ui-hierarchy.png
:alt: Widget hierarchy of the application window
:align: center
Widget hierarchy of the application window
This hierarchy is presented to the designer in the Glade user interface, and is convenient for selecting and navigating between widgets.
The `Glade Tutorials`_ page on the GNOME website provides links to tutorials that give instruction on how to use the Glade tool to construct user interfaces.
.. _`examples_Treasure_primary_menu`:
Primary Menu
------------
Unlike the user interface for the window, the menu's UI file was defined in a text editor and relates menu items to actions that the application's code can handle:
.. literalinclude:: ../app/src/ui/menus.ui
The actions are defined to be application-wide: each has the ``app.`` prefix. Their strings are marked as translatable so that they will be included in the message catalogs described in :ref:`examples_Treasure_po_dir`. Actions are described in more details in the `Actions section`_ of the `Python GTK+ 3 Tutorial`_.
.. include:: /links.txt
.. |project| replace:: https://source.puri.sm/librem5-examples
.. |repo| replace:: treasure.git
.. |project-repo| replace:: ``https://source.puri.sm/librem5-examples/treasure.git``
.. include:: /Apps/Examples/common/Getting_the_App.txt
Overview
========
.. toctree::
Overview/Source_Files
Overview/Data_Files
Overview/Translation_Files
Although the Treasure application is a simple game, it needs to provide some standard features so that it will run correctly in the phone environment. It also needs to include files containing metadata so that it can be packaged and installed properly. Much of this is taken care of by the tools you will use to create an application. In this section we will look at the directory structure of the example and explain how the application is put together.
The root directory of the application contains the following directories:
============= ===============================================================
Name Purpose
============= ===============================================================
``build-aux`` Helper scripts for configuring and building the application.
``data`` Resources such as icons, images and metadata files. |br|
See :ref:`examples_Treasure_data_dir` for a description of this
directory.
``docs`` Source files for the documentation.
``po`` Translation files for localization of the application at
run-time. |br| See :ref:`examples_Treasure_po_dir` for a
description of this directory.
``src`` Source code for the application, including executables and
modules. |br| See :ref:`examples_Treasure_src_dir` for a
description of this directory.
============= ===============================================================
It also contains the following files:
============================= ===================================================
Name Purpose
============================= ===================================================
``COPYING`` The license file for the application.
``Makefile`` Used for building the documentation.
``meson.build`` The main build file for the application.
``README.md`` A description and instructions for the application.
``com.example.treasure.json`` A Flatpak manifest file, used for distribution.
============================= ===================================================
Since the application uses the Meson build system for configuration and build tasks, we find a ``meson.build`` file in the main directory. Other ``meson.build`` files can also be found in subdirectories.
.. _`examples_Treasure_data_dir`:
Data Files
----------
The ``data`` directory contains files that are not included in the resource file that is used by the application. These files will be installed to standard locations in the filing system, such as ``/usr/share/icons`` and the application's own installation directory.
======================================= =========================================
Name Purpose
======================================= =========================================
``iconshicolor/scalable/apps`` A directory containing the icon for the
application.
``images`` A directory containing images used in
the application.
``meson.build`` The build file for the ``data``
directory.
``com.example.treasure.appdata.xml.in`` An `AppStream`_ metadata file.
``com.example.treasure.desktop.in`` An `Desktop Entry`_ file.
``com.example.treasure.gschema.xml`` A `GSettings`_ schema file.
======================================= =========================================
The ``icons`` directory in this example contains a deeply-nested set of directories with a ``treasure.svg`` `Scalable Vector Graphics`_ (SVG) file inside them. This reflects the structure of the directories where application icons are stored by the system but, for simple applications like this one, the icon could be kept in the ``data`` directory itself. The ``meson.build`` file contains a rule for installing this directory structure in the correct place.
The ``images`` directory contains the images that the application uses for the tiles in the game. Instead of handling each SVG individually, the ``meson.build`` file contains a rule for installing the whole ``images`` directory as a subdirectory of the application's installation directory.
Additional Files
~~~~~~~~~~~~~~~~
There are three additional files that appear in the ``data`` directory, two of which are templates that will be processed to produce files that can be installed.
The ``com.example.treasure.appdata.xml.in`` file is a template of an `AppStream`_ file, containing metadata including the license of the application and a short piece of text describing what it does:
.. literalinclude:: ../app/data/com.example.treasure.appdata.xml.in
The ``com.example.treasure.desktop.in`` file is a template of a `Desktop Entry`_ file that application launchers can use to show information about the application, including its name, icon and the name of its executable file:
.. literalinclude:: ../app/data/com.example.treasure.desktop.in
The ``com.example.treasure.gschema.xml`` file is a `GSettings`_ schema file that contains information about persistent settings that the application will use, either for configuration purposes or as a way of retaining state when it is closed. This application simply uses it to keep a permanent record of the best score obtained:
.. literalinclude:: ../app/data/com.example.treasure.gschema.xml
Unlike the other two files, this file will be installed without any changes being made to it by the build system.
.. include:: /links.txt
.. _`examples_Treasure_src_dir`:
Source Files
------------
The code that describes the application's behavior is stored in the ``src`` directory. This typically contains files that are generated by an IDE (such as GNOME Builder) when the project is created and those that the developer creates. Since this example is written in Python, these files are all ``.py`` files except for the ``treasure.in`` file, which is a template for the script that will be installed as the application's executable, and the ``config.py.in`` file, which is a template Python module. Both ``.in`` files will be processed by the build system to create valid Python scripts.
========================== =====================================================
Name Purpose
========================== =====================================================
``__init__.py`` The package file for the application's code.
``config.py.in`` A template Python module containing information |br|
about the installation that the application can |br|
query at run-time.
``main.py`` The main program.
``meson.build`` The build file for the ``src`` directory.
``treasure.gresource.xml`` A resource description file describing the files |br|
that will be included in a binary resource bundle.
``treasure.in`` A template for the main executable file.
========================== =====================================================
The ``treasure.gresource.xml`` file contains references to files that will be compiled into a resource bundle. Using a bundle is a convenient way to include fairly small resources that the application depends on, and is most often used to include user interface (UI) files for menus and windows.
The user interface files are included in the ``src/ui`` subdirectory. We will look at these in more detail in :ref:`examples_Treasure_user_interface`.
.. _`examples_Treasure_po_dir`:
Translations Directory
----------------------
The ``po`` directory holds files related to translations of the application into different languages. It also contains files that help the build system keep the translations up to date.
=================== ==========================================================
Name Purpose
=================== ==========================================================
``treasure.pot`` POT file containing the translatable strings for the
application.
``meson.build`` The build file for the ``po`` directory.
``POTFILES`` Contains a list of files to scan for translatable strings.
``LINGUAS`` Contains a list of language codes corresponding to the
PO files that should be processed for use in the
application.
``en_GB.po`` A sample PO translation file for British English.
=================== ==========================================================
The ``treasure.pot`` file is a Portable Object Template (POT) file that contains the user-visible strings for the application. Strings are obtained from the files listed in the ``POTFILES`` file, which includes source and UI files.
Translations of the strings are stored as message catalogs in Portable Object (PO) files for each language. These files are compiled to Machine Object (MO) files by the build system if the languages they represent are listed in the ``LINGUAS`` file. For example, the ``en_GB.po`` file will be processed because it is listed as ``en_GB`` in the ``LINGUAS`` file.
The ``treasure.pot`` file is generated when the ``meson-treasure-pot`` build rule is run::
ninja -C _build meson-treasure-pot
Run this rule whenever you update the application with new user-visible strings that will need to be translated. Then run the ``meson-treasure-update-po`` rule to update the PO files::
ninja -C _build meson-treasure-update-po
The strings in each PO file can then be checked and translated to the appropriate language. When the application is built these are automatically compiled to MO files. When the application is installed these are copied to the appropriate directories on the system.
.. include:: /links.txt
.. |manifest| replace:: ``com.example.treasure.json``
.. |manifest-path| replace:: app/com.example.treasure.json
.. |app-id| replace:: com.example.treasure
.. include:: ../../common/Packaging_the_App.txt
Subproject commit 1f8bafbce3a827c624d00d44121ffbf076ec7bd0
.. _examples_Treasure:
Treasure
========
This example shows how to write a simple application in Python, using the `PyGObject`_ bindings to create the user interface. It also demonstrates how to provide support for most of the things that applications will be expected to provide, such as translations, an application icon, resources and other metadata files.
.. image:: images/screenshot.png
:scale: 50%
:align: center
:alt: A screenshot of the application running in the phone environment
The documentation for this example describes the application in some detail. The :ref:`app_development_tutorials` cover some of the same concepts for simpler applications, so you may find it useful to refer to those when reading the following sections.
.. toctree::
:maxdepth: 2
:includehidden:
Getting_the_App
Overview
Details
Building_the_App
Packaging_the_App
.. include:: /links.txt
.. |main-file| replace:: ``main.py``
.. |executable| replace:: ``network-state``
.. |desktop-entry-in| replace:: ``com.example.network_state.desktop``
.. |desktop-entry| replace:: ``com.example.network_state.desktop``
.. |desktop-entry-ref| replace:: desktop entry file
.. |svg-file| replace:: ``com.example.network_state.svg``
.. include:: /Apps/Examples/common/Building_the_App.txt
......@@ -36,7 +36,7 @@ files are processed when the build occurs:
.. literalinclude:: app/src/meson.build
:language: python3
In this case, we instruct Meson to take the ``main.py`` file in the ``src``
In this case, we instruct Meson to take the |main-file| file in the ``src``
directory and copy it into the build directory as |executable| --
this is the name given as the executable in the |desktop-entry-ref|.
......@@ -52,12 +52,12 @@ processed when the build occurs:
.. literalinclude:: app/data/meson.build
:language: python3
Here, we tell Meson to simply copy the ``com.example.first_application.desktop``
file into the build directory. We also declare that it should be installed, and
Here, we tell Meson to copy the |desktop-entry-in| file into the build directory
as |desktop-entry|. We also declare that it should be installed, and
that its installation directory is the ``applications`` subdirectory of the
system location for data files (``datadir``).
The ``com.example.first_application.svg`` file is more easily described to
The |svg-file| file is more easily described to
Meson. It will be installed in the appropriate subdirectory of the system
location for data files that is used for icons.
......
......@@ -19,6 +19,7 @@ development tools and libraries.
.. toctree::
:maxdepth: 1
General/Treasure/index
Networking/NetworkState/index
.. include:: /links.txt
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment