Skip to main content

File chooser dialog

FileChooserDialog

The GtkFileChooserDialog is a subclass of GtkDialog (see diaogue article) and provides opening and saving of files and folders.

/images/16_fcd.thumbnail.png

Glade

The dialog can be added from the "Toplevel" section of the widget sidebar. In addition to the file browser itself the widget has an intern GtkBox for additional widgets and a GtkButtonBox as "action area" for buttons.

First the Gtk.FileChooserAction mode must be defined (see Python GI API Reference): open or save file, choose or create a folder.

Action area and Responses

The response signal is emitted on widget interaction in the action area which also passes the response value. So for these widgets there is no need to activate the clicked signal of buttons.

By default the "action area" is generated beneath the file browser area.

/images/16_fcd_glade.thumbnail.png

If the FileChooserDialog is used without Glade (see below) the buttons are created in the headerbar. This seems to be standard procedure because Glade generated dialogs induce the warning

Gtk-WARNING **: Content added to the action area of a dialog using header bars

This message is not shown if buttons are not added to the intern action area.

If a headerbar with buttons is created in Glade the buttons cannot be assigned to a response value.

There may be several solutions to the problem:

XML file

After creating a headerbar with button(s) the Glade file is opened in a text editor and add line(s) to the <action-widgets> element:

<object class="GtkFileChooserDialog" id="filechooser_dialog">
  <property ... ></property>
  <property ... ></property>
  <!-- ... -->
  <action-widgets>
    <!-- Buttons innerhalb der action area -->
    <action-widget response="0">button1</action-widget>
    <action-widget response="1">button2</action-widget>
    <!-- Button in Headerbar -->
    <action-widget response="-1">hb_button</action-widget>
  </action-widgets>
  <!-- ... -->
</object>

This works but this procedure is surely not the intended way to deal with the problem because after altering the Glade file the edit is retracted.

add_action_widget function

The add_action_widget adds activatable widgets to the action area and hold a response value. This includes widgets of the Gtk.Activatable class: Buttons, MenuItem, RecentChooserMenu, Switch and ToolItem.

The scheme for creating a button is

widget.add_action_widget(button,response)

The widget property "can-default" of the button must be activated:

button.set_property("can-default",True)

In the example the standard buttons "apply/cancel" are added to the file dialog:

button = Gtk.Button.new_from_stock(Gtk.STOCK_CANCEL)
button.set_property("can-default",True)
self.obj("filechooser_dialog").add_action_widget(button, Gtk.ResponseType.CANCEL)
button = Gtk.Button.new_from_stock(Gtk.STOCK_APPLY)
button.set_property("can-default",True)
self.obj("filechooser_dialog").add_action_widget(button, Gtk.ResponseType.OK)

To apply file selection on doubleclick the file-activated is also required in addition to the response signal.

Preview widget

The dialogue can contain an optional preview widget. To use it activate "Preview Widget Active" and choose a free widget (p.e. a GtkImage). It may be necessary to create the preview widget in an empty container widget and pull it into a free area.

If the preview requires a refresh the update-preview signal is emitted.

FileFilter

Files can be filtered according to certain criteria by using FileFilter. There can be defined several (shell style glob) patterns or MIME-types for each filter.

In Glade filters can be found in the widget sidebar in the "Miscellaneous" group. A filter for a dialog can be selected in the general widget properties. This corresponds to the set_filter function.

Python

Dialog without Glade

The FileChooserDialog is a complex but also easy to use graphic interface item. Realizing the dialog without Glade also avoids the headerbar problem discussed above. Creating a dialog follows the scheme

dialog = Gtk.FileChooserDialog("window title",
                               parent_window,
                               file_chooser_action,
                               (button1,response1,
                               button2,response2))

The dialog then can be directly run and processed:

response = dialog.run()
if response == response1:
    ...
elif response == response2:
    ...
dialog.destroy()

FileFilter

There are two possibilities to apply a FileFilter:

  1. No user choice. The applied filter is preset:

dialog.set_filter(filter)
  1. Selection per dropdown menu. The user can choose between different defined filters:

dialog.add_filter(filter1)
dialog.add_filter(filter2)
...

Listings

Glade

16_filechooser.glade (Source)

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.0 -->
<interface>
  <requires lib="gtk+" version="3.20"/>
  <object class="GtkFileFilter" id="filefilter">
    <mime-types>
      <mime-type>image/*</mime-type>
    </mime-types>
  </object>
  <object class="GtkFileFilter" id="jpg_filter">
    <mime-types>
      <mime-type>image/jpeg</mime-type>
    </mime-types>
  </object>
  <object class="GtkFileFilter" id="png_filter">
    <mime-types>
      <mime-type>image/png</mime-type>
    </mime-types>
  </object>
  <object class="GtkImage" id="preview">
    <property name="width_request">200</property>
    <property name="visible">True</property>
    <property name="can_focus">False</property>
    <property name="margin_right">5</property>
  </object>
  <object class="GtkApplicationWindow" id="window">
    <property name="width_request">300</property>
    <property name="height_request">200</property>
    <property name="can_focus">False</property>
    <signal name="destroy" handler="on_window_destroy" swapped="no"/>
    <child>
      <object class="GtkBox">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="orientation">vertical</property>
        <property name="homogeneous">True</property>
        <child>
          <object class="GtkButton" id="file_button">
            <property name="label" translatable="yes">Choose an image file...</property>
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">True</property>
            <signal name="clicked" handler="on_file_button_clicked" swapped="no"/>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">0</property>
          </packing>
        </child>
        <child>
          <object class="GtkButton" id="dir_button">
            <property name="label" translatable="yes">Choose folder...</property>
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">True</property>
            <signal name="clicked" handler="on_dir_button_clicked" swapped="no"/>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">1</property>
          </packing>
        </child>
      </object>
    </child>
    <child type="titlebar">
      <placeholder/>
    </child>
  </object>
  <object class="GtkFileChooserDialog" id="filechooser_dialog">
    <property name="width_request">800</property>
    <property name="height_request">500</property>
    <property name="can_focus">False</property>
    <property name="type_hint">dialog</property>
    <property name="transient_for">window</property>
    <property name="attached_to">window</property>
    <property name="preview_widget">preview</property>
    <property name="use_preview_label">False</property>
    <signal name="delete-event" handler="on_dialog_close" swapped="no"/>
    <signal name="file-activated" handler="on_filechooser_dialog_file_activated" swapped="no"/>
    <signal name="response" handler="on_filechooser_dialog_response" swapped="no"/>
    <signal name="update-preview" handler="on_filechooser_dialog_update_preview" swapped="no"/>
    <child internal-child="vbox">
      <object class="GtkBox" id="fcbox">
        <property name="can_focus">False</property>
        <property name="orientation">vertical</property>
        <child internal-child="action_area">
          <object class="GtkButtonBox">
            <property name="can_focus">False</property>
            <child>
              <object class="GtkButton" id="button2">
                <property name="label">gtk-cancel</property>
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="receives_default">True</property>
                <property name="use_stock">True</property>
              </object>
              <packing>
                <property name="expand">True</property>
                <property name="fill">True</property>
                <property name="position">2</property>
              </packing>
            </child>
            <child>
              <object class="GtkButton" id="button1">
                <property name="label">gtk-apply</property>
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="receives_default">True</property>
                <property name="use_stock">True</property>
              </object>
              <packing>
                <property name="expand">True</property>
                <property name="fill">True</property>
                <property name="position">3</property>
              </packing>
            </child>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">False</property>
            <property name="position">0</property>
          </packing>
        </child>
      </object>
    </child>
    <action-widgets>
      <action-widget response="-6">button2</action-widget>
      <action-widget response="-5">button1</action-widget>
    </action-widgets>
    <child type="titlebar">
      <object class="GtkHeaderBar">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="title">Choose image...</property>
        <property name="show_close_button">True</property>
      </object>
    </child>
    <action-widgets>
      <action-widget response="-6">button2</action-widget>
      <action-widget response="-5">button1</action-widget>
    </action-widgets>
  </object>
</interface>

Python

16_filechooser.py (Source)

#!/usr/bin/python
# -*- coding: utf-8 -*-

import os
import sys

import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, Gio, GdkPixbuf


class Handler:

    def on_window_destroy(self, window):
        window.close()

    def on_dialog_close(self, widget, *event):
        widget.hide_on_delete()
        return True

    def on_filechooser_dialog_response(self, widget, response):
        if response == -6:
            print("Cancel")
        elif response == -5:
            print("File selection: {}".format(widget.get_filename()))
        self.on_dialog_close(widget)

    def on_filechooser_dialog_file_activated(self, widget):
        self.on_filechooser_dialog_response(widget, -5)

    def on_filechooser_dialog_update_preview(self, widget):
        if widget.get_filename() != None and os.path.isfile(widget.get_filename()):
            pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(widget.get_filename(),
                                                             200,
                                                             200,
                                                             True,
                                                            )
            app.obj("preview").set_from_pixbuf(pixbuf)

    def on_file_button_clicked(self,widget):
        app.obj("filechooser_dialog").show_all()

    def on_dir_button_clicked(self,widget):
        dialog = Gtk.FileChooserDialog(title="Choose a folder",
                                       parent=app.obj("window"),
                                       action=Gtk.FileChooserAction.SELECT_FOLDER,
                                       )
        dialog.set_default_size(600, 300)
        dialog.add_buttons("Cancel", Gtk.ResponseType.CANCEL,
                           "OK", Gtk.ResponseType.OK)

        response = dialog.run()
        if response == Gtk.ResponseType.OK:
            print("Folder selection: {}".format(dialog.get_filename()))
        elif response == Gtk.ResponseType.CANCEL:
            print("Cancel")

        dialog.destroy()

class ExampleApp:

    def __init__(self):

        self.app = Gtk.Application.new("org.application.test", Gio.ApplicationFlags(0))
        self.app.connect("activate", self.on_app_activate)
        self.app.connect("shutdown", self.on_app_shutdown)

    def on_app_activate(self, app):
        builder = Gtk.Builder()
        builder.add_from_file("16_filechooser.glade")
        builder.connect_signals(Handler())

        self.obj = builder.get_object
        self.obj("window").set_application(app)
        self.obj("window").show_all()

        #add filters to filechooser dialog
        self.obj("filefilter").set_name("Image files")
        self.obj("filechooser_dialog").add_filter(self.obj("filefilter"))
        self.obj("png_filter").set_name("PNG files")
        self.obj("filechooser_dialog").add_filter(self.obj("png_filter"))
        self.obj("jpg_filter").set_name("JPG files")
        self.obj("filechooser_dialog").add_filter(self.obj("jpg_filter"))

        #add buttons to headerbar of Glade generated dialog
        button = Gtk.Button.new_with_label("Cancel")
        button.set_property("can-default", True)
        self.obj("filechooser_dialog").add_action_widget(button, Gtk.ResponseType.CANCEL)
        button = Gtk.Button.new_with_label("OK")
        button.set_property("can-default", True)
        self.obj("filechooser_dialog").add_action_widget(button, Gtk.ResponseType.OK)

    def on_app_shutdown(self, app):
        self.app.quit()

    def run(self, argv):
        self.app.run(argv)


app = ExampleApp()
app.run(sys.argv)

Desktop integration

Desktop integration: icon, headerbar, commndline options

(Continuation if the GtkApplication article)

/images/15_application.thumbnail.png

Glade

Icon

To assign an icon to an window just select "General Appearance > Icon File". Problematic here is that Glade only shows image files located in the same folder as the Glade file even if an image from another folder is chosen.

A simple solution is editing the Glade file in a text editor and add the relative path to the icon. This edit is preserved even when changing and saving the file with Glade again:

<object class="GtkApplicationWindow" id="window">
  ...
  <!-- <property name="icon">duckyou.svg</property> -->
  <property name="icon">../files/duckyou.svg</property>
  ...

Headerbar

Headerbars were introduced in GNOME 3.10 and unite titlebar and toolbar. Besides title and subtitle there is room for widgets such as buttons or menus and client side window controls.

A headerbar is optional. To make use of it "General > Appearance > Client side window decorations" has to be activated if not set yet. This prepares a reserved container area in the upper window area to add the headerbar widget in. If a headerbar is placed out of this specific area a regular titlebar is generated in addition to the headerbar.

/images/15_headerbarglade.png

Commandline options

GtkApplication provides functions to define individual commandline options of the applications (Handling command line options in GApplication).

Create Options

Options are added by the add_main_option_entries(entrylist) function. The entries must be GLib.OptionEntry formatted which requires a bunch of parameters.

def __init__(self):
    self.app = Gtk.Application.new("org.application.test", Gio.ApplicationFlags(0))
    self.app.add_main_option_entries([
        self.create_option_entry("--version", description="Show version numbers and exit"),
        self.create_option_entry("--setlabel", description="Set label widget", arg=GLib.OptionArg.STRING,),
        self.create_option_entry("--bollocks", description="Additional test option - exit"),
    ])

def create_option_entry(self,
                        long_name,
                        short_name=None,
                        flags=0,
                        arg=GLib.OptionArg.NONE,
                        arg_data=None,
                        description=None,
                        arg_description=None):
    option = GLib.OptionEntry()
    option.long_name = long_name.lstrip('-')
    option.short_name = 0 if not short_name else ord(short_name.lstrip('-'))
    option.flags = flags
    option.arg = arg
    option.arg_data = arg_data
    option.description = description
    option.arg_description = arg_description
    return option

Short names

An option can have a one character synonym ("a printable ASCII character different from ‘-‘" to be precise), the short name. Lokking at the option --help this commonly is -h.

The short_name variable of OptionEntry ist surprisingly integer. The not very obvious solution here is to pass the character's decimal code, p.e. 97 for "a". An error message will be thrown when trying to pass invalid numbers. Options without a short code get a value of 0.

Connect signal

The "handle-local-options" signal of Gtk.Application handles commandline options. If the signal is connected the signal is emitted before the "startup" signal.

self.app.connect("handle-local-options", self.on_local_option)

Processing options

The option will be passed as an element of the GLib.VariantDict class which can be searched for by calling contains("option"):

def on_local_option(self, app, option):
    if option.contains("option1"):
        #do something and exit normally
        return 0
    elif option.contains("option2"):
        #do something different and exit
        return 0
    elif option.contains("option3"):
        #do more and continue
    return -1

A string can be extracted by calling end() which converts GLib.VariantDict to a GLib.Variant element. That GLib.Variant then can be culled by calling keys():

var = GLib.VariantDict.end(option)
option_string = var[var.keys()[0]]

The handler function demands a return value that corresponds to the exit status:

  • -1: application execution will be continued

  • 0: execution successful, application will be quit, "startup/activate" will not be emitted

  • 1 or positive value: execution was not successful, application will be quit

Run application with options

The option --help is always available and lists all defined options of the application and their descriptions.

The options of the example file now can be executed:

$ python script.py --version
Python: 3.6.0
GTK+:   3.22.6

or pass a string to the application's Gtk.Label:

$ python script.py --setlabel "I can haz options!"

Listings

Glade

15_application.glade (Source)

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.0 -->
<interface>
  <requires lib="gtk+" version="3.20"/>
  <object class="GtkMenu" id="menu">
    <property name="visible">True</property>
    <property name="can_focus">False</property>
    <child>
      <object class="GtkMenuItem">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="label" translatable="yes">MenuItem 1</property>
        <property name="use_underline">True</property>
      </object>
    </child>
    <child>
      <object class="GtkMenuItem">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="label" translatable="yes">MenuItem 2</property>
        <property name="use_underline">True</property>
      </object>
    </child>
    <child>
      <object class="GtkMenuItem">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="label" translatable="yes">MenuItem 3</property>
        <property name="use_underline">True</property>
      </object>
    </child>
  </object>
  <object class="GtkApplicationWindow" id="window">
    <property name="width_request">400</property>
    <property name="height_request">300</property>
    <property name="can_focus">False</property>
    <property name="title" translatable="yes">Titel</property>
    <property name="icon">../files/duckyou.svg</property>
    <property name="show_menubar">False</property>
    <child>
      <object class="GtkBox">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="orientation">vertical</property>
        <child>
          <object class="GtkImage">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="pixbuf">../files/duckyou.svg</property>
          </object>
          <packing>
            <property name="expand">True</property>
            <property name="fill">True</property>
            <property name="position">0</property>
          </packing>
        </child>
        <child>
          <object class="GtkLabel" id="label">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="pack_type">end</property>
            <property name="position">1</property>
          </packing>
        </child>
        <child>
          <object class="GtkSeparator">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">2</property>
          </packing>
        </child>
      </object>
    </child>
    <child type="titlebar">
      <object class="GtkHeaderBar">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="title">Titel</property>
        <property name="subtitle">Untertitel</property>
        <property name="show_close_button">True</property>
        <child>
          <object class="GtkMenuButton">
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">True</property>
            <property name="popup">menu</property>
            <child>
              <placeholder/>
            </child>
          </object>
        </child>
        <child>
          <object class="GtkButton">
            <property name="label">gtk-no</property>
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">True</property>
            <property name="use_stock">True</property>
            <property name="always_show_image">True</property>
          </object>
          <packing>
            <property name="pack_type">end</property>
            <property name="position">2</property>
          </packing>
        </child>
        <child>
          <object class="GtkButton">
            <property name="label">gtk-yes</property>
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">True</property>
            <property name="use_stock">True</property>
            <property name="always_show_image">True</property>
          </object>
          <packing>
            <property name="pack_type">end</property>
            <property name="position">2</property>
          </packing>
        </child>
      </object>
    </child>
  </object>
</interface>

Python

15_application.py (Source)

#!/usr/bin/python
# -*- coding: utf-8 -*-

import sys
import setproctitle

import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, Gio, GLib


class ExampleApp:

    def __init__(self):

        setproctitle.setproctitle("Application test")
        GLib.set_prgname("testapp")

        self.app = Gtk.Application.new("org.application.test", Gio.ApplicationFlags(0))

        self.app.add_main_option_entries([
            self.create_option_entry("--version",
                                     description="Show version numbers and exit",
                                    ),
            self.create_option_entry("--setlabel",
                                     description="Set label widget",
                                     arg=GLib.OptionArg.STRING,
                                    ),
            self.create_option_entry("--bollocks",
                                     description="Additional test option - exit",
                                    ),
        ])

        self.app.connect("handle-local-options", self.on_local_option)
        self.app.connect("activate", self.on_app_activate)

    def on_local_option(self, app, option):
        self.option_string = ""
        if option.contains("version"):
            var = GLib.VariantDict.end(option)
            print("Python: {}".format(sys.version[:5]))
            print("GTK+:   {}.{}.{}".format(Gtk.MAJOR_VERSION,
                                             Gtk.MINOR_VERSION,
                                             Gtk.MICRO_VERSION,
                                             ))
            return 0
        elif option.contains("bollocks"):
            return 1
        elif option.contains("setlabel"):
            var = GLib.VariantDict.end(option)
            self.option_string = var[var.keys()[0]]
        return -1

    def create_option_entry(self,
                            long_name,
                            short_name=None,
                            flags=0,
                            arg=GLib.OptionArg.NONE,
                            arg_data=None,
                            description=None,
                            arg_description=None,
                            ):
        option = GLib.OptionEntry()
        option.long_name = long_name.lstrip("-")
        option.short_name = 0 if not short_name else ord(short_name.lstrip("-"))
        option.flags = flags
        option.arg = arg
        option.arg_data = arg_data
        option.description = description
        option.arg_description = arg_description
        return option

    def on_app_activate(self, app):
        builder = Gtk.Builder()
        builder.add_from_file("15_application.glade")

        self.obj = builder.get_object
        self.obj("window").set_application(app)
        self.obj("label").set_text(self.option_string)
        self.obj("window").show_all()

    def run(self, argv):
        self.app.run(argv)


app = ExampleApp()
app.run(sys.argv)

Stand-alone

Run program as GtkApplication

GtkApplication handles different important aspects of a GTK+ application like GTK+ initialization, session management and desktop integration.

/images/14_application.thumbnail.png

XML files

Glade

Glade is used as usual, main windows should be Gtk.ApplicationWindows. As example the file from the dialogue article is reused.

GMenu

The GNOME Shell supports appmenus residing in the (top) panel. The Gmenu XML file must be formatted to be recognized as GioMenu:

<?xml version="1.0"?>
<interface>
 <menu id="appmenu">
    <section>
      <item>
        <attribute name="label" translatable="yes">Menu Item</attribute>
        <attribute name="action">app.item</attribute>
      </item>
    </section>
  </menu>
</interface>

Glade identifies this file format as an outdated Glade file and refuses to open it. Apart from that GtkBuilder can handle the menu and address identifiers.

Python

Initialize GtkApplication

The initialization process requires the parameters application_id and flags. Flags can normally set to 0 being the same as FLAGS_NONE (see Gio.ApplicationFlags), naming conventions for application_id are listed here.

The application can be connected to different signals being emitted on preassigned events. It is mandatory to at least define an activate signal:

def __init__(self):

    self.app = Gtk.Application.new("org.application.test", 0)
    #self.app.connect("startup", self.on_app_startup) #optional
    self.app.connect("activate", self.on_app_activate)
    #self.app.connect("shutdown", self.on_app_shutdown) #optional

def on_app_activate(self, app):

    #setting up GtkBuilder etc.
    ...
    ...
    ...

Appmenu

GMenu XML files are loaded by GtkBuilder:

builder.add_from_file("menu.ui")
app.set_app_menu(builder.get_object("appmenu"))

Menu entries now have to be connected to actions which are created as GioSimpleActions:

def add_simple_action(self, name, callback):
    action = Gio.SimpleAction.new(name)
    action.connect("activate", callback)
    self.app.add_action(action)

In the example file actions invokes dialog windows.

Start and quit

GtkApplication takes over the handling of the GTK+ mainloop so there is no need of starting and quitting GTK+ manually and run() and quit() called instead:

Gtk.main()      ->  app.run(argv)
Gtk.main_quit() ->  app.quit()

If the application is quit by the [X] button or the "Quit" appmenu entry the "shutdown" signal is emitted (see above) and the program is terminated. That means there is no need to define these signals like in previous examples using GtkWindow. The "shutdown" also works even if the signal is not explicitly connected to a function during the initialization process.

Listings

Glade

13_dialoge.glade (Source)

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1 -->
<interface>
  <requires lib="gtk+" version="3.20"/>
  <object class="GtkApplicationWindow" id="window">
    <property name="can_focus">False</property>
    <property name="title" translatable="yes">Titel</property>
    <signal name="destroy" handler="on_window_destroy" swapped="no"/>
    <child type="titlebar">
      <placeholder/>
    </child>
    <child>
      <object class="GtkBox">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="orientation">vertical</property>
        <child>
          <object class="GtkButton" id="aboutbutton">
            <property name="label" translatable="yes">GtkAboutDialog</property>
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">True</property>
            <signal name="clicked" handler="on_aboutbutton_clicked" swapped="no"/>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">0</property>
          </packing>
        </child>
        <child>
          <object class="GtkButton" id="messagebutton">
            <property name="label" translatable="yes">GtkMessageDialog</property>
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">True</property>
            <signal name="clicked" handler="on_messagebutton_clicked" swapped="no"/>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">1</property>
          </packing>
        </child>
      </object>
    </child>
  </object>
  <object class="GtkAboutDialog" id="aboutdialog">
    <property name="can_focus">False</property>
    <property name="type_hint">dialog</property>
    <property name="transient_for">window</property>
    <property name="attached_to">window</property>
    <property name="program_name">Glade-Tutorial</property>
    <property name="version">1.0</property>
    <property name="logo_icon_name">help-about</property>
    <property name="license_type">mit-x11</property>
    <signal name="delete-event" handler="on_dialog_delete_event" swapped="no"/>
    <signal name="response" handler="on_dialog_response" swapped="no"/>
    <child type="titlebar">
      <placeholder/>
    </child>
    <child internal-child="vbox">
      <object class="GtkBox">
        <property name="can_focus">False</property>
        <property name="orientation">vertical</property>
        <property name="spacing">2</property>
        <child internal-child="action_area">
          <object class="GtkButtonBox">
            <property name="can_focus">False</property>
            <property name="layout_style">end</property>
            <child>
              <object class="GtkButton" id="button1">
                <property name="label">gtk-ok</property>
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="receives_default">True</property>
                <property name="use_stock">True</property>
              </object>
              <packing>
                <property name="expand">True</property>
                <property name="fill">True</property>
                <property name="position">1</property>
              </packing>
            </child>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">False</property>
            <property name="position">0</property>
          </packing>
        </child>
      </object>
    </child>
    <action-widgets>
      <action-widget response="-8">button1</action-widget>
    </action-widgets>
  </object>
  <object class="GtkMessageDialog" id="messdialog">
    <property name="can_focus">False</property>
    <property name="type_hint">dialog</property>
    <property name="transient_for">window</property>
    <property name="buttons">yes-no</property>
    <property name="text" translatable="yes">&lt;b&gt;MessageDialog schließen?&lt;/b&gt;</property>
    <property name="use_markup">True</property>
    <signal name="response" handler="on_dialog_response" swapped="no"/>
    <child type="titlebar">
      <placeholder/>
    </child>
    <child internal-child="vbox">
      <object class="GtkBox">
        <property name="can_focus">False</property>
        <property name="orientation">vertical</property>
        <property name="spacing">2</property>
        <child internal-child="action_area">
          <object class="GtkButtonBox">
            <property name="can_focus">False</property>
            <property name="homogeneous">True</property>
            <property name="layout_style">end</property>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">False</property>
            <property name="position">0</property>
          </packing>
        </child>
      </object>
    </child>
  </object>
</interface>

GMenu

14_giomenu.ui (Source)

<?xml version="1.0"?>
<interface>
  <menu id="appmenu">
    <section>
      <item>
        <attribute name="label" translatable="yes">About</attribute>
        <attribute name="action">app.about</attribute>
      </item>
      <item>
        <attribute name="label" translatable="yes">Message</attribute>
        <attribute name="action">app.message</attribute>
      </item>
    </section>
    <section>
      <item>
        <attribute name="label" translatable="yes">Quit</attribute>
        <attribute name="action">app.quit</attribute>
        <attribute name="accel">&lt;Primary&gt;q</attribute>
      </item>
    </section>
  </menu>
</interface>

Python

14_application.py (Source)

#!/usr/bin/python
# -*- coding: utf-8 -*-

import sys

import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, Gio


class Handler:

    def on_window_destroy(self,window):
        window.close()

    def on_dialog_delete_event(self,widget,event):
        widget.hide_on_delete()
        return True

    def on_aboutbutton_clicked(self,widget):
        app.obj("aboutdialog").show_all()

    def on_messagebutton_clicked(self,widget):
        app.obj("messdialog").format_secondary_text("")
        app.obj("messdialog").show_all()

    def on_dialog_response(self,widget,response):
        if response == -8:
            widget.hide_on_delete()
        elif response == -9:
            widget.format_secondary_text("Doch!")

class ExampleApp:

    def __init__(self):

        self.app = Gtk.Application.new("org.application.test",
                                        Gio.ApplicationFlags(0),
                                       )
        self.app.connect("startup", self.on_app_startup)
        self.app.connect("activate", self.on_app_activate)
        self.app.connect("shutdown", self.on_app_shutdown)

    def on_app_startup(self, app):
        print("Gio.Application startup signal emitted")

    def on_app_activate(self, app):
        print("Gio.Application activate signal emitted")
        builder = Gtk.Builder()
        builder.add_from_file("13_dialoge.glade")
        builder.add_from_file("14_giomenu.ui")
        builder.connect_signals(Handler())

        app.set_app_menu(builder.get_object("appmenu"))
        self.obj = builder.get_object
        self.obj("window").set_application(app)

        # display application name in upper panel of the GNOME Shell (deprecated)
        # self.obj("window").set_wmclass("Application test","Application test")
        self.obj("window").show_all()

        self.add_simple_action("about", self.on_action_about_activated)
        self.add_simple_action("message", self.on_action_message_activated)
        self.add_simple_action("quit", self.on_action_quit_activated)

    def on_app_shutdown(self, app):
        print("Gio.Application shutdown signal emitted")

    def add_simple_action(self, name, callback):
        action = Gio.SimpleAction.new(name)
        action.connect("activate", callback)
        self.app.add_action(action)

    def on_action_about_activated(self, action, user_data):
        self.obj("aboutdialog").show_all()

    def on_action_message_activated(self, action, user_data):
        Handler().on_messagebutton_clicked(self)

    def on_action_quit_activated(self, action, user_data):
        self.app.quit()

    def run(self, argv):
        self.app.run(argv)


app = ExampleApp()
app.run(sys.argv)

NoN: progress

Knights of Ni - progress

As said before I am currently working on a GUI for Nikola doing some progress:

  • removed titlebar, using headerbar instead

  • switch between multiple Nikola instances

  • bookmark local Nikola instances (only adding for now)

  • support for multilinual sites:
    • display of configured languages

    • show existent translations in posts/pages tab

    • "Translation" tab: show files, generate new translation file for post/page (this is somehow redundant to the posts/pages tab, I still think about it...)

  • if there is no title the "Title" column will show the slug or (if this does not exist neither) filename

  • some mor log messages to pretent important things happen

/images/non/non2.thumbnail.png

Translating articles

As soon as I figured out multilinguality in Nikola I will translate the tutorial series into English.

...

Well, that was kinda easy...(but I will need some time for translation and writing new stuff and improving the new project).

Dialogues

Handle dialogue windows

Dialogues are complementary windows which are useful in interaction with the user, to show some relevant information or demand input. The GtkDialog class provides subclasses for common dialogue use cases like the AboutDialog and MessageDialog used in the example file. (FileChooserDialog article).

/images/13_dialoge.thumbnail.png

Glade

In the widget sidebar dialog widgets are integrated to the "Toplevel" section next to window widgets.

Dialogs are complementary windows to grab the user's focus. They can fixed to a superordinated window but at least be configured transient to a parent window via General > Window Attributes Transient For:".

AboutDialog

The "About" dialog window in general gives information about the project, its version, license, participating programmers, translators etc. All this can be directly typed into Glade.

MessageDialog

The MessageDialog is a standard dialog to show information or call for input. It is configurated to be drawn without window decoration or showing up seperately in the taskbar. Furthermore there is the possibility to add standard buttons.

Buttons and responses

Dialogs already own an intern GtkButtonBox to place any buttons in.

In constrast to regular windows the clicked signals of the buttons do not have to be assigned in these Buttonboxes (it's still possible to do so, of course). Instead in the "General" button properties you define a response answer (int) and assign the response signal of the GtkDialog.

Standard buttons available for example in MessageDialogs have a fixed response (see also Python GI API Reference):

  • Ok -5

  • Abort -6

  • Close -7

  • Yes -8

  • No -9

  • [X] -4

The huge advantage of that procedure is that the response refers to the dialog object so the responses can be processed by a single function.

Reestablish dialogues

The problem of windows closed via destroy signal is that they cannot be reactivated therefore the delete-event signal is used here.

Python

Responses

When emitting the response signal the response is passed as parameter. As said before this offers the option to process all responses in one function:

def on_dialog_response(self,widget,response):
    if response == 0:
        widget.hide_on_delete()
    elif response == 1:
        do.something()
    elif response == (2 or 3):
        do.something.different()

Delete-event

The hide_on_delete() function removes a window but can be reestablished by show_all():

def on_dialog_delete_event(self,widget,event):
    widget.hide_on_delete()
    return True

Several Glade files

As mentioned before several Glade files can be used within a project. It is not possible though to associate dialogs with their parent window if separated into different files. So the set_transient_for function of GtkWindow is required:

dialog.set_transient_for(mainwindow)

Listings

Glade

13_dialoge.glade (Source)

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1 -->
<interface>
  <requires lib="gtk+" version="3.20"/>
  <object class="GtkApplicationWindow" id="window">
    <property name="can_focus">False</property>
    <property name="title" translatable="yes">Titel</property>
    <signal name="destroy" handler="on_window_destroy" swapped="no"/>
    <child type="titlebar">
      <placeholder/>
    </child>
    <child>
      <object class="GtkBox">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="orientation">vertical</property>
        <child>
          <object class="GtkButton" id="aboutbutton">
            <property name="label" translatable="yes">GtkAboutDialog</property>
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">True</property>
            <signal name="clicked" handler="on_aboutbutton_clicked" swapped="no"/>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">0</property>
          </packing>
        </child>
        <child>
          <object class="GtkButton" id="messagebutton">
            <property name="label" translatable="yes">GtkMessageDialog</property>
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">True</property>
            <signal name="clicked" handler="on_messagebutton_clicked" swapped="no"/>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">1</property>
          </packing>
        </child>
      </object>
    </child>
  </object>
  <object class="GtkAboutDialog" id="aboutdialog">
    <property name="can_focus">False</property>
    <property name="type_hint">dialog</property>
    <property name="transient_for">window</property>
    <property name="attached_to">window</property>
    <property name="program_name">Glade-Tutorial</property>
    <property name="version">1.0</property>
    <property name="logo_icon_name">help-about</property>
    <property name="license_type">mit-x11</property>
    <signal name="delete-event" handler="on_dialog_delete_event" swapped="no"/>
    <signal name="response" handler="on_dialog_response" swapped="no"/>
    <child type="titlebar">
      <placeholder/>
    </child>
    <child internal-child="vbox">
      <object class="GtkBox">
        <property name="can_focus">False</property>
        <property name="orientation">vertical</property>
        <property name="spacing">2</property>
        <child internal-child="action_area">
          <object class="GtkButtonBox">
            <property name="can_focus">False</property>
            <property name="layout_style">end</property>
            <child>
              <object class="GtkButton" id="button1">
                <property name="label">gtk-ok</property>
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="receives_default">True</property>
                <property name="use_stock">True</property>
              </object>
              <packing>
                <property name="expand">True</property>
                <property name="fill">True</property>
                <property name="position">1</property>
              </packing>
            </child>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">False</property>
            <property name="position">0</property>
          </packing>
        </child>
      </object>
    </child>
    <action-widgets>
      <action-widget response="-8">button1</action-widget>
    </action-widgets>
  </object>
  <object class="GtkMessageDialog" id="messdialog">
    <property name="can_focus">False</property>
    <property name="type_hint">dialog</property>
    <property name="transient_for">window</property>
    <property name="buttons">yes-no</property>
    <property name="text" translatable="yes">&lt;b&gt;MessageDialog schließen?&lt;/b&gt;</property>
    <property name="use_markup">True</property>
    <signal name="response" handler="on_dialog_response" swapped="no"/>
    <child type="titlebar">
      <placeholder/>
    </child>
    <child internal-child="vbox">
      <object class="GtkBox">
        <property name="can_focus">False</property>
        <property name="orientation">vertical</property>
        <property name="spacing">2</property>
        <child internal-child="action_area">
          <object class="GtkButtonBox">
            <property name="can_focus">False</property>
            <property name="homogeneous">True</property>
            <property name="layout_style">end</property>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">False</property>
            <property name="position">0</property>
          </packing>
        </child>
      </object>
    </child>
  </object>
</interface>

Python

13_dialoge.py (Source)

#!/usr/bin/python
# -*- coding: utf-8 -*-

import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk


class Handler:

    def on_window_destroy(self, *args):
        Gtk.main_quit()

    def on_dialog_delete_event(self, widget, event):
        widget.hide_on_delete()
        return True

    def on_aboutbutton_clicked(self, widget):
        x.obj("aboutdialog").run()

    def on_messagebutton_clicked(self, widget):
        x.obj("messdialog").format_secondary_text("")
        x.obj("messdialog").run()

    def on_dialog_response(self, widget, response):
        if response == -8:
            widget.hide_on_delete()
        elif response == -9:
            widget.format_secondary_text("Doch!")


class Example:

    def __init__(self):

        builder = Gtk.Builder()
        builder.add_from_file("13_dialoge.glade")
        builder.connect_signals(Handler())

        self.obj = builder.get_object
        self.obj("window").show_all()

    def main(self):
        Gtk.main()


x = Example()
x.main()

Data view

Display TreeStore data

(Continuation of the ListStore article)

/images/12_treestore.thumbnail.png

TreeStore vs. ListStore

In contrast to ListStores TreeStore rows can possess child rows. That's why the append function requires another parameter that specifies the parent row reference:

#append row to liststore
store.append([value1,value2,value3])

#append row to treestore
store.append(parent,[value1,value2,value3])

The parent value is either

  • None if the current row is not a child row of another, or

  • TreeIter pointing to the superordinate row.

The TreeIter value is generated when creating a row, subordinate rows are created by

row1 = store.append(None,[value1,value2,value3])
row2 = store.append(row1,[value1,value2,value3])

The TreeIter of a cell is obtained by calling the get_selection function of the automatically generated GtkTreeSelection widget.

Glade

In the example there are two TreeStores with some columns and the coresponding TreeView widgets to display the data columns.

TreeModelSort

Sorting a column is set by calling set_sort_column_id. If this is applied to the TreeStore all TreeView widgets using this store are equally sorted.

If this behaviour is not diesired TreeModelSort elements come into play and which are "interposed" between store and view widgets. First the TreeModelSort is created via "Miscellaneous > Tree Model Sort" from the widget sidebar. Then you choose a source TreeView to use data from. After that the model in the TreeView widget is replaced by the newly created TreeModelSort.

The sort function is now simply applied to the TreeModelSort object instead to the TreeView object.

TreeModelFilter

TreeModelFilter allows to only show data that matches the specified filter criteria. Handling this object is analogue to TreeModelSort.

In the example the varieties can be filtered according to fruit colour so there is a GtkButtonBox required to put the corresponding buttons into.

Load formatting values from the model

Besides the columns containing displayed data there is a "weight" column in the first TreeStore. This value is used to show the cell in bold text. It is realized by setting the CellRenderer's property of "Font weight" to the column containing the corresponding value (normal font is 400). In this way the appearance of cells can be defined, for example colours or font formating.

Python

TreeModelSort

Requesting a position by calling GtkTreeSelection.get_selected() returns a tuple (model, pos), pos of model points to TreeModelSort (or TreeModelFilter) and requires conversion to the TreeStore position:

model,pos = selection.get_selected()
converted_iter = treesort.convert_iter_to_child_iter(pos)
store.set_value(converted_iter,column,value)

TreeModelFilter

First of all a filter function is required defining the visibility of cells, in the example it's the variable self.color:

def color_filter_func(self,model,iter,data):
    if model[iter][2] == self.color:
        return True
    else:
        return False

This function has to be assigned to TreeFilter

treefilter.set_visible_func(filter_func)

A filter process is then executed by calling the refilter() function on the TreeFilter object:

def on_button_clicked(self,widget):
    x.color = widget.get_label()
    x.obj("treefilter").refilter()

Listings

Glade

12_treestore.glade (Source)

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.0 -->
<interface>
  <requires lib="gtk+" version="3.20"/>
  <object class="GtkTreeStore" id="filterstore">
    <columns>
      <!-- column-name var -->
      <column type="gchararray"/>
      <!-- column-name spec -->
      <column type="gchararray"/>
      <!-- column-name color -->
      <column type="gchararray"/>
    </columns>
  </object>
  <object class="GtkTreeModelFilter" id="treefilter">
    <property name="child_model">filterstore</property>
  </object>
  <object class="GtkTreeStore" id="store">
    <columns>
      <!-- column-name name -->
      <column type="gchararray"/>
      <!-- column-name spec -->
      <column type="gchararray"/>
      <!-- column-name color -->
      <column type="gchararray"/>
      <!-- column-name note -->
      <column type="gchararray"/>
      <!-- column-name weight -->
      <column type="gint"/>
      <!-- column-name counter -->
      <column type="gint"/>
    </columns>
  </object>
  <object class="GtkTreeModelSort" id="treesort">
    <property name="model">store</property>
  </object>
  <object class="GtkApplicationWindow" id="window">
    <property name="width_request">600</property>
    <property name="height_request">500</property>
    <property name="can_focus">False</property>
    <property name="title" translatable="yes">Titel</property>
    <signal name="destroy" handler="on_window_destroy" swapped="no"/>
    <child>
      <object class="GtkBox" id="box">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="orientation">vertical</property>
        <property name="spacing">12</property>
        <property name="homogeneous">True</property>
        <child>
          <object class="GtkScrolledWindow">
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="shadow_type">in</property>
            <child>
              <object class="GtkTreeView" id="view">
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="model">treesort</property>
                <property name="headers_clickable">False</property>
                <property name="rules_hint">True</property>
                <child internal-child="selection">
                  <object class="GtkTreeSelection" id="selection"/>
                </child>
                <child>
                  <object class="GtkTreeViewColumn">
                    <property name="title" translatable="yes">Name</property>
                    <property name="sort_order">descending</property>
                    <property name="sort_column_id">5</property>
                    <child>
                      <object class="GtkCellRendererText"/>
                      <attributes>
                        <attribute name="text">0</attribute>
                        <attribute name="weight">4</attribute>
                      </attributes>
                    </child>
                  </object>
                </child>
                <child>
                  <object class="GtkTreeViewColumn">
                    <property name="title" translatable="yes">Farbe</property>
                    <child>
                      <object class="GtkCellRendererText"/>
                      <attributes>
                        <attribute name="text">2</attribute>
                      </attributes>
                    </child>
                  </object>
                </child>
                <child>
                  <object class="GtkTreeViewColumn">
                    <property name="title" translatable="yes">Bemerkung</property>
                    <child>
                      <object class="GtkCellRendererText" id="cellrenderer_note">
                        <property name="editable">True</property>
                        <signal name="edited" handler="on_cellrenderer_note_edited" swapped="no"/>
                      </object>
                      <attributes>
                        <attribute name="text">3</attribute>
                      </attributes>
                    </child>
                  </object>
                </child>
              </object>
            </child>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">0</property>
          </packing>
        </child>
        <child>
          <object class="GtkBox">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <child>
              <object class="GtkScrolledWindow">
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="shadow_type">in</property>
                <property name="propagate_natural_width">True</property>
                <child>
                  <object class="GtkTreeView">
                    <property name="visible">True</property>
                    <property name="can_focus">True</property>
                    <property name="model">treefilter</property>
                    <child internal-child="selection">
                      <object class="GtkTreeSelection"/>
                    </child>
                    <child>
                      <object class="GtkTreeViewColumn">
                        <property name="title" translatable="yes">Sorte</property>
                        <child>
                          <object class="GtkCellRendererText"/>
                          <attributes>
                            <attribute name="text">0</attribute>
                          </attributes>
                        </child>
                      </object>
                    </child>
                    <child>
                      <object class="GtkTreeViewColumn">
                        <property name="title" translatable="yes">Art</property>
                        <child>
                          <object class="GtkCellRendererText"/>
                          <attributes>
                            <attribute name="text">1</attribute>
                          </attributes>
                        </child>
                      </object>
                    </child>
                  </object>
                </child>
              </object>
              <packing>
                <property name="expand">True</property>
                <property name="fill">True</property>
                <property name="position">0</property>
              </packing>
            </child>
            <child>
              <object class="GtkButtonBox" id="buttonbox">
                <property name="visible">True</property>
                <property name="can_focus">False</property>
                <property name="orientation">vertical</property>
                <property name="layout_style">start</property>
                <child>
                  <placeholder/>
                </child>
              </object>
              <packing>
                <property name="expand">False</property>
                <property name="fill">True</property>
                <property name="padding">5</property>
                <property name="pack_type">end</property>
                <property name="position">1</property>
              </packing>
            </child>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">2</property>
          </packing>
        </child>
      </object>
    </child>
    <child type="titlebar">
      <placeholder/>
    </child>
  </object>
</interface>

Python

12_treestore.py (Source)

#!/usr/bin/python
# -*- coding: utf-8 -*-

import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk


class Handler:

    def on_window_destroy(self, *args):
        Gtk.main_quit()

    def on_button_clicked(self, widget):
        x.color = widget.get_label()
        x.obj("treefilter").refilter()

    def on_cellrenderer_note_edited(self, widget, row, edit):
        model, pos = x.obj("selection").get_selected()
        conv_iter = x.obj("treesort").convert_iter_to_child_iter(pos)
        x.obj("store").set_value(conv_iter,3,edit)


class Example:

    def __init__(self):

        self.builder = Gtk.Builder()
        self.builder.add_from_file("12_treestore.glade")
        self.builder.connect_signals(Handler())
        self.obj = self.builder.get_object

        #read data from text file
        with open("12_capsicum.txt") as f:
            data = [line.strip("\n") for line in f]

        colors = set()
        species = set()
        varieties = []
        for line in data:
            variety = line.split(";")
            colors.add(variety[2])
            species.add(variety[1])
            try:
                variety[3]
            except IndexError:
                variety.append("")
            varieties.append(variety)

        #append lines to 1st treestore
        for s in species:
            counter = 0
            row = self.obj("store").append(None,
                                            [None, None, None, None, 800, None],
                                            )
            for v in varieties:
                if v[1] == s:
                    self.obj("store").append(row,
                                             [v[0], v[1], v[2], v[3], 400, None],
                                             )
                    counter += 1
            self.obj("store").set_value(row, 0, "{} ({})".format(s, counter))
            self.obj("store").set_value(row, 5, counter)

        #append lines to 2nd treestore
        [self.obj("filterstore").append(None, [v[0], v[1], v[2]]) for v in varieties]

        #create buttons in buttonbox
        for c in colors:
            button = Gtk.Button.new_with_label(c)
            button.connect("clicked",Handler().on_button_clicked)
            self.obj("buttonbox").add(button)

        self.obj("view").expand_all()
        self.obj("treesort").set_sort_column_id(5, Gtk.SortType.DESCENDING)
        self.obj("treefilter").set_visible_func(self.color_filter_func)
        self.obj("window").show_all()

    def color_filter_func(self, model, iter, data):
        if model[iter][2] == self.color:
            return True
        else:
            return False

    def main(self):
        Gtk.main()


x = Example()
x.main()

New project: Knights of Ni

Knights of Ni - small managing tool for the static website generator Nikola

/images/non/non_window.thumbnail.png

New little project made of Glade and Python on GitHub: Knights of Ni.

Exterminate!

The VTE terminal widget

/images/11_terminal.thumbnail.png

Glade

The widget can be found in the lower part of the widget side bar and provides a complete terminal emulator. To close the window on the exit command the child-exited signal has to be assigned.

A button click shall open a Python prompt within the terminal window, so we need the familiar clicked signal.

Python

Elements used in Glade that are not part of the Gtk module have to be registered as a GObject object (this is also required when using a GtkSourceView widget as the functionality is provided by the GtkSource module):

GObject.type_register(Vte.Terminal)

The terminal emulator is initiated by calling spawn_sync expecting 7 parameters. Detailed information on the parameters are available in the documentation but for a common start a lot of defaults and Nones will do:

terminal.spawn_sync(
        Vte.PtyFlags.DEFAULT,
        None,
        ["/bin/bash"],
        None,
        GLib.SpawnFlags.DEFAULT,
        None,
        None,
        )

The feed_child function must be called to send a command to the console. The expected parameters are the string including a newline and the length of the string:

command = "python\n"
x.terminal.feed_child(command,len(command))

Listings

Glade

11_terminal.glade (Source)

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.0 -->
<interface>
  <requires lib="gtk+" version="3.20"/>
  <requires lib="vte-2.91" version="0.46"/>
  <object class="GtkApplicationWindow" id="window">
    <property name="can_focus">False</property>
    <signal name="destroy" handler="on_window_destroy" swapped="no"/>
    <child>
      <object class="GtkBox">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="orientation">vertical</property>
        <child>
          <object class="VteTerminal" id="term">
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="has_focus">True</property>
            <property name="hscroll_policy">natural</property>
            <property name="vscroll_policy">natural</property>
            <property name="encoding">UTF-8</property>
            <property name="scroll_on_keystroke">True</property>
            <property name="scroll_on_output">False</property>
            <signal name="child-exited" handler="on_window_destroy" swapped="no"/>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">0</property>
          </packing>
        </child>
        <child>
          <object class="GtkButton" id="button">
            <property name="label" translatable="yes">start python console</property>
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">True</property>
            <signal name="clicked" handler="on_button_clicked" swapped="no"/>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">False</property>
            <property name="position">2</property>
          </packing>
        </child>
      </object>
    </child>
    <child type="titlebar">
      <placeholder/>
    </child>
  </object>
</interface>

Python

11_terminal.py (Source)

#!/usr/bin/python
# -*- coding: utf-8 -*-

import os

import gi
gi.require_version("Gtk", "3.0")
gi.require_version("Vte", "2.91")
from gi.repository import Gtk, Vte, GObject, GLib


class Handler:

    def on_window_destroy(self, *args):
        Gtk.main_quit()

    def on_button_clicked(self, widget):
        command = "python\n"
        x.terminal.feed_child(command.encode())


class Example:

    def __init__(self):

        self.builder = Gtk.Builder()
        GObject.type_register(Vte.Terminal)

        self.builder.add_from_file("11_terminal.glade")
        self.builder.connect_signals(Handler())

        self.terminal = self.builder.get_object("term")
        self.terminal.spawn_sync(
            Vte.PtyFlags.DEFAULT,
            None,
            ["/bin/bash"],
            None,
            GLib.SpawnFlags.DEFAULT,
            )

        window = self.builder.get_object("window")
        window.show_all()

    def main(self):
        Gtk.main()


x = Example()
x.main()

Romani ite domum

Localization with gettext and locale

/images/10_lokalisation.thumbnail.png

Glade

Strings in widgets are by default configurated as translatable so there are no preparations required. GetText directly provercesses Glade project files.

Python

Translatable strings

Approved translatable strings are recognized by xgettext by brackets with a leading underscore:

_ = gettext.gettext
translatable_string = _("translate me")

configure (bind)textdomain

Now name and location of the MO files have to be configured in the source code:

locale.bindtextdomain(appname,locales_dir)
locale.textdomain(locales_dir)
gettext.bindtextdomain(appname,locales_dir)
gettext.textdomain(appname)
builder.set_translation_domain(appname)

set_translation_domain has to be called before loading Glade files.

GetText

POT

POT is the abbrevation for Portable Object Template. This file contains all original translatable strings. After generating an empty POT file, xgettext is executed for all source files containing translatable strings:

$ xgettext --options -o output.pot sourcefile.ext

The identified strings are added to the POT file.

#: sourcefile.ext:line number
msgid "translatable string"
msgstr ""

The file number reference comment can be avoided by passting the option --no-location.

In this article's example it is required to run xgettext once for the Glade file and once for the Python source code; the -j (--join-existing) option adds new found strings to an existing file:

$ xgettext --sort-output --keyword=translatable --language=Glade -j -o 10_localization/TUT.pot 10_lokalisation.glade
$ xgettext --language=Python -j -o 10_localization/TUT.pot 10_lokalisation.py

PO

Translated strings are stored in a PO file per language. A new translation ist invoked by

$ msginit --input=source.pot --locale=xx
# xx=language code

that generates a file after the pattern xx.po (p.e. de.po). This file can be edited in any text editor or dedicated tools such like PoEdit. A German localization for example is created by the command

$ msginit --input=TUT.pot --locale=de

If the POT file is altered the PO files are updated with the new strings by executing msgmerge:

$ msgmerge lang.po template.pot > new_lang.po

MO

MO files are (machine readable) binary files and mandatory for gettext to work. Localization files are located below the bindtextdomain following the file structure path/to/bindtextdomain)/locale/language code/LC_MESSAGES/appname.po.

In the example the bindtextdomain is created in the local directory, the generated de.po translation text file then transformed into the corresponding MO file:

$ msgfmt --output locale/de/LC_MESSAGES/TUT.mo de.po

Tipps

xgettext options

--no-location

Oppress writing line number(s) and file name as comment

--omit-header

Avoid overwriting header information

Remove obsolete strings

Strings that are removed from the template remain in the translation files. You can get rid of these by executing this command:

$ msgattrib --set-obsolete --ignore-file=PRJ.pot -o xx.po xx.po

Listings

Glade

10_lokalisation.glade (Source)

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.0 -->
<interface>
  <requires lib="gtk+" version="3.20"/>
  <object class="GtkApplicationWindow" id="window">
    <property name="can_focus">False</property>
    <property name="title" translatable="yes">Localization example (English)</property>
    <property name="default_width">400</property>
    <property name="default_height">400</property>
    <signal name="destroy" handler="on_window_destroy" swapped="no"/>
    <child>
      <object class="GtkBox">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="orientation">vertical</property>
        <child>
          <object class="GtkMenuBar">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <child>
              <object class="GtkMenuItem">
                <property name="visible">True</property>
                <property name="can_focus">False</property>
                <property name="label" translatable="yes">_File</property>
                <property name="use_underline">True</property>
                <child type="submenu">
                  <object class="GtkMenu">
                    <property name="visible">True</property>
                    <property name="can_focus">False</property>
                    <child>
                      <object class="GtkImageMenuItem">
                        <property name="label">gtk-new</property>
                        <property name="visible">True</property>
                        <property name="can_focus">False</property>
                        <property name="use_underline">True</property>
                        <property name="use_stock">True</property>
                      </object>
                    </child>
                    <child>
                      <object class="GtkImageMenuItem">
                        <property name="label">gtk-open</property>
                        <property name="visible">True</property>
                        <property name="can_focus">False</property>
                        <property name="use_underline">True</property>
                        <property name="use_stock">True</property>
                      </object>
                    </child>
                    <child>
                      <object class="GtkImageMenuItem">
                        <property name="label">gtk-save</property>
                        <property name="visible">True</property>
                        <property name="can_focus">False</property>
                        <property name="use_underline">True</property>
                        <property name="use_stock">True</property>
                      </object>
                    </child>
                    <child>
                      <object class="GtkImageMenuItem">
                        <property name="label">gtk-save-as</property>
                        <property name="visible">True</property>
                        <property name="can_focus">False</property>
                        <property name="use_underline">True</property>
                        <property name="use_stock">True</property>
                      </object>
                    </child>
                    <child>
                      <object class="GtkSeparatorMenuItem">
                        <property name="visible">True</property>
                        <property name="can_focus">False</property>
                      </object>
                    </child>
                    <child>
                      <object class="GtkImageMenuItem">
                        <property name="label">gtk-quit</property>
                        <property name="visible">True</property>
                        <property name="can_focus">False</property>
                        <property name="use_underline">True</property>
                        <property name="use_stock">True</property>
                      </object>
                    </child>
                  </object>
                </child>
              </object>
            </child>
            <child>
              <object class="GtkMenuItem">
                <property name="visible">True</property>
                <property name="can_focus">False</property>
                <property name="label" translatable="yes">_Edit</property>
                <property name="use_underline">True</property>
                <child type="submenu">
                  <object class="GtkMenu">
                    <property name="visible">True</property>
                    <property name="can_focus">False</property>
                    <child>
                      <object class="GtkImageMenuItem">
                        <property name="label">gtk-cut</property>
                        <property name="visible">True</property>
                        <property name="can_focus">False</property>
                        <property name="use_underline">True</property>
                        <property name="use_stock">True</property>
                      </object>
                    </child>
                    <child>
                      <object class="GtkImageMenuItem">
                        <property name="label">gtk-copy</property>
                        <property name="visible">True</property>
                        <property name="can_focus">False</property>
                        <property name="use_underline">True</property>
                        <property name="use_stock">True</property>
                      </object>
                    </child>
                    <child>
                      <object class="GtkImageMenuItem">
                        <property name="label">gtk-paste</property>
                        <property name="visible">True</property>
                        <property name="can_focus">False</property>
                        <property name="use_underline">True</property>
                        <property name="use_stock">True</property>
                      </object>
                    </child>
                    <child>
                      <object class="GtkImageMenuItem">
                        <property name="label">gtk-delete</property>
                        <property name="visible">True</property>
                        <property name="can_focus">False</property>
                        <property name="use_underline">True</property>
                        <property name="use_stock">True</property>
                      </object>
                    </child>
                  </object>
                </child>
              </object>
            </child>
            <child>
              <object class="GtkMenuItem">
                <property name="visible">True</property>
                <property name="can_focus">False</property>
                <property name="label" translatable="yes">_View</property>
                <property name="use_underline">True</property>
              </object>
            </child>
            <child>
              <object class="GtkMenuItem">
                <property name="visible">True</property>
                <property name="can_focus">False</property>
                <property name="label" translatable="yes">_Help</property>
                <property name="use_underline">True</property>
                <child type="submenu">
                  <object class="GtkMenu">
                    <property name="visible">True</property>
                    <property name="can_focus">False</property>
                    <child>
                      <object class="GtkImageMenuItem">
                        <property name="label">gtk-about</property>
                        <property name="visible">True</property>
                        <property name="can_focus">False</property>
                        <property name="use_underline">True</property>
                        <property name="use_stock">True</property>
                      </object>
                    </child>
                  </object>
                </child>
              </object>
            </child>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">0</property>
          </packing>
        </child>
        <child>
          <object class="GtkScrolledWindow">
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="shadow_type">in</property>
            <child>
              <object class="GtkViewport">
                <property name="visible">True</property>
                <property name="can_focus">False</property>
                <child>
                  <object class="GtkLabel">
                    <property name="visible">True</property>
                    <property name="can_focus">False</property>
                    <property name="label" translatable="yes">Good morning. In less than an hour, aircraft from here will join others from around the world. And you will be launching the largest aerial battle in this history of mankind.

Mankind -- that word should have new meaning for all of us today.

We can't be consumed by our petty differences anymore.

We will be united in our common interests.

Perhaps its fate that today is the 4th of July, and you will once again be fighting for our freedom, not from tyranny, oppression, or persecution -- but from annihilation.

We're fighting for our right to live, to exist.

And should we win the day, the 4th of July will no longer be known as an American holiday, but as the day when the world declared in one voice:

"We will not go quietly into the night!
We will not vanish without a fight!
We're going to live on!
We're going to survive!"

Today, we celebrate our Independence Day!</property>
                    <property name="wrap">True</property>
                  </object>
                </child>
              </object>
            </child>
          </object>
          <packing>
            <property name="expand">True</property>
            <property name="fill">True</property>
            <property name="position">1</property>
          </packing>
        </child>
      </object>
    </child>
    <child>
      <placeholder/>
    </child>
  </object>
</interface>

Python

10_lokalisation.py (Source)

#!/usr/bin/python
# -*- coding: utf-8 -*-

import gettext
import locale
import os

import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk

_ = gettext.gettext


class Handler:

    def on_window_destroy(self, *args):
        Gtk.main_quit()


class Example:

    def __init__(self):

        #setting up localization
        locales_dir = os.path.join(os.getcwd(),
                                    "10_localization",
                                    "locale",
                                    )
        appname = "TUT"

        #required for showing Glade file translations
        locale.bindtextdomain(appname, locales_dir)
        locale.textdomain(locales_dir)
        #required for code translations
        gettext.bindtextdomain(appname, locales_dir)
        gettext.textdomain(appname)

        self.builder = Gtk.Builder()
        self.builder.set_translation_domain(appname)

        self.builder.add_from_file("10_lokalisation.glade")
        self.builder.connect_signals(Handler())

        #translatable strings
        print(_("It's a trap!"))
        print(_("""These aren't the droids you're looking for.\n"""))

        #not translatable
        nonono = """\"Jar Jar is the key to all of this.\""""
        george = "...ruined it."
        print(nonono, george)

        window = self.builder.get_object("window")
        window.show_all()

    def main(self):
        Gtk.main()


x = Example()
x.main()

POT

10_localization/TUT.pot (Source)

# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-11-28 13:06+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"

msgid ""
"Good morning. In less than an hour, aircraft from here will join others from "
"around the world. And you will be launching the largest aerial battle in "
"this history of mankind.\n"
"\n"
"Mankind -- that word should have new meaning for all of us today.\n"
"\n"
"We can't be consumed by our petty differences anymore.\n"
"\n"
"We will be united in our common interests.\n"
"\n"
"Perhaps its fate that today is the 4th of July, and you will once again be "
"fighting for our freedom, not from tyranny, oppression, or persecution -- "
"but from annihilation.\n"
"\n"
"We're fighting for our right to live, to exist.\n"
"\n"
"And should we win the day, the 4th of July will no longer be known as an "
"American holiday, but as the day when the world declared in one voice:\n"
"\n"
"\"We will not go quietly into the night!\n"
"We will not vanish without a fight!\n"
"We're going to live on!\n"
"We're going to survive!\"\n"
"\n"
"Today, we celebrate our Independence Day!"
msgstr ""

msgid "It's a trap!"
msgstr ""

msgid "Localization example (English)"
msgstr ""

msgid "These aren't the droids you're looking for.\n"
msgstr ""

msgid "_Edit"
msgstr ""

msgid "_File"
msgstr ""

msgid "_Help"
msgstr ""

msgid "_View"
msgstr ""