Composite widgets and alert dialog

The source files are in the Gtk4 tutorial GitHub repository. Download it and see src/tfe6 directory.

An outline of new Tfe text editor

Tfe text editor will be restructured. The program is divided into six parts.

This section describes TfeAlert. Others will be explained in the following sections.

Composite widgets

The alert dialog is like this:

Alert dialog

Tfe uses it when a user quits the application or closes a notebook without saving data to files.

The dialog has a title, buttons, an icon and a message. Therefore, it consists of several widgets. Such dialog is called a composite widget.

Composite widgets are defined with template XMLs. The class is built in the class initialization function and the instances are built and desposed by the following functions.

TfeAlert is a good example to know composite widgets. It is defined with the three files.

The XML file

A template tag is used in a composite widget XML.

<?xml version="1.0" encoding="UTF-8"?>
<interface>
  <template class="TfeAlert" parent="GtkWindow">
    <property name="resizable">FALSE</property>
    <property name="modal">TRUE</property>
    <property name="titlebar">
      <object class="GtkHeaderBar">
        <property name="show-title-buttons">FALSE</property>
        <property name="title-widget">
          <object class="GtkLabel" id="lb_title">
            <property name="label">Are you sure?</property>
            <property name="single-line-mode">True</property>
          </object>
        </property>
        <child type="start">
          <object class="GtkButton" id="btn_cancel">
            <property name="label">Cancel</property>
            <style>
              <class name="suggested-action"/>
            </style>
            <signal name="clicked" handler="cancel_cb" swapped="TRUE" object="TfeAlert"></signal>
          </object>
        </child>
        <child type="end">
          <object class="GtkButton" id="btn_accept">
            <property name="label">Close</property>
            <style>
              <class name="destructive-action"/>
            </style>
            <signal name="clicked" handler="accept_cb" swapped="TRUE" object="TfeAlert"></signal>
          </object>
        </child>
      </object>
    </property>
    <child>
      <object class="GtkBox">
        <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
        <property name="spacing">12</property>
        <property name="margin-top">12</property>
        <property name="margin-bottom">12</property>
        <property name="margin-start">12</property>
        <property name="margin-end">12</property>
        <child>
          <object class="GtkImage">
            <property name="icon-name">dialog-warning</property>
            <property name="icon-size">GTK_ICON_SIZE_LARGE</property>
          </object>
        </child>
        <child>
          <object class="GtkLabel" id="lb_message">
          </object>
        </child>
      </object>
    </child>
  </template>
</interface>
$ gtk4-icon-browser

The “dialog-warning” icon is something like this.

dialog-warning icon is like …

These are made by my hand. The real image on the alert dialog is nicer.

It is possible to define the alert widget as a child of GtkDialog. But GtkDialog is deprecated since GTK version 4.10. And users should use GtkWindow instead of GtkDialog.

The header file

The header file is similar to the one of TfeTextView.

#pragma once

#include <gtk/gtk.h>

#define TFE_TYPE_ALERT tfe_alert_get_type ()
G_DECLARE_FINAL_TYPE (TfeAlert, tfe_alert, TFE, ALERT, GtkWindow)

/* "response" signal id */
enum TfeAlertResponseType
{
  TFE_ALERT_RESPONSE_ACCEPT,
  TFE_ALERT_RESPONSE_CANCEL
};

const char *
tfe_alert_get_title (TfeAlert *alert);

const char *
tfe_alert_get_message (TfeAlert *alert);

const char *
tfe_alert_get_button_label (TfeAlert *alert);

void
tfe_alert_set_title (TfeAlert *alert, const char *title);

void
tfe_alert_set_message (TfeAlert *alert, const char *message);

void
tfe_alert_set_button_label (TfeAlert *alert, const char *btn_label);

GtkWidget *
tfe_alert_new (void);

GtkWidget *
tfe_alert_new_with_data (const char *title, const char *message, const char* btn_label);

The C file

Functions for composite widgets

The following codes are extracted from tfealert.c.

#include <gtk/gtk.h>
#include "tfealert.h"

struct _TfeAlert {
  GtkWindow parent;
  GtkLabel *lb_title;
  GtkLabel *lb_message;
  GtkButton *btn_accept;
  GtkButton *btn_cancel;
};

G_DEFINE_FINAL_TYPE (TfeAlert, tfe_alert, GTK_TYPE_WINDOW);

static void
cancel_cb (TfeAlert *alert) {
  ... ... ...
}

static void
accept_cb (TfeAlert *alert) {
  ... ... ...
}

static void
tfe_alert_dispose (GObject *gobject) { // gobject is actually a TfeAlert instance.
  gtk_widget_dispose_template (GTK_WIDGET (gobject), TFE_TYPE_ALERT);
  G_OBJECT_CLASS (tfe_alert_parent_class)->dispose (gobject);
}

static void
tfe_alert_init (TfeAlert *alert) {
  gtk_widget_init_template (GTK_WIDGET (alert));
}

static void
tfe_alert_class_init (TfeAlertClass *class) {
  G_OBJECT_CLASS (class)->dispose = tfe_alert_dispose;
  gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (class), "/com/github/ToshioCP/tfe/tfealert.ui");
  gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), TfeAlert, lb_title);
  gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), TfeAlert, lb_message);
  gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), TfeAlert, btn_accept);
  gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), TfeAlert, btn_cancel);
  gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), cancel_cb);
  gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), accept_cb);
  ... ... ...
}

GtkWidget *
tfe_alert_new (void) {
  return GTK_WIDGET (g_object_new (TFE_TYPE_ALERT, NULL));
}

Other functions

The following is the full codes of tfealert.c.

#include <gtk/gtk.h>
#include "tfealert.h"

struct _TfeAlert {
  GtkWindow parent;
  GtkLabel *lb_title;
  GtkLabel *lb_message;
  GtkButton *btn_accept;
  GtkButton *btn_cancel;
};

G_DEFINE_FINAL_TYPE (TfeAlert, tfe_alert, GTK_TYPE_WINDOW);

enum {
  RESPONSE,
  NUMBER_OF_SIGNALS
};

static guint tfe_alert_signals[NUMBER_OF_SIGNALS];

static void
cancel_cb (TfeAlert *alert) {
  g_signal_emit (alert, tfe_alert_signals[RESPONSE], 0, TFE_ALERT_RESPONSE_CANCEL);
  gtk_window_destroy (GTK_WINDOW (alert));
}

static void
accept_cb (TfeAlert *alert) {
  g_signal_emit (alert, tfe_alert_signals[RESPONSE], 0, TFE_ALERT_RESPONSE_ACCEPT);
  gtk_window_destroy (GTK_WINDOW (alert));
}

const char *
tfe_alert_get_title (TfeAlert *alert) {
  return gtk_label_get_text (alert->lb_title);
}

const char *
tfe_alert_get_message (TfeAlert *alert) {
    return gtk_label_get_text (alert->lb_message);
}

const char *
tfe_alert_get_button_label (TfeAlert *alert) {
  return gtk_button_get_label (alert->btn_accept);
}

void
tfe_alert_set_title (TfeAlert *alert, const char *title) {
  gtk_label_set_text (alert->lb_title, title);
}

void
tfe_alert_set_message (TfeAlert *alert, const char *message) {
  gtk_label_set_text (alert->lb_message, message);
}

void
tfe_alert_set_button_label (TfeAlert *alert, const char *btn_label) {
  gtk_button_set_label (alert->btn_accept, btn_label);
}

static void
tfe_alert_dispose (GObject *gobject) { // gobject is actually a TfeAlert instance.
  gtk_widget_dispose_template (GTK_WIDGET (gobject), TFE_TYPE_ALERT);
  G_OBJECT_CLASS (tfe_alert_parent_class)->dispose (gobject);
}

static void
tfe_alert_init (TfeAlert *alert) {
  gtk_widget_init_template (GTK_WIDGET (alert));
}

static void
tfe_alert_class_init (TfeAlertClass *class) {
  G_OBJECT_CLASS (class)->dispose = tfe_alert_dispose;
  gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (class), "/com/github/ToshioCP/tfe/tfealert.ui");
  gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), TfeAlert, lb_title);
  gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), TfeAlert, lb_message);
  gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), TfeAlert, btn_accept);
  gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), TfeAlert, btn_cancel);
  gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), cancel_cb);
  gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), accept_cb);

  tfe_alert_signals[RESPONSE] = g_signal_new ("response",
                                G_TYPE_FROM_CLASS (class),
                                G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
                                0 /* class offset */,
                                NULL /* accumulator */,
                                NULL /* accumulator data */,
                                NULL /* C marshaller */,
                                G_TYPE_NONE /* return_type */,
                                1     /* n_params */,
                                G_TYPE_INT
                                );
}

GtkWidget *
tfe_alert_new (void) {
  return GTK_WIDGET (g_object_new (TFE_TYPE_ALERT, NULL));
}

GtkWidget *
tfe_alert_new_with_data (const char *title, const char *message, const char* btn_label) {
  GtkWidget *alert = tfe_alert_new ();
  tfe_alert_set_title (TFE_ALERT (alert), title);
  tfe_alert_set_message (TFE_ALERT (alert), message);
  tfe_alert_set_button_label (TFE_ALERT (alert), btn_label);
  return alert;
}

The function tfe_alert_new_with_data is used more often than tfe_alert_new to create a new instance. It creates the instance and sets three data at the same time. The following is the common process when you use the TfeAlert class.

The rest of the program is:

An example

There’s an example in the src/tfe6/example directory. It shows how to use TfeAlert. The program is src/example/ex_alert.c.

#include <gtk/gtk.h>
#include "../tfealert.h"

static void
alert_response_cb (TfeAlert *alert, int response, gpointer user_data) {
  if (response == TFE_ALERT_RESPONSE_ACCEPT)
    g_print ("%s\n", tfe_alert_get_button_label (alert));
  else if (response == TFE_ALERT_RESPONSE_CANCEL)
    g_print ("Cancel\n");
  else
    g_print ("Unexpected error\n");
}

static void
app_activate (GApplication *application) {
  GtkWidget *alert;
  char *title, *message, *btn_label;

  title = "Example for TfeAlert"; message = "Click on Cancel or Accept button"; btn_label = "Accept";
  alert = tfe_alert_new_with_data (title, message, btn_label);
  g_signal_connect (TFE_ALERT (alert), "response", G_CALLBACK (alert_response_cb), NULL);
  gtk_window_set_application (GTK_WINDOW (alert), GTK_APPLICATION (application));
  gtk_window_present (GTK_WINDOW (alert));
}

static void
app_startup (GApplication *application) {
}

#define APPLICATION_ID "com.github.ToshioCP.example_tfe_alert"

int
main (int argc, char **argv) {
  GtkApplication *app;
  int stat;

  app = gtk_application_new (APPLICATION_ID, G_APPLICATION_DEFAULT_FLAGS);
  g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL);
  g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
  stat =g_application_run (G_APPLICATION (app), argc, argv);
  g_object_unref (app);
  return stat;
}

The “activate” signal handler app_activate initializes the alert dialog.

A user clicks on either the cancel button or the accept button. Then, the “response” signal is emitted and the dialog is destroyed. The signal handler alert_response_cb checks the response and prints “Accept” or “Cancel”. If an error happens, it prints “Unexpected error”.

You can compile it with meson and ninja.

$ cd src/tfe6/example
$ meson setup _build
$ ninja -C _build
$ _build/ex_alert
Accept #<= if you clicked on the accept button