c++-gtk-utils

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 constructs the top level window and all its children using GtkBuilder:

   #include <exception>
   #include <gtk/gtk.h>
   #include <c++-gtk-utils/window.h>
   #include <c++-gtk-utils/shared_handle.h>
   #include <c++-gtk-utils/gobj_handle.h>


   // *** class headers ***

   struct 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 memberer 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, const Cgu::GobjHandle<GtkBuilder>& = Message::build_interface());
   };

   // *** class implementation ***

   void message_button_clicked(GtkWidget*, void* data) {
     static_cast<Message*>(data)->close();
   }

   // 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='GtkWindow' 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>";

   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());
     if (!gtk_builder_add_from_string(builder, ui, sizeof(ui) - 1, 0)) {
       throw UiBuildError("Message::build_interface: Can't create interface from ui");
     }
     return builder;
   }

   Message::Message(const char* text,
                    const Cgu::GobjHandle<GtkBuilder>& builder): Cgu::WinBase("Message", 0,
                                                                              true, 0,
                                                                              get_window(builder)) {

     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()));
   }

   // *** user code ***

   int main(int argc, char* argv[]) {
     gtk_init(&argc, &argv);
     Message dialog("This is a message");
     dialog.exec();
     return 0;
   }

Secondly, an example which attaches a widget heirarchy constructed with GtkBuilder to a GtkWindow object generated by the Cgu::WinBase object:

   #include <exception>
   #include <gtk/gtk.h>
   #include <c++-gtk-utils/window.h>
   #include <c++-gtk-utils/shared_handle.h>
   #include <c++-gtk-utils/gobj_handle.h>


   // *** class headers ***

   struct 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);
   };

   // *** class implementation ***

   void message_button_clicked(GtkWidget*, void* data) {
     static_cast<Message*>(data)->close();
   }

   // 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='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>";

   Message::Message(const char* text): Cgu::WinBase("Message", 0, true) {

     Cgu::GobjHandle<GtkBuilder> builder(gtk_builder_new());
     if (!gtk_builder_add_from_string(builder, ui, sizeof(ui) - 1, 0)) {
       throw UiBuildError("Message::build_interface: Can't create interface from ui");
     }

     GObject* component = gtk_builder_get_object(builder, "vbox");
     gtk_container_add(GTK_CONTAINER(get_win()), GTK_WIDGET(component));

     component = gtk_builder_get_object(builder, "label");
     gtk_label_set_text(GTK_LABEL(component), text);

     component = gtk_builder_get_object(builder, "button");
     g_signal_connect(component, "clicked",
                      G_CALLBACK(message_button_clicked), this);
  
     gtk_widget_show_all(GTK_WIDGET(get_win()));
   }

   // *** user code ***

   int main(int argc, char* argv[]) {
     gtk_init(&argc, &argv);
     Message dialog("This is a message");
     dialog.exec();
     return 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.