首选项对话框
经典的应用程序会有一些首选项,它们应该被记住而不需要在下次运行时在进行设置。即使对于我们这个简单的应用程序示例,我们可能也想改变用于文本内容的字体。
我们将使用Gio::Settings来储存我们的首选项。Gio::Settings需要一个用于描述我们的设置的模式,在本例中为org.gtkmm.exampleapp.gschema.xml文件。
在我们应用程序使用模式之前,我们需要将其编译为Gio::Settings能期望的二进制格式。GUI为此提供了基于autotools项目的宏来执行此操作。具体信息请参阅GSettings。而Meson在GNOME模块中提供了compile_schemas()执行此操作。
接下来,我们需要将设置连接到应该被控制的部件上。一种很方便的方法是使用Gio::Settings::bind()将对象的属性和设置键相互绑定,就像我们在ExampleAppWindow的构造函数中做的过渡设置一样。
m_settings = Gio::Settings::create("org.gtkmm.exampleapp"); m_settings->bind("transition", m_stack->property_transition_type());
字体设置的连接要麻烦的多,因为我们需要先创建对应的对象属性的Gtk::TextTag。以下代码在ExampleAppWindow::open_file_view()函数中。
auto tag = buffer->create_tag(); m_settings->bind("font", tag->property_font()); buffer->apply_tag(tag, buffer->begin(), buffer->end());
此时,如果你更改其中一项设置(例如:使用gsettings命令行工具。),则应用程序会做出反应。当然,我们会希望应用程序为这些首选项提供一个对话框。所以我们现在就如此做。我们的首选项对话框将是Gtk::Dialog的子类,并且使用我们之前已经在ExampleAppWindow中看到的技术:Gtk::Builder UI文件和设置绑定。
我们创建prefs.ui文件和ExampleAppPrefs子类之后,我们重访ExampleApplication::on_action_preferences()成员函数,并使其打开一个新的首选项对话框。
auto prefs_dialog = ExampleAppPrefs::create(*get_active_window()); prefs_dialog->present();
完成所有工作后,我们的应用程序现在可以显示如下所示的首选项对话框:
File: exampleapplication.h (For use with gtkmm 4)
#include "../step4/exampleapplication.h" // Equal to the corresponding file in step4
File: exampleappprefs.h (For use with gtkmm 4)
#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: exampleappwindow.h (For use with gtkmm 4)
#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; Gtk::MenuButton* m_gears; }; #endif /* GTKMM_EXAMPLEAPPWINDOW_H */
File: main.cc (For use with gtkmm 4)
#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 4)
#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_gears(nullptr) { // Get widgets from the Gtk::Builder file. m_stack = m_refBuilder->get_widget<Gtk::Stack>("stack"); if (!m_stack) throw std::runtime_error("No \"stack\" object in window.ui"); m_gears = m_refBuilder->get_widget<Gtk::MenuButton>("gears"); if (!m_gears) throw std::runtime_error("No \"gears\" object in window.ui"); // Bind settings. m_settings = Gio::Settings::create("org.gtkmm.exampleapp"); m_settings->bind("transition", m_stack->property_transition_type()); // Connect the menu to the MenuButton m_gears. // (The connection between action and menu item is specified in gears_menu.ui.) auto menu_builder = Gtk::Builder::create_from_resource("/org/gtkmm/exampleapp/gears_menu.ui"); auto menu = menu_builder->get_object<Gio::MenuModel>("menu"); if (!menu) throw std::runtime_error("No \"menu\" object in gears_menu.ui"); m_gears->set_menu_model(menu); } //static ExampleAppWindow* ExampleAppWindow::create() { // Load the Builder file and instantiate its widgets. auto refBuilder = Gtk::Builder::create_from_resource("/org/gtkmm/exampleapp/window.ui"); auto window = Gtk::Builder::get_widget_derived<ExampleAppWindow>(refBuilder, "app_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 Glib::ustring basename = file->get_basename(); auto scrolled = Gtk::make_managed<Gtk::ScrolledWindow>(); scrolled->set_expand(true); auto view = Gtk::make_managed<Gtk::TextView>(); view->set_editable(false); view->set_cursor_visible(false); scrolled->set_child(*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 4)
#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_font = m_refBuilder->get_widget<Gtk::FontButton>("font"); if (!m_font) throw std::runtime_error("No \"font\" object in prefs.ui"); m_transition = m_refBuilder->get_widget<Gtk::ComboBoxText>("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()); 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"); auto dialog = Gtk::Builder::get_widget_derived<ExampleAppPrefs>(refBuilder, "prefs_dialog"); if (!dialog) throw std::runtime_error("No \"prefs_dialog\" object in prefs.ui"); dialog->set_transient_for(parent); return dialog; }
File: exampleapplication.cc (For use with gtkmm 4)
#include "exampleapplication.h" #include "exampleappwindow.h" #include "exampleappprefs.h" #include <iostream> #include <exception> ExampleApplication::ExampleApplication() : Gtk::Application("org.gtkmm.examples.application", Gio::Application::Flags::HANDLES_OPEN) { } Glib::RefPtr<ExampleApplication> ExampleApplication::create() { return Glib::make_refptr_for_instance<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); // A window can be added to an application with Gtk::Application::add_window() // or Gtk::Window::set_application(). When all added windows have been hidden // or removed, the application stops running (Gtk::Application::run() returns()), // unless Gio::Application::hold() has been called. // Delete the window when it is hidden. appwindow->signal_hide().connect(sigc::bind(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 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"); } 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(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: exampleapp.gresource.xml (For use with gtkmm 4)
<?xml version="1.0" encoding="UTF-8"?> <gresources> <gresource prefix="/org/gtkmm/exampleapp"> <file preprocess="xml-stripblanks">window.ui</file> <file preprocess="xml-stripblanks">gears_menu.ui</file> <file preprocess="xml-stripblanks">prefs.ui</file> </gresource> </gresources>
File: prefs.ui (For use with gtkmm 4)
<?xml version="1.0" encoding="UTF-8"?> <interface> <object class="GtkDialog" id="prefs_dialog"> <property name="title" translatable="yes">Preferences</property> <property name="resizable">False</property> <property name="modal">True</property> <child internal-child="content_area"> <object class="GtkBox" id="content_area"> <child> <object class="GtkGrid" id="grid"> <property name="margin-start">12</property> <property name="margin-end">12</property> <property name="margin-top">12</property> <property name="margin-bottom">12</property> <property name="row-spacing">12</property> <property name="column-spacing">12</property> <child> <object class="GtkLabel" id="fontlabel"> <property name="label">_Font:</property> <property name="use-underline">True</property> <property name="mnemonic-widget">font</property> <property name="xalign">1</property> <layout> <property name="column">0</property> <property name="row">0</property> </layout> </object> </child> <child> <object class="GtkFontButton" id="font"> <layout> <property name="column">1</property> <property name="row">0</property> </layout> </object> </child> <child> <object class="GtkLabel" id="transitionlabel"> <property name="label">_Transition:</property> <property name="use-underline">True</property> <property name="mnemonic-widget">transition</property> <property name="xalign">1</property> <layout> <property name="column">0</property> <property name="row">1</property> </layout> </object> </child> <child> <object class="GtkComboBoxText" id="transition"> <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> <layout> <property name="column">1</property> <property name="row">1</property> </layout> </object> </child> </object> </child> </object> </child> </object> </interface>
File: org.gtkmm.exampleapp.gschema.xml (For use with gtkmm 4)
<?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>