Using .ui template with Gtkmm

Hello, everybody.

I’m trying to reuse a .UI file that I previously used with Gtk 4 (vanilla C), but this time around with Gtkmm.

The problem I’m getting is that, by following the Gtk::Builder part of the Gtkmm documentation, the file is being rejected with the following message:

BuilderError: src/app/res/ui/forms/qxplayer-window.ui:9:1 Template declaration (class 'QxplayerWindow', parent 'GtkApplicationWindow') where templates aren't supported

The following is my code, adapted from this example.

main.cpp
#include <gtkmm.h>
#include <iostream>

#include "app/qxplayer_window.hpp"

namespace {
Gtk::Window *pDialog = nullptr;
Glib::RefPtr<Gtk::Application> app;

void on_button_clicked() {
    if (pDialog)
        pDialog->set_visible(false); // set_visible(false) will cause
                                     // Gtk::Application::run() to end.
}

void on_app_activate() {
    // Load the GtkBuilder file and instantiate its widgets:
    auto refBuilder = Gtk::Builder::create();
    try {
        refBuilder->add_from_file("src/app/res/ui/forms/qxplayer-window.ui");
    } catch (const Glib::FileError &ex) {
        std::cerr << "FileError: " << ex.what() << std::endl;
        return;
    } catch (const Glib::MarkupError &ex) {
        std::cerr << "MarkupError: " << ex.what() << std::endl;
        return;
    } catch (const Gtk::BuilderError &ex) {
        std::cerr << "BuilderError: " << ex.what() << std::endl;
        return;
    }

    // Get the GtkBuilder-instantiated dialog:
    pDialog = Gtk::Builder::get_widget_derived<QxPlayerWindow>(
        refBuilder, "QxplayerWindow");
    if (!pDialog) {
        std::cerr << "Could not get the dialog" << std::endl;
        return;
    }

    app->add_window(*pDialog);
    pDialog->set_visible(true);
}
} // anonymous namespace

int main(int argc, char **argv) {
    app = Gtk::Application::create("org.gtkmm.example");

    // Instantiate a dialog when the application has been activated.
    // This can only be done after the application has been registered.
    // It's possible to call app->register_application() explicitly, but
    // usually it's easier to let app->run() do it for you.
    app->signal_activate().connect([]() { on_app_activate(); });

    return app->run(argc, argv);
}

Is this because Gtkmm’s Builder only supports Glade files? Am I missing something else?

I’ve already tried searching for this situation on Google and in this forum, but I didn’t find anything. Also, the API reference doesn’t mention this anywhere, from what I could see.

Thank you.

Edit: I forgot to add the .UI file

qxplayer_window.ui
<?xml version='1.0' encoding='UTF-8'?>
<!-- Created with Cambalache 0.10.3 -->
<interface>
  <!-- interface-name qxplayer.ui -->
  <!-- interface-authors Arthur Araruna <ararunaufc@gmail.com>
Alunos da disciplina QXD0010 - T01A - 2022.2 -->
  <requires lib="gtk" version="4.6"/>
  <template class="QxplayerWindow" parent="GtkApplicationWindow">
    <property name="height-request">400</property>
    <property name="width-request">600</property>
    <child type="titlebar">
      <object class="GtkHeaderBar">
        <child type="start">
          <object class="GtkButton" id="b_about">
            <property name="action-name">app.about</property>
            <property name="icon-name">help-about-symbolic</property>
            <property name="tooltip-text">Ver informações sobre o QxPlayer</property>
          </object>
        </child>
        <child type="start">
          <object class="GtkButton" id="b_shortcuts">
            <property name="action-name">win.show-help-overlay</property>
            <property name="has-tooltip">True</property>
            <property name="icon-name">preferences-desktop-keyboard-shortcuts-symbolic</property>
            <property name="tooltip-text">Ver atalhos de teclado</property>
          </object>
        </child>
        <child type="title">
          <object class="GtkBox">
            <property name="orientation">vertical</property>
            <property name="margin-top">3</property>
            <property name="margin-bottom">3</property>
            <property name="valign">GTK_ALIGN_CENTER</property>
            <child>
              <object class="GtkLabel" id="l_title">
                <property name="label">QxPlayer</property>
                <property name="css-classes">title</property>
              </object>
            </child>
            <child>
              <object class="GtkRevealer">
                <child>
                  <object class="GtkLabel" id="l_media_title">
                    <property name="label">(sem título)</property>
                    <property name="margin-top">2</property>
                    <style>
                      <class name="subtitle"/>
                      <class name="qx-media-title-empty"/>
                    </style>
                  </object>
                </child>
              </object>
            </child>
          </object>
        </child>
      </object>
    </child>
    <child>
      <object class="GtkGrid">
        <property name="column-spacing">5</property>
        <property name="margin-bottom">5</property>
        <property name="margin-end">5</property>
        <property name="margin-start">5</property>
        <property name="margin-top">5</property>
        <property name="row-spacing">10</property>
        <child>
          <object class="GtkInfoBar" id="ib_message">
            <property name="message-type">GTK_MESSAGE_WARNING</property>
            <property name="revealed">False</property>
            <property name="show-close-button">True</property>
            <child>
              <object class="GtkLabel" id="l_message">
                <property name="label">Mensagem de teste</property>
              </object>
            </child>
            <layout>
              <property name="column">0</property>
              <property name="column-span">2</property>
              <property name="row">0</property>
              <property name="row-span">1</property>
            </layout>
          </object>
        </child>
        <child>
          <object class="GtkBox">
            <property name="spacing">2</property>
            <child>
              <object class="GtkToggleButton" id="tb_playpause">
                <property name="action-name">app.playback-playing</property>
                <property name="has-tooltip">True</property>
                <property name="icon-name">media-playback-start-symbolic</property>
                <property name="tooltip-text">Iniciar / pausar reprodução</property>
                <property name="valign">start</property>
              </object>
            </child>
            <child>
              <object class="GtkButton" id="b_previous">
                <property name="action-name">app.playlist-prev</property>
                <property name="has-tooltip">True</property>
                <property name="icon-name">media-skip-backward-symbolic</property>
                <property name="tooltip-text">Reiniciar / reproduzir faixa anterior</property>
              </object>
            </child>
            <child>
              <object class="GtkButton" id="b_next">
                <property name="action-name">app.playlist-next</property>
                <property name="has-tooltip">True</property>
                <property name="icon-name">media-skip-forward-symbolic</property>
                <property name="tooltip-text">Reproduzir próxima faixa</property>
              </object>
            </child>
            <child>
              <object class="GtkLabel" id="l_audio_position">
                <property name="label">00:00</property>
                <property name="margin-start">5</property>
              </object>
            </child>
            <child>
              <object class="GtkScale" id="s_seek">
                <property name="hexpand">True</property>
                <property name="round-digits">0</property>
                <property name="adjustment">ad_seek</property>
                <property name="sensitive">False</property>
                <!-- <binding name="sensitive"> -->
                <!--   <lookup name="state"> -->
                <!--     <closure type="GSimpleAction" function="g_action_map_lookup_action"> -->
	               <!--    <closure type="GActionMap" function="g_application_get_default"></closure> -->
	               <!--    <constant type="gchararray">playback-playing</constant> -->
                <!--     </closure> -->
                <!--   </lookup> -->
                <!-- </binding> -->
              </object>
            </child>
            <child>
              <object class="GtkLabel" id="l_audio_duration">
                <property name="label">00:00</property>
                <property name="margin-end">5</property>
              </object>
            </child>
            <child>
              <object class="GtkToggleButton" id="tb_repeat">
                <property name="action-name">app.playback-repeating</property>
                <property name="has-tooltip">True</property>
                <property name="icon-name">media-playlist-repeat-symbolic</property>
                <property name="tooltip-text">Controla a repetição da reprodução</property>
              </object>
            </child>
            <child>
              <object class="GtkVolumeButton" id="vb_volume">
                <property name="use-symbolic">True</property>
                <property name="adjustment">ad_volume</property>
              </object>
            </child>
            <layout>
              <property name="column">0</property>
              <property name="column-span">2</property>
              <property name="row">2</property>
              <property name="row-span">1</property>
            </layout>
          </object>
        </child>
        <child>
          <object class="GtkBox">
            <property name="homogeneous">True</property>
            <property name="orientation">vertical</property>
            <property name="spacing">2</property>
            <property name="valign">start</property>
            <child>
              <object class="GtkButton" id="b_add_file">
                <property name="action-name">app.playlist-insert</property>
                <property name="has-tooltip">True</property>
                <property name="icon-name">list-add-symbolic</property>
                <property name="tooltip-text">Adicionar à playlist</property>
              </object>
            </child>
            <child>
              <object class="GtkButton" id="b_remove_file">
                <property name="action-name">app.playlist-remove</property>
                <property name="has-tooltip">True</property>
                <property name="icon-name">list-remove-symbolic</property>
                <property name="tooltip-text">Remover seleção da playlist</property>
              </object>
            </child>
            <layout>
              <property name="column">0</property>
              <property name="column-span">1</property>
              <property name="row">1</property>
              <property name="row-span">1</property>
            </layout>
          </object>
        </child>
        <child>
          <object class="GtkScrolledWindow">
            <property name="child">
              <object class="GtkListView" id="lv_playlist">
                <property name="css-classes">rich-list</property>
                <property name="hexpand">True</property>
                <property name="show-separators">True</property>
                <property name="vexpand">True</property>
                <signal name="activate" handler="item_activated_cb"/>
              </object>
            </property>
            <layout>
              <property name="column">1</property>
              <property name="column-span">1</property>
              <property name="row">1</property>
              <property name="row-span">1</property>
            </layout>
          </object>
        </child>
      </object>
    </child>
  </template>
  <object class="GtkAdjustment" id="ad_seek">
    <property name="value">0</property>
    <property name="lower">0</property>
    <property name="upper">1</property>
    <property name="step-increment">5</property>
    <property name="page-increment">10</property>
    <property name="page-size">0</property>
  </object>
  <object class="GtkAdjustment" id="ad_volume">
    <property name="value">1</property>
    <property name="lower">0</property>
    <property name="upper">1</property>
    <property name="step-increment">0.05</property>
    <property name="page-increment">0.1</property>
    <property name="page-size">0</property>
  </object>
</interface>

There are two elements in your .ui file that can’t be used with gtkmm’s Gtk::Builder.
One is template. The other one is signal.

1 Like

To add to that, you can only use GtkBuilder with <object> at the toplevel.

If you want to use <template> then you have to create a custom widget and set the template in the class init function, using gtk_widget_class_set_template or similar. There is no gtkmm binding for those functions so you will have to just call the C functions.

<signal> can be used but additional setup has to be done using GtkBuilderScope, which also has no gtkmm binding so you have bind everything you want as a C function and then call gtk_builder_set_scope to add them. And if you want to use that with templates, you will have to do similar with gtk_widget_class_bind_template_callback.

1 Like

The example program in gtk/examples has been translated to C++ in
gtkmm-documentation/examples/book/buildapp.

The window.ui file in gtk/examples/application9 corresponds to
the window.ui file
in gtkmm-documentation/examples/book/buildapp/step9.

  <template class="ExampleAppWindow" parent="GtkApplicationWindow">

has been replaced by

  <object class="GtkApplicationWindow" id="app_window">

and the signal elements have been replaced by calls to connect() methods in
exampleappwindow.cc. This is reasonably easy when you need only one instance
of the class. I suspect that it becomes more complicated if you need many instances
of your QxplayerWindow class.

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.