Using c++-gtk-utils with GtkBuilder

The Cgu::WinBase and Cgu::MainWidgetBase classes can manage GTK+ objects constructed with GtkBuilder, and thus with UI interfaces generated by, say, glade, just as they can be used with GTK+ objects generated by hand. Lifetime management will work automatically (the Cgu::MainWidgetBase constructor calls g_object_ref_sink() to take ownership from the GtkBuilder object, and the Cgu::WinBase destructor calls gtk_widget_destroy() to correctly handle the reference count of top level windows).

With Cgu::WinBase, two different approaches are possible. First, the WinBase object can take an object comprising or derived from GtkWindow which has been generated by GtkBuilder in the ordinary way. Alternatively, Cgu::WinBase can construct its own GtkWindow object, and a widget heirarchy generated by GtkBuilder can be added to that top level window. The following two examples construct the demonstration Message class (as appearing in the Cgu::WinBase documentation) using GtkBuilder adopting either approach.

First, an example which attaches a widget heirarchy constructed with GtkBuilder to a GtkApplicationWindow object constructed with a Cgu::WinBase object:

#include <exception>
#include <string>
#include <gtk/gtk.h>
#include <c++-gtk-utils/window.h>
#include <c++-gtk-utils/shared_handle.h>
#include <c++-gtk-utils/gobj_handle.h>
#include <c++-gtk-utils/application.h>
#include <c++-gtk-utils/callback.h>
// *** class headers ***
class UiBuildError: public std::exception {
Cgu::GcharSharedHandle message;
public:
virtual const char* what() const throw() {return message.get();}
UiBuildError(const char* msg):
message(g_strdup_printf("UiBuildError: %s", msg)) {}
~UiBuildError() throw() {}
};
extern "C" void message_button_clicked(GtkWidget*, void*);
class Message: public Cgu::WinBase {
public:
friend void message_button_clicked(GtkWidget*, void*);
Message(const char* text, GApplication* app);
};
// *** class implementation ***
// *** Message menu actions ***
namespace {
extern "C" {
void app_beep(GSimpleAction*,
GVariant*,
void*) {
gdk_beep();
}
void app_quit(GSimpleAction*,
GVariant*,
void* data) {
Cgu::Application* app = static_cast< Cgu::Application*>(data);
std::list<Cgu::WinBase*> wins = app->get_windows();
for (auto iter = wins.begin(); iter != wins.end(); ++iter) {
delete *iter; // this will also remove the Message object from
// the Cgu::Application object
}
}
GActionEntry app_entries[] = {
{ "beep", app_beep, NULL, NULL, NULL },
{ "quit", app_quit, NULL, NULL, NULL },
};
void win_beep(GSimpleAction*, GVariant*, void*) {
gdk_beep();
}
GActionEntry win_entries[] = {
{ "beep", win_beep, NULL, NULL, NULL },
};
} // extern "C"
} // unnamed namespace
// *** Message callbacks ***
void message_button_clicked(GtkWidget*, void* data) {
static_cast<Message*>(data)->close();
}
// *** Message UI and menu XML definitions
const gchar ui[] =
"<interface>"
" <object class='GtkVBox' id='vbox'>"
" <property name='homogeneous'>FALSE</property>"
" <property name='spacing'>2</property>"
" <child>"
" <object class='GtkLabel' id='label'>"
" </object>"
" <packing>"
" <property name='fill'>FALSE</property>"
" </packing>"
" </child>"
" <child>"
" <object class='GtkHButtonBox' id='bbox'>"
" <child>"
" <object class='GtkButton' id='button'>"
" <property name='label'>gtk-ok</property>"
" <property name='use-stock'>TRUE</property>"
" <property name='can-focus'>TRUE</property>"
" </object>"
" </child>"
" </object>"
" <packing>"
" <property name='expand'>FALSE</property>"
" <property name='fill'>FALSE</property>"
" </packing>"
" </child>"
" </object>"
"</interface>";
const gchar menus[] =
"<interface>"
" <menu id='app-menu'>"
" <section>"
" <item>"
" <attribute name='label'>Beep</attribute>"
" <attribute name='action'>app.beep</attribute>"
" </item>"
" <item>"
" <attribute name='label'>_Quit</attribute>"
" <attribute name='action'>app.quit</attribute>"
" </item>"
" </section>"
" </menu>"
" <menu id='menubar'>"
" <submenu>"
" <attribute name='label'>Menu1</attribute>"
" <section>"
" <item>"
" <attribute name='label'>More beep</attribute>"
" <attribute name='action'>win.beep</attribute>"
" </item>"
" </section>"
" </submenu>"
" </menu>"
"</interface>";
// *** Message methods
Message::Message(const char* text,
GApplication* app):
Cgu::WinBase{"Message", 0,
false, 0,
GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(app)))} {
g_action_map_add_action_entries(G_ACTION_MAP(get_win()),
win_entries,
G_N_ELEMENTS(win_entries),
static_cast<void*>(0));
Cgu::GobjHandle<GtkBuilder> builder{gtk_builder_new()};
GError* err = 0;
if (!gtk_builder_add_from_string(builder, ui, sizeof(ui) - 1, &err)) {
std::string msg{"Message::build_interface: "};
msg += err->message;
throw UiBuildError{msg.c_str()};
}
GObject* component = gtk_builder_get_object(builder, "vbox");
if (!component) {
throw UiBuildError{"Message constructor: Can't find 'vbox' object"};
}
gtk_container_add(GTK_CONTAINER(get_win()), GTK_WIDGET(component));
component = gtk_builder_get_object(builder, "label");
if (!component) {
throw UiBuildError{"Message constructor: Can't find 'label' object"};
}
gtk_label_set_text(GTK_LABEL(component), text);
component = gtk_builder_get_object(builder, "button");
if (!component) {
throw UiBuildError{"Message constructor: Can't find 'button' object"};
}
g_signal_connect(component, "clicked",
G_CALLBACK(message_button_clicked), this);
gtk_widget_show_all(GTK_WIDGET(get_win()));
}
// *** Cgu::Application callbacks
void startup(Cgu::Application* app) {
g_action_map_add_action_entries(G_ACTION_MAP(app->get_g_app()),
app_entries,
G_N_ELEMENTS(app_entries),
app);
// we cannot propagate exceptions out of the signal handler, so use
// gtk_builder_new_from_string() and error out if loading fails
Cgu::GobjHandle<GtkBuilder> builder{gtk_builder_new_from_string(menus, -1)};
GObject* component = gtk_builder_get_object(builder, "app-menu");
if (!component) {
g_error("%s", "startup: Can't find 'app-menu' object");
}
gtk_application_set_app_menu(GTK_APPLICATION(app->get_g_app()),
G_MENU_MODEL(component));
component = gtk_builder_get_object(builder, "menubar");
if (!component) {
g_error("%s", "startup: Can't find 'menubar' object");
}
gtk_application_set_menubar(GTK_APPLICATION(app->get_g_app()),
G_MENU_MODEL(component));
}
void activate(Cgu::Application* app) {
if (app->get_win_count() > 0) {
gtk_window_present(app->get_windows().front()->get_win());
return;
}
// we cannot propagate exceptions out of the signal handler, so
// error out with g_error if there is an exception
Cgu::WinBase* dialog;
try {
dialog = new Message{"This is a message", app->get_g_app()};
}
catch (std::exception& e) {
g_error("%s", e.what());
}
dialog->show_all();
app->add(dialog);
}
// *** user code ***
int main(int argc, char* argv[]) {
Cgu::Application app{"my_prog", G_APPLICATION_FLAGS_NONE};
app.startup.connect(Cgu::Callback::make(startup));
app.activate.connect(Cgu::Callback::make(activate));
return app.run(0, 0);
}

Secondly, an example which constructs the top level window and all its children using GtkBuilder:

#include <exception>
#include <string>
#include <gtk/gtk.h>
#include <c++-gtk-utils/window.h>
#include <c++-gtk-utils/shared_handle.h>
#include <c++-gtk-utils/gobj_handle.h>
#include <c++-gtk-utils/application.h>
#include <c++-gtk-utils/callback.h>
// *** class headers ***
class UiBuildError: public std::exception {
Cgu::GcharSharedHandle message;
public:
virtual const char* what() const throw() {return message.get();}
UiBuildError(const char* msg):
message(g_strdup_printf("UiBuildError: %s", msg)) {}
~UiBuildError() throw() {}
};
extern "C" void message_button_clicked(GtkWidget*, void*);
// Message objects are constructed from GtkBuilder objects. The class
// has two helper static member functions, get_window() and
// build_interface().
class Message: public Cgu::WinBase {
// get_window() must be static as it is called when initialising the
// base class
static GtkWindow* get_window(const Cgu::GobjHandle<GtkBuilder>&);
public:
// build_interface() must be static as it is invoked before the
// Message object constructor is entered
static Cgu::GobjHandle<GtkBuilder> build_interface();
friend void message_button_clicked(GtkWidget*, void*);
// the Message::build_interface() static member function provides
// the in-built default UI for Message objects. Alternative
// interfaces can be passed when the Message object is constructed,
// but they must provide the 'win', 'label' and 'button' objects or
// the constructor will throw UiBuildError
Message(const char* text,
GApplication* app,
const Cgu::GobjHandle<GtkBuilder>& = Message::build_interface());
};
// *** class implementation ***
// *** Message menu actions ***
namespace {
extern "C" {
void app_beep(GSimpleAction*,
GVariant*,
void*) {
gdk_beep();
}
void app_quit(GSimpleAction*,
GVariant*,
void* data) {
Cgu::Application* app = static_cast< Cgu::Application*>(data);
std::list<Cgu::WinBase*> wins = app->get_windows();
for (auto iter = wins.begin(); iter != wins.end(); ++iter) {
delete *iter; // this will also remove the Message object from
// the Cgu::Application object
}
}
GActionEntry app_entries[] = {
{ "beep", app_beep, NULL, NULL, NULL },
{ "quit", app_quit, NULL, NULL, NULL },
};
void win_beep(GSimpleAction*, GVariant*, void*) {
gdk_beep();
}
GActionEntry win_entries[] = {
{ "beep", win_beep, NULL, NULL, NULL },
};
} // extern "C"
} // unnamed namespace
// *** Message callbacks ***
void message_button_clicked(GtkWidget*, void* data) {
static_cast<Message*>(data)->close();
}
// *** Message UI and menu XML definitions
// this is the default UI for Message objects, obtained by calling
// Message::build_interface(). Any custom interface passed to the
// Message constructor must provide 'win', 'label' and 'button'
// objects. The remainder are optional. A custom interface could
// for example provide an icon in the window, or change layout.
const gchar ui[] =
"<interface>"
" <object class='GtkApplicationWindow' id='win'>"
" <child>"
" <object class='GtkVBox' id='vbox'>"
" <property name='homogeneous'>FALSE</property>"
" <property name='spacing'>2</property>"
" <child>"
" <object class='GtkLabel' id='label'>"
" </object>"
" <packing>"
" <property name='fill'>FALSE</property>"
" </packing>"
" </child>"
" <child>"
" <object class='GtkHButtonBox' id='bbox'>"
" <child>"
" <object class='GtkButton' id='button'>"
" <property name='label'>gtk-ok</property>"
" <property name='use-stock'>TRUE</property>"
" <property name='can-focus'>TRUE</property>"
" </object>"
" </child>"
" </object>"
" <packing>"
" <property name='expand'>FALSE</property>"
" <property name='fill'>FALSE</property>"
" </packing>"
" </child>"
" </object>"
" </child>"
" </object>"
"</interface>";
const gchar menus[] =
"<interface>"
" <menu id='app-menu'>"
" <section>"
" <item>"
" <attribute name='label'>Beep</attribute>"
" <attribute name='action'>app.beep</attribute>"
" </item>"
" <item>"
" <attribute name='label'>_Quit</attribute>"
" <attribute name='action'>app.quit</attribute>"
" </item>"
" </section>"
" </menu>"
" <menu id='menubar'>"
" <submenu>"
" <attribute name='label'>Menu1</attribute>"
" <section>"
" <item>"
" <attribute name='label'>More beep</attribute>"
" <attribute name='action'>win.beep</attribute>"
" </item>"
" </section>"
" </submenu>"
" </menu>"
"</interface>";
// *** Message methods
GtkWindow* Message::get_window(const Cgu::GobjHandle<GtkBuilder>& builder) {
GObject* win = gtk_builder_get_object(builder, "win");
if (!win) {
throw UiBuildError{"Message::get_window(): Can't find 'win' object"};
}
return GTK_WINDOW(win);
}
Cgu::GobjHandle<GtkBuilder> Message::build_interface() {
Cgu::GobjHandle<GtkBuilder> builder{gtk_builder_new()};
GError* err = 0;
if (!gtk_builder_add_from_string(builder, ui, sizeof(ui) - 1, &err)) {
std::string msg{"Message::build_interface: "};
msg += err->message;
throw UiBuildError{msg.c_str()};
}
return builder;
}
Message::Message(const char* text,
GApplication* app,
const Cgu::GobjHandle<GtkBuilder>& builder): Cgu::WinBase{"Message", 0,
false, 0,
get_window(builder)} {
// set the application property of the GtkApplicationWindow object
// to the underlying GtkApplication object before we add action
// entries - GtkBuilder cannot do this for us and sets the
// application property to NULL
g_object_set(get_win(),
"application", app,
static_cast<void*>(0));
g_action_map_add_action_entries(G_ACTION_MAP(get_win()),
win_entries,
G_N_ELEMENTS(win_entries),
static_cast<void*>(0));
GObject* component = gtk_builder_get_object(builder, "label");
if (!component) {
throw UiBuildError{"Message constructor: Can't find 'label' object"};
}
gtk_label_set_text(GTK_LABEL(component), text);
component = gtk_builder_get_object(builder, "button");
if (!component) {
throw UiBuildError{"Message constructor: Can't find 'button' object"};
}
g_signal_connect(component, "clicked",
G_CALLBACK(message_button_clicked), this);
gtk_widget_show_all(GTK_WIDGET(get_win()));
}
// *** Cgu::Application callbacks
void startup(Cgu::Application* app) {
g_action_map_add_action_entries(G_ACTION_MAP(app->get_g_app()),
app_entries,
G_N_ELEMENTS(app_entries),
app);
// we cannot propagate exceptions out of the signal handler, so use
// gtk_builder_new_from_string() and error out if loading fails
Cgu::GobjHandle<GtkBuilder> builder{gtk_builder_new_from_string(menus, -1)};
GObject* component = gtk_builder_get_object(builder, "app-menu");
if (!component) {
g_error("%s", "startup: Can't find 'app-menu' object");
}
gtk_application_set_app_menu(GTK_APPLICATION(app->get_g_app()),
G_MENU_MODEL(component));
component = gtk_builder_get_object(builder, "menubar");
if (!component) {
g_error("%s", "startup: Can't find 'menubar' object");
}
gtk_application_set_menubar(GTK_APPLICATION(app->get_g_app()),
G_MENU_MODEL(component));
}
void activate(Cgu::Application* app) {
if (app->get_win_count() > 0) {
gtk_window_present(app->get_windows().front()->get_win());
return;
}
// we cannot propagate exceptions out of the signal handler, so
// error out with g_error if there is an exception
Cgu::WinBase* dialog;
try {
dialog = new Message{"This is a message", app->get_g_app()};
}
catch (std::exception& e) {
g_error("%s", e.what());
}
dialog->show_all();
app->add(dialog);
}
// *** user code ***
int main(int argc, char* argv[]) {
Cgu::Application app{"my_prog", G_APPLICATION_FLAGS_NONE};
app.startup.connect(Cgu::Callback::make(startup));
app.activate.connect(Cgu::Callback::make(activate));
return app.run(0, 0);
}

Care must be taken if initializing a Cgu::GobjHandle object with a widget or GObject object obtained from GtkBuilder. It is necessary to call g_object_ref() by hand in that case, as the Cgu::GobjHandle constructor taking a pointer does not call g_object_ref_sink() if the initializing object does not have a floating reference, and GtkBuilder always sinks floating references itself.

Cgu::WinBase
This is a class for managing the lifetime of top level widgets.
Definition: window.h:226
Cgu
Definition: application.h:44
Cgu::Callback::make
CallbackArg< FreeArgs... > * make(T &t, void(T::*func)(FreeArgs...))
Definition: callback.h:1659
Cgu::GobjHandle
This is a handle for managing the reference count of GObjects.
Definition: gobj_handle.h:148
callback.h
This file provides classes for type erasure.
Cgu::Application
This is a class for constructing and managing GtkApplication objects.
Definition: application.h:409
Cgu::Application::add
void add(Cgu::WinBase *win)
Cgu::Application::startup
Cgu::SafeEmitterArg< Cgu::Application * > startup
Definition: application.h:462
gobj_handle.h
Cgu::Application::get_windows
std::list< Cgu::WinBase * > get_windows() const
Definition: application.h:659
Cgu::SafeEmitterArg::connect
Callback::SafeFunctorArg< FreeArgs... > connect(const Callback::SafeFunctorArg< FreeArgs... > &f)
Cgu::SharedHandle< gchar *, GFree >
Cgu::WinBase::show_all
void show_all()
Definition: window.h:326
shared_handle.h
Cgu::Application::get_win_count
size_type get_win_count() const
Definition: application.h:684
Cgu::Application::get_g_app
GApplication * get_g_app() const
Definition: application.h:642
window.h
Cgu::Application::run
int run(int argc, char **argv)
Definition: application.h:627
Cgu::SharedHandle::get
T get() const
Definition: shared_handle.h:765
Cgu::Application::activate
Cgu::SafeEmitterArg< Cgu::Application * > activate
Definition: application.h:442
application.h