File chooser dialog

Deutsch   |   Source

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: %s" % 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("Choose a folder",
                                    app.obj("window"),
                                    Gtk.FileChooserAction.SELECT_FOLDER,
                                    (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
                                    Gtk.STOCK_APPLY, Gtk.ResponseType.OK))
        dialog.set_default_size(600, 300)

        response = dialog.run()
        if response == Gtk.ResponseType.OK:
            print("Folder selection: %s" % 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").set_wmclass("Filechooser example","Filechooser example")
        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_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)

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

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

app = ExampleApp()
app.run(sys.argv)
Comments powered by Disqus