A preference dialog
| Chapter 31. Building applications |

## A preference dialog

A typical application will have some preferences that should be remembered from one run to the next. Even for our simple example application, we may want to change the font that is used for the content.

We are going to use `Gio::Settings` to store our preferences. `Gio::Settings` requires a schema that describes our settings, in our case the `org.gtkmm.exampleapp.gschema.xml` file.

Before we can make use of this schema in our application, we need to compile it into the binary form that `Gio::Settings` expects. GIO provides macros to do this in autotools-based projects. See the description of GSettings.

Next, we need to connect our settings to the widgets that they are supposed to control. One convenient way to do this is to use `Gio::Settings::bind()` to bind settings keys to object properties, as we do for the transition setting in `ExampleAppWindow`'s constructor.

    m_settings = Gio::Settings::create("org.gtkmm.exampleapp");
    m_settings->bind("transition", m_stack->property_transition_type());

The code to connect the font setting is a little more involved, since it corresponds to an object property in a `Gtk::TextTag` that we must first create. The code is in `ExampleAppWindow::open_file_view()`.

    auto tag = buffer->create_tag();
    m_settings->bind("font", tag->property_font());
    buffer->apply_tag(tag, buffer->begin(), buffer->end());

At this point, the application will already react if you change one of the settings, e.g. using the gsettings commandline tool. Of course, we expect the application to provide a preference dialog for these. So lets do that now. Our preference dialog will be a subclass of `Gtk::Dialog`, and we'll use the same techniques that we've already seen in `ExampleAppWindow`: a `Gtk::Builder` ui file and settings bindings.

When we've created the `prefs.ui` file and the `ExampleAppPrefs` class, we revisit the `ExampleApplication::on_action_preferences()` method in our application class, and make it open a new preference dialog.

    auto prefs_dialog = ExampleAppPrefs::create(*get_active_window());
    prefs_dialog->present();

After all this work, our application can now show a preference dialog like this:

Figure 31.5. An preference dialog

Source Code

File: `exampleappwindow.h` (For use with gtkmm 3, not gtkmm 2)

    #ifndef GTKMM_EXAMPLEAPPWINDOW_H_
    #define GTKMM_EXAMPLEAPPWINDOW_H_

    #include <gtkmm.h>

    class ExampleAppWindow : public Gtk::ApplicationWindow
    {
    public:
      ExampleAppWindow(BaseObjectType* cobject,
        const Glib::RefPtr<Gtk::Builder>& refBuilder);

      static ExampleAppWindow* create();

      void open_file_view(const Glib::RefPtr<Gio::File>& file);

    protected:
      Glib::RefPtr<Gtk::Builder> m_refBuilder;
      Glib::RefPtr<Gio::Settings> m_settings;
      Gtk::Stack* m_stack;
    };

    #endif /* GTKMM_EXAMPLEAPPWINDOW_H */

File: `exampleappprefs.h` (For use with gtkmm 3, not gtkmm 2)

    #ifndef GTKMM_EXAMPLEAPPPREFS_H_
    #define GTKMM_EXAMPLEAPPPREFS_H_

    #include <gtkmm.h>

    class ExampleAppPrefs : public Gtk::Dialog
    {
    public:
      ExampleAppPrefs(BaseObjectType* cobject,
        const Glib::RefPtr<Gtk::Builder>& refBuilder);

      static ExampleAppPrefs* create(Gtk::Window& parent);

    protected:
      Glib::RefPtr<Gtk::Builder> m_refBuilder;
      Glib::RefPtr<Gio::Settings> m_settings;
      Gtk::FontButton* m_font;
      Gtk::ComboBoxText* m_transition;
    };

    #endif /* GTKMM_EXAMPLEAPPPREFS_H_ */

File: `exampleapplication.h` (For use with gtkmm 3, not gtkmm 2)

    #include "../step4/exampleapplication.h"
    // Equal to the corresponding file in step4

File: `exampleapplication.cc` (For use with gtkmm 3, not gtkmm 2)

    #include "exampleapplication.h"
    #include "exampleappwindow.h"
    #include "exampleappprefs.h"
    #include <iostream>
    #include <exception>

    ExampleApplication::ExampleApplication()
    : Gtk::Application("org.gtkmm.examples.application", Gio::APPLICATION_HANDLES_OPEN)
    {
    }

    Glib::RefPtr<ExampleApplication> ExampleApplication::create()
    {
      return Glib::RefPtr<ExampleApplication>(new ExampleApplication());
    }

    ExampleAppWindow* ExampleApplication::create_appwindow()
    {
      auto appwindow = ExampleAppWindow::create();

      // Make sure that the application runs for as long this window is still open.
      add_window(*appwindow);

      // Gtk::Application::add_window() connects a signal handler to the window's
      // signal_hide(). That handler removes the window from the application.
      // If it's the last window to be removed, the application stops running.
      // Gtk::Window::set_application() does not connect a signal handler, but is
      // otherwise equivalent to Gtk::Application::add_window().

      // Delete the window when it is hidden.
      appwindow->signal_hide().connect(sigc::bind<Gtk::Window*>(sigc::mem_fun(*this,
        &ExampleApplication::on_hide_window), appwindow));

      return appwindow;
    }

    void ExampleApplication::on_startup()
    {
      // Call the base class's implementation.
      Gtk::Application::on_startup();

      // Add actions and keyboard accelerators for the application menu.
      add_action("preferences", sigc::mem_fun(*this, &ExampleApplication::on_action_preferences));
      add_action("quit", sigc::mem_fun(*this, &ExampleApplication::on_action_quit));
      set_accel_for_action("app.quit", "<Ctrl>Q");

      auto refBuilder = Gtk::Builder::create();
      try
      {
        refBuilder->add_from_resource("/org/gtkmm/exampleapp/app_menu.ui");
      }
      catch (const Glib::Error& ex)
      {
        std::cerr << "ExampleApplication::on_startup(): " << ex.what() << std::endl;
        return;
      }

      auto object = refBuilder->get_object("appmenu");
      auto app_menu = Glib::RefPtr<Gio::MenuModel>::cast_dynamic(object);
      if (app_menu)
        set_app_menu(app_menu);
      else
        std::cerr << "ExampleApplication::on_startup(): No \"appmenu\" object in app_menu.ui"
                  << std::endl;
    }

    void ExampleApplication::on_activate()
    {
      try
      {
        // The application has been started, so let's show a window.
        auto appwindow = create_appwindow();
        appwindow->present();
      }
      // If create_appwindow() throws an exception (perhaps from Gtk::Builder),
      // no window has been created, no window has been added to the application,
      // and therefore the application will stop running.
      catch (const Glib::Error& ex)
      {
        std::cerr << "ExampleApplication::on_activate(): " << ex.what() << std::endl;
      }
      catch (const std::exception& ex)
      {
        std::cerr << "ExampleApplication::on_activate(): " << ex.what() << std::endl;
      }
    }

    void ExampleApplication::on_open(const Gio::Application::type_vec_files& files,
      const Glib::ustring& /* hint */)
    {
      // The application has been asked to open some files,
      // so let's open a new view for each one.
      ExampleAppWindow* appwindow = nullptr;
      auto windows = get_windows();
      if (windows.size() > 0)
        appwindow = dynamic_cast<ExampleAppWindow*>(windows[0]);

      try
      {
        if (!appwindow)
          appwindow = create_appwindow();

        for (const auto& file : files)
          appwindow->open_file_view(file);

        appwindow->present();
      }
      catch (const Glib::Error& ex)
      {
        std::cerr << "ExampleApplication::on_open(): " << ex.what() << std::endl;
      }
      catch (const std::exception& ex)
      {
        std::cerr << "ExampleApplication::on_open(): " << ex.what() << std::endl;
      }
    }

    void ExampleApplication::on_hide_window(Gtk::Window* window)
    {
      delete window;
    }

    void ExampleApplication::on_action_preferences()
    {
      try
      {
        auto prefs_dialog = ExampleAppPrefs::create(*get_active_window());
        prefs_dialog->present();

        // Delete the dialog when it is hidden.
        prefs_dialog->signal_hide().connect(sigc::bind<Gtk::Window*>(sigc::mem_fun(*this,
          &ExampleApplication::on_hide_window), prefs_dialog));
      }
      catch (const Glib::Error& ex)
      {
        std::cerr << "ExampleApplication::on_action_preferences(): " << ex.what() << std::endl;
      }
      catch (const std::exception& ex)
      {
        std::cerr << "ExampleApplication::on_action_preferences(): " << ex.what() << std::endl;
      }
    }

    void ExampleApplication::on_action_quit()
    {
      // Gio::Application::quit() will make Gio::Application::run() return,
      // but it's a crude way of ending the program. The window is not removed
      // from the application. Neither the window's nor the application's
      // destructors will be called, because there will be remaining reference
      // counts in both of them. If we want the destructors to be called, we
      // must remove the window from the application. One way of doing this
      // is to hide the window. See comment in create_appwindow().
      auto windows = get_windows();
      for (auto window : windows)
        window->hide();

      // Not really necessary, when Gtk::Widget::hide() is called, unless
      // Gio::Application::hold() has been called without a corresponding call
      // to Gio::Application::release().
      quit();
    }

File: `main.cc` (For use with gtkmm 3, not gtkmm 2)

    #include "exampleapplication.h"

    int main(int argc, char* argv[])
    {
      // Since this example is running uninstalled, we have to help it find its
      // schema. This is *not* necessary in a properly installed application.
      Glib::setenv ("GSETTINGS_SCHEMA_DIR", ".", false);

      auto application = ExampleApplication::create();

      // Start the application, showing the initial window,
      // and opening extra views for any files that it is asked to open,
      // for instance as a command-line parameter.
      // run() will return when the last window has been closed.
      return application->run(argc, argv);
    }

File: `exampleappwindow.cc` (For use with gtkmm 3, not gtkmm 2)

    #include "exampleappwindow.h"
    #include <iostream>
    #include <stdexcept>

    ExampleAppWindow::ExampleAppWindow(BaseObjectType* cobject,
      const Glib::RefPtr<Gtk::Builder>& refBuilder)
    : Gtk::ApplicationWindow(cobject),
      m_refBuilder(refBuilder),
      m_settings(),
      m_stack(nullptr)
    {
      m_refBuilder->get_widget("stack", m_stack);
      if (!m_stack)
        throw std::runtime_error("No \"stack\" object in window.ui");

      m_settings = Gio::Settings::create("org.gtkmm.exampleapp");
      m_settings->bind("transition", m_stack->property_transition_type());
    }

    //static
    ExampleAppWindow* ExampleAppWindow::create()
    {
      // Load the Builder file and instantiate its widgets.
      auto refBuilder = Gtk::Builder::create_from_resource("/org/gtkmm/exampleapp/window.ui");

      ExampleAppWindow* window = nullptr;
      refBuilder->get_widget_derived("app_window", window);
      if (!window)
        throw std::runtime_error("No \"app_window\" object in window.ui");

      return window;
    }

    void ExampleAppWindow::open_file_view(const Glib::RefPtr<Gio::File>& file)
    {
      const auto basename = file->get_basename();

      auto scrolled = Gtk::make_managed<Gtk::ScrolledWindow>();
      scrolled->set_hexpand(true);
      scrolled->set_vexpand(true);
      scrolled->show();
      auto view = Gtk::make_managed<Gtk::TextView>();
      view->set_editable(false);
      view->set_cursor_visible(false);
      view->show();
      scrolled->add(*view);
      m_stack->add(*scrolled, basename, basename);

      auto buffer = view->get_buffer();
      try
      {
        char* contents = nullptr;
        gsize length = 0;

        file->load_contents(contents, length);
        buffer->set_text(contents, contents+length);
        g_free(contents);
      }
      catch (const Glib::Error& ex)
      {
        std::cout << "ExampleAppWindow::open_file_view(\"" << file->get_parse_name()
          << "\"):\n  " << ex.what() << std::endl;
      }

      auto tag = buffer->create_tag();
      m_settings->bind("font", tag->property_font());
      buffer->apply_tag(tag, buffer->begin(), buffer->end());
    }

File: `exampleappprefs.cc` (For use with gtkmm 3, not gtkmm 2)

    #include "exampleappprefs.h"
    #include "exampleappwindow.h"
    #include <stdexcept>

    ExampleAppPrefs::ExampleAppPrefs(BaseObjectType* cobject,
      const Glib::RefPtr<Gtk::Builder>& refBuilder)
    : Gtk::Dialog(cobject),
      m_refBuilder(refBuilder),
      m_settings(),
      m_font(nullptr),
      m_transition(nullptr)
    {
      m_refBuilder->get_widget("font", m_font);
      if (!m_font)
        throw std::runtime_error("No \"font\" object in prefs.ui");

      m_refBuilder->get_widget("transition", m_transition);
      if (!m_transition)
        throw std::runtime_error("No \"transition\" object in prefs.ui");

      m_settings = Gio::Settings::create("org.gtkmm.exampleapp");
      m_settings->bind("font", m_font->property_font_name());
      m_settings->bind("transition", m_transition->property_active_id());
    }

    //static
    ExampleAppPrefs* ExampleAppPrefs::create(Gtk::Window& parent)
    {
      // Load the Builder file and instantiate its widgets.
      auto refBuilder = Gtk::Builder::create_from_resource("/org/gtkmm/exampleapp/prefs.ui");

      ExampleAppPrefs* dialog = nullptr;
      refBuilder->get_widget_derived("prefs_dialog", dialog);
      if (!dialog)
        throw std::runtime_error("No \"prefs_dialog\" object in prefs.ui");

      dialog->set_transient_for(parent);

      return dialog;
    }

File: `exampleapp.gresource.xml` (For use with gtkmm 3, not gtkmm 2)

    <?xml version="1.0" encoding="UTF-8"?>
    <gresources>
      <gresource prefix="/org/gtkmm/exampleapp">
        <file preprocess="xml-stripblanks">window.ui</file>
        <file preprocess="xml-stripblanks">app_menu.ui</file>
        <file preprocess="xml-stripblanks">prefs.ui</file>
      </gresource>
    </gresources>

File: `prefs.ui` (For use with gtkmm 3, not gtkmm 2)

    <?xml version="1.0" encoding="UTF-8"?>
    <interface>
      <!-- interface-requires gtk+ 3.8 -->
      <object class="GtkDialog" id="prefs_dialog">
        <property name="title" translatable="yes">Preferences</property>
        <property name="resizable">False</property>
        <property name="modal">True</property>
        <property name="use-header-bar">1</property>
        <child internal-child="vbox">
          <object class="GtkBox" id="vbox">
            <child>
              <object class="GtkGrid" id="grid">
                <property name="visible">True</property>
                <property name="margin">6</property>
                <property name="row-spacing">12</property>
                <property name="column-spacing">6</property>
                <child>
                  <object class="GtkLabel" id="fontlabel">
                    <property name="visible">True</property>
                    <property name="label">_Font:</property>
                    <property name="use-underline">True</property>
                    <property name="mnemonic-widget">font</property>
                    <property name="xalign">1</property>
                  </object>
                  <packing>
                    <property name="left-attach">0</property>
                    <property name="top-attach">0</property>
                  </packing>
                </child>
                <child>
                  <object class="GtkFontButton" id="font">
                    <property name="visible">True</property>
                  </object>
                  <packing>
                    <property name="left-attach">1</property>
                    <property name="top-attach">0</property>
                  </packing>
                </child>
                <child>
                  <object class="GtkLabel" id="transitionlabel">
                    <property name="visible">True</property>
                    <property name="label">_Transition:</property>
                    <property name="use-underline">True</property>
                    <property name="mnemonic-widget">transition</property>
                    <property name="xalign">1</property>
                  </object>
                  <packing>
                    <property name="left-attach">0</property>
                    <property name="top-attach">1</property>
                  </packing>
                </child>
                <child>
                  <object class="GtkComboBoxText" id="transition">
                    <property name="visible">True</property>
                    <items>
                      <item translatable="yes" id="none">None</item>
                      <item translatable="yes" id="crossfade">Fade</item>
                      <item translatable="yes" id="slide-left-right">Slide</item>
                    </items>
                  </object>
                  <packing>
                    <property name="left-attach">1</property>
                    <property name="top-attach">1</property>
                  </packing>
                </child>
              </object>
            </child>
          </object>
        </child>
      </object>
    </interface>

File: `org.gtkmm.exampleapp.gschema.xml` (For use with gtkmm 3, not gtkmm 2)

    <?xml version="1.0" encoding="UTF-8"?>
    <schemalist>
      <schema path="/org/gtkmm/exampleapp/" id="org.gtkmm.exampleapp">
        <key name="font" type="s">
          <default>'Monospace 12'</default>
          <summary>Font</summary>
          <description>The font to be used for content.</description>
        </key>
        <key name="transition" type="s">
          <choices>
            <choice value='none'/>
            <choice value='crossfade'/>
            <choice value='slide-left-right'/>
          </choices>
          <default>'none'</default>
          <summary>Transition</summary>
          <description>The transition to use when switching tabs.</description>
        </key>
      </schema>
    </schemalist>

An application menu |  | Adding a search bar