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