Source Files

Most of 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.

Much of the is very similar to other examples and tutorials. We will focus on the parts that are specific to this example.

Relevant Modules

Besides standard Python modules such as os and sys, the Handy module helps us to create adaptive user interfaces. This module is imported in the same way as the Gtk module:

import os
import sys
import gi

gi.require_version('GdkPixbuf', '2.0')
gi.require_version('Gtk', '3.0')
from gi.repository import GdkPixbuf, GLib, Gtk

gi.require_version('Handy', '0.0')
from gi.repository import Handy
Handy.init()

from .widgets import Pages

The widgets module contains a helper class that we won’t cover in any detail.

Setting up the User Interface

The Application class provides the usual methods to set up the application and perform tasks when it is run.

In the do_startup method we define two parameters for the thumbnail dimensions:

    def do_startup(self):
        Gtk.Application.do_startup(self)

        self.thumbnail_width = 160
        self.thumbnail_height = 160

These are hard-coded in this example, but more complex applications would load these values from the application’s settings.

In the do_activate method we set up the user interface, using a helper class to set up an adaptive user interface consisting of two leaflets: one in the window’s header bar, the other in the main area of the window:

    def do_activate(self):
        window = Gtk.ApplicationWindow(application=self)
        window.set_icon_name('com.example.pictures')

        title_bar = Handy.TitleBar()
        window.set_titlebar(title_bar)

        self.pages = Pages(window, title_bar, 2)
        self.pages.add_page(self.create_thumbnails_page(),
                            title=_('Pictures'))
        self.pages.add_page(self.create_details_page())

        window.set_default_size(320, 512)
        window.show_all()

The leaflet in the main area holds two pages: one with a list of thumbnails, the other with a simple image viewer.

The two pages of the application side by side

The two pages of the application side by side

For the first page we use a Gtk.ScrolledWindow widget to provide a scrolling list of thumbnails. The thumbnails are held by a Gtk.ListStore object that we create, specifying the data types it holds: a Pixbuf and a string that holds the file name of the image:

    def create_thumbnails_page(self):

        page = Gtk.ScrolledWindow(
            halign='center',
            kinetic_scrolling=True,
            min_content_width=self.thumbnail_width * 2
        )

        self.model = Gtk.ListStore(GdkPixbuf.Pixbuf, str)
        self.load_thumbnails()

We populate the model by calling the load_thumbnails method which we describe later.

The thumbnails are displayed by a Gtk.IconView widget, using the model as a data source, and mapping the fields in the model to columns in the view.

        self.view = Gtk.IconView(
            activate_on_single_click=True,
            columns=1,
            item_width=self.thumbnail_width,
            model=self.model,
            selection_mode=Gtk.SelectionMode.BROWSE
        )
        self.view.set_pixbuf_column(0)
        self.view.set_text_column(1)
        self.view.connect('item-activated', self.show_details)
        page.add(self.view)

        return page

We also connect the item-activated signal to the show_details method to respond when the user clicks or touches a thumbnail.

The second page is also a Gtk.ScrolledWindow widget, but only contains a single Gtk.Image widget that initially contains the application’s own icon:

    def create_details_page(self):

        page = Gtk.ScrolledWindow(
            hexpand=True,
            kinetic_scrolling=True,
            vexpand=True
        )

        self.image = Gtk.Image.new_from_icon_name('com.example.pictures',
                                                  Gtk.IconSize.DIALOG)
        page.add(self.image)

        return page

As for the first page, we also return the widget that represents the page.

Loading and Displaying Images

The load_thumbnails method begins by locating the user’s Pictures directory:

    def load_thumbnails(self):

        pictures_dir = GLib.get_user_special_dir(
            GLib.UserDirectory.DIRECTORY_PICTURES)

As described in the Files section of the Settings, User Data and Files guide, the GLib.get_user_special_dir function is used to obtain the path to the Pictures directory, specified using the DIRECTORY_PICTURES constant.

We iterate over the files in the directory, loading each of them at the size required for the thumbnails, and we add them to the model created in the create_thumbnails_page method:

        for name in os.listdir(pictures_dir):

            try:
                pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(
                    os.path.join(pictures_dir, name),
                    self.thumbnail_width,
                    self.thumbnail_height,
                    True
                )
                if pixbuf:
                    self.model.append([pixbuf, name])

            except GLib.Error:
                pass

Each thumbnail is added as a list of fields with types that correspond to the ones we specified when we created the Gtk.ListStore model.

The show_details method loads an image at its full size for display in the details page:

    def show_details(self, view, tree_path):

        pictures_dir = GLib.get_user_special_dir(
            GLib.UserDirectory.DIRECTORY_PICTURES)

        tree_iter = self.model.get_iter(tree_path)
        name = self.model.get_value(tree_iter, 1)

        try:
            pixbuf = GdkPixbuf.Pixbuf.new_from_file(
                os.path.join(pictures_dir, name)
            )

            if pixbuf:
                self.image.set_from_pixbuf(pixbuf)

        except GLib.Error:
            pass

        # Show the details page and corresponding header.
        self.pages.show_page(1, name)

We use the Gtk.TreePath passed to this method, along with a Gtk.TreeIter object, to obtain the file name of the image from the model. A good introduction to this class is provided by the Tree and List Widgets chapter of the Python GTK 3 Tutorial.

Summary

You can access files in specific directories in the user’s home directory by calling the GLib.get_user_special_dir function to obtain the file paths you require. The directory you want to access is specified using a value from the GLib.UserDirectory enum.

In this case we use DIRECTORY_PICTURES to access the user’s Pictures directory and load images using the GdkPixbuf.Pixbuf class, using the new_from_file_at_scale method for thumbnails and the new_from_file method for full size images.

Note that the load_thumbnails method will only return once all the images have been loaded. If the user has a large number of images in their Pictures directory then the user interface will be unresponsive when the application starts. We could use a background thread or some kind of lazy loading mechanism to make image loading appear quicker.