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>
// *** 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::Application* app) {startup(app);});
app.activate.connect([] (Cgu::Application* app) {activate(app);});
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>
// *** 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::Application* app) {startup(app);});
app.activate.connect([] (Cgu::Application* app) {activate(app);});
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:227
Cgu
Definition: application.h:44
Cgu::GobjHandle
This is a handle for managing the reference count of GObjects.
Definition: gobj_handle.h:148
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:350
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 noexcept
Definition: shared_handle.h:765
Cgu::Application::activate
Cgu::SafeEmitterArg< Cgu::Application * > activate
Definition: application.h:442
application.h