Руководство по gtk

GTK is a widget toolkit.
Each user interface created by GTK consists of widgets. This is implemented
in C using GObject, an object-oriented framework for C. Widgets
are organized in a hierarchy. The window widget is the main container.
The user interface is then built by adding buttons, drop-down menus, input
fields, and other widgets to the window. If you are creating complex user
interfaces it is recommended to use GtkBuilder and its GTK-specific markup
description language, instead of assembling the interface manually.

GTK is event-driven. The toolkit listens for events such as a click
on a button, and passes the event to your application.

This chapter contains some tutorial information to get you started with
GTK programming. It assumes that you have GTK, its dependencies and a C
compiler installed and ready to use. If you need to build GTK itself first,
refer to the Compiling the GTK libraries section in this reference.

Basics

To begin our introduction to GTK, we’ll start with a very simple
application. This program will create an empty 200 × 200 pixel window.

A window

Create a new file with the following content named example-0.c.

#include <gtk/gtk.h>

static void
activate (GtkApplication* app,
          gpointer        user_data)
{
  GtkWidget *window;

  window = gtk_application_window_new (app);
  gtk_window_set_title (GTK_WINDOW (window), "Window");
  gtk_window_set_default_size (GTK_WINDOW (window), 200, 200);
  gtk_widget_show (window);
}

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

  app = gtk_application_new ("org.gtk.example", G_APPLICATION_DEFAULT_FLAGS);
  g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);
  status = g_application_run (G_APPLICATION (app), argc, argv);
  g_object_unref (app);

  return status;
}

You can compile the program above with GCC using:

gcc $( pkg-config --cflags gtk4 ) -o example-0 example-0.c $( pkg-config --libs gtk4 )

Note: If the above compilation does not work due to an error regarding G_APPLICATION_DEFAULT_FLAGS
this could be due to your OS providing an older version of GLib. For GLib versions older than 2.74 you
will need to replace G_APPLICATION_DEFAULT_FLAGS with G_APPLICATION_FLAGS_NONE in this example, and
others in this documentation.
For more information on how to compile a GTK application, please
refer to the Compiling GTK Applications
section in this reference.

All GTK applications will, of course, include gtk/gtk.h, which declares
functions, types and macros required by GTK applications.

Even if GTK installs multiple header files, only the top-level gtk/gtk.h
header can be directly included by third-party code. The compiler will abort
with an error if any other header is directly included.

In a GTK application, the purpose of the main() function is to create a
GtkApplication object and run it. In this example a
GtkApplication pointer named app is declared and then initialized
using gtk_application_new().

When creating a GtkApplication, you need to pick an application
identifier (a name) and pass it to gtk_application_new() as parameter. For
this example org.gtk.example is used. For choosing an identifier for your
application, see this guide.
Lastly, gtk_application_new() takes GApplicationFlags as input
for your application, if your application would have special needs.

Next the activate signal is
connected to the activate() function above the main() function. The activate
signal will be emitted when your application is launched with g_application_run()
on the line below. The g_application_run() call also takes as arguments the
command line arguments (the argc count and the argv string array).
Your application can override the command line handling, e.g. to open
files passed on the commandline.

Within g_application_run() the activate signal is sent and we then proceed
into the activate() function of the application. This is where we construct
our GTK window, so that a window is shown when the application is launched.
The call to gtk_application_window_new() will create a new
GtkApplicationWindow and store it inside the window pointer. The
window will have a frame, a title bar, and window controls depending on the platform.

A window title is set using gtk_window_set_title(). This function
takes a GtkWindow pointer and a string as input. As our window pointer
is a GtkWidget pointer, we need to cast it to GtkWindow; instead of
casting window via a typical C cast like (GtkWindow*), window can be
cast using the macro GTK_WINDOW(). GTK_WINDOW() will check if the
pointer is an instance of the GtkWindow class, before casting, and emit a
warning if the check fails. More information about this convention can be
found in the GObject documentation.

Finally the window size is set using gtk_window_set_default_size()
and the window is then shown by GTK via gtk_widget_show().

When you close the window, by (for example) pressing the X button, the
g_application_run() call returns with a number which is saved inside an
integer variable named status. Afterwards, the GtkApplication object is
freed from memory with g_object_unref(). Finally the status integer is
returned and the application exits.

While the program is running, GTK is receiving events. These are typically
input events caused by the user interacting with your program, but also things
like messages from the window manager or other applications. GTK processes
these and as a result, signals may be emitted on your widgets. Connecting
handlers for these signals is how you normally make your program do something
in response to user input.

The following example is slightly more complex, and tries to
showcase some of the capabilities of GTK.

Hello, World

In the long tradition of programming languages and libraries,
this example is called Hello, World.

Hello, world

Hello World in C

Create a new file with the following content named example-1.c.

#include <gtk/gtk.h>

static void
print_hello (GtkWidget *widget,
             gpointer   data)
{
  g_print ("Hello World\n");
}

static void
activate (GtkApplication *app,
          gpointer        user_data)
{
  GtkWidget *window;
  GtkWidget *button;
  GtkWidget *box;

  window = gtk_application_window_new (app);
  gtk_window_set_title (GTK_WINDOW (window), "Window");
  gtk_window_set_default_size (GTK_WINDOW (window), 200, 200);

  box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
  gtk_widget_set_halign (box, GTK_ALIGN_CENTER);
  gtk_widget_set_valign (box, GTK_ALIGN_CENTER);

  gtk_window_set_child (GTK_WINDOW (window), box);

  button = gtk_button_new_with_label ("Hello World");

  g_signal_connect (button, "clicked", G_CALLBACK (print_hello), NULL);
  g_signal_connect_swapped (button, "clicked", G_CALLBACK (gtk_window_destroy), window);

  gtk_box_append (GTK_BOX (box), button);

  gtk_widget_show (window);
}

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

  app = gtk_application_new ("org.gtk.example", G_APPLICATION_DEFAULT_FLAGS);
  g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);
  status = g_application_run (G_APPLICATION (app), argc, argv);
  g_object_unref (app);

  return status;
}

You can compile the program above with GCC using:

gcc $( pkg-config --cflags gtk4 ) -o example-1 example-1.c $( pkg-config --libs gtk4 )

As seen above, example-1.c builds further upon example-0.c by adding a
button to our window, with the label “Hello World”. Two new GtkWidget
pointers are declared to accomplish this, button and box. The box
variable is created to store a GtkBox, which is GTK’s way of
controlling the size and layout of buttons.

The GtkBox widget is created with gtk_box_new(), which takes a
GtkOrientation enumeration value as parameter. The buttons which
this box will contain can either be laid out horizontally or vertically.
This does not matter in this particular case, as we are dealing with only
one button. After initializing box with the newly created GtkBox, the code
adds the box widget to the window widget using gtk_window_set_child().

Next the button variable is initialized in similar manner.
gtk_button_new_with_label() is called which returns a
GtkButton to be stored in button. Afterwards button is added to
our box.

Using g_signal_connect(), the button is connected to a function in our app called
print_hello(), so that when the button is clicked, GTK will call this function.
As the print_hello() function does not use any data as input, NULL is passed
to it. print_hello() calls g_print() with the string “Hello World” which will
print Hello World in a terminal if the GTK application was started from one.

After connecting print_hello(), another signal is connected to the “clicked”
state of the button using g_signal_connect_swapped(). This functions is similar
to a g_signal_connect(), with the difference lying in how the callback function
is treated; g_signal_connect_swapped() allows you to specify what the callback
function should take as parameter by letting you pass it as data. In this case
the function being called back is gtk_window_destroy() and the window pointer
is passed to it. This has the effect that when the button is clicked, the whole
GTK window is destroyed. In contrast if a normal g_signal_connect() were used
to connect the “clicked” signal with gtk_window_destroy(), then the function
would be called on button (which would not go well, since the function expects
a GtkWindow as argument).

More information about creating buttons can be found
here.

The rest of the code in example-1.c is identical to example-0.c. The next
section will elaborate further on how to add several GtkWidgets to your
GTK application.

Packing

When creating an application, you’ll want to put more than one widget inside
a window. When you do so, it becomes important to control how each widget is
positioned and sized. This is where packing comes in.

GTK comes with a large variety of layout containers whose purpose it
is to control the layout of the child widgets that are added to them, like:

  • GtkBox
  • GtkGrid
  • GtkRevealer
  • GtkStack
  • GtkOverlay
  • GtkPaned
  • GtkExpander
  • GtkFixed

The following example shows how the GtkGrid container lets you
arrange several buttons:

Grid packing

Packing buttons

Create a new file with the following content named example-2.c.

#include <gtk/gtk.h>

static void
print_hello (GtkWidget *widget,
             gpointer   data)
{
  g_print ("Hello World\n");
}

static void
activate (GtkApplication *app,
          gpointer        user_data)
{
  GtkWidget *window;
  GtkWidget *grid;
  GtkWidget *button;

  /* create a new window, and set its title */
  window = gtk_application_window_new (app);
  gtk_window_set_title (GTK_WINDOW (window), "Window");

  /* Here we construct the container that is going pack our buttons */
  grid = gtk_grid_new ();

  /* Pack the container in the window */
  gtk_window_set_child (GTK_WINDOW (window), grid);

  button = gtk_button_new_with_label ("Button 1");
  g_signal_connect (button, "clicked", G_CALLBACK (print_hello), NULL);

  /* Place the first button in the grid cell (0, 0), and make it fill
   * just 1 cell horizontally and vertically (ie no spanning)
   */
  gtk_grid_attach (GTK_GRID (grid), button, 0, 0, 1, 1);

  button = gtk_button_new_with_label ("Button 2");
  g_signal_connect (button, "clicked", G_CALLBACK (print_hello), NULL);

  /* Place the second button in the grid cell (1, 0), and make it fill
   * just 1 cell horizontally and vertically (ie no spanning)
   */
  gtk_grid_attach (GTK_GRID (grid), button, 1, 0, 1, 1);

  button = gtk_button_new_with_label ("Quit");
  g_signal_connect_swapped (button, "clicked", G_CALLBACK (gtk_window_destroy), window);

  /* Place the Quit button in the grid cell (0, 1), and make it
   * span 2 columns.
   */
  gtk_grid_attach (GTK_GRID (grid), button, 0, 1, 2, 1);

  gtk_widget_show (window);

}

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

  app = gtk_application_new ("org.gtk.example", G_APPLICATION_DEFAULT_FLAGS);
  g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);
  status = g_application_run (G_APPLICATION (app), argc, argv);
  g_object_unref (app);

  return status;
}

You can compile the program above with GCC using:

gcc $( pkg-config --cflags gtk4 ) -o example-2 example-2.c $( pkg-config --libs gtk4 )

Custom Drawing

Many widgets, like buttons, do all their drawing themselves. You just tell
them the label you want to see, and they figure out what font to use, draw
the button outline and focus rectangle, etc. Sometimes, it is necessary to
do some custom drawing. In that case, a GtkDrawingArea might be the right
widget to use. It offers a canvas on which you can draw by setting its
draw function.

The contents of a widget often need to be partially or fully redrawn, e.g.
when another window is moved and uncovers part of the widget, or when the
window containing it is resized. It is also possible to explicitly cause a
widget to be redrawn, by calling gtk_widget_queue_draw(). GTK takes
care of most of the details by providing a ready-to-use cairo context to the
draw function.

The following example shows how to use a draw function with GtkDrawingArea.
It is a bit more complicated than the previous examples, since it also
demonstrates input event handling with event controllers.

Drawing

Drawing in response to input

Create a new file with the following content named example-3.c.

#include <gtk/gtk.h>

/* Surface to store current scribbles */
static cairo_surface_t *surface = NULL;

static void
clear_surface (void)
{
  cairo_t *cr;

  cr = cairo_create (surface);

  cairo_set_source_rgb (cr, 1, 1, 1);
  cairo_paint (cr);

  cairo_destroy (cr);
}

/* Create a new surface of the appropriate size to store our scribbles */
static void
resize_cb (GtkWidget *widget,
           int        width,
           int        height,
           gpointer   data)
{
  if (surface)
    {
      cairo_surface_destroy (surface);
      surface = NULL;
    }

  if (gtk_native_get_surface (gtk_widget_get_native (widget)))
    {
      surface = gdk_surface_create_similar_surface (gtk_native_get_surface (gtk_widget_get_native (widget)),
                                                    CAIRO_CONTENT_COLOR,
                                                    gtk_widget_get_width (widget),
                                                    gtk_widget_get_height (widget));

      /* Initialize the surface to white */
      clear_surface ();
    }
}

/* Redraw the screen from the surface. Note that the draw
 * callback receives a ready-to-be-used cairo_t that is already
 * clipped to only draw the exposed areas of the widget
 */
static void
draw_cb (GtkDrawingArea *drawing_area,
         cairo_t        *cr,
         int             width,
         int             height,
         gpointer        data)
{
  cairo_set_source_surface (cr, surface, 0, 0);
  cairo_paint (cr);
}

/* Draw a rectangle on the surface at the given position */
static void
draw_brush (GtkWidget *widget,
            double     x,
            double     y)
{
  cairo_t *cr;

  /* Paint to the surface, where we store our state */
  cr = cairo_create (surface);

  cairo_rectangle (cr, x - 3, y - 3, 6, 6);
  cairo_fill (cr);

  cairo_destroy (cr);

  /* Now invalidate the drawing area. */
  gtk_widget_queue_draw (widget);
}

static double start_x;
static double start_y;

static void
drag_begin (GtkGestureDrag *gesture,
            double          x,
            double          y,
            GtkWidget      *area)
{
  start_x = x;
  start_y = y;

  draw_brush (area, x, y);
}

static void
drag_update (GtkGestureDrag *gesture,
             double          x,
             double          y,
             GtkWidget      *area)
{
  draw_brush (area, start_x + x, start_y + y);
}

static void
drag_end (GtkGestureDrag *gesture,
          double          x,
          double          y,
          GtkWidget      *area)
{
  draw_brush (area, start_x + x, start_y + y);
}

static void
pressed (GtkGestureClick *gesture,
         int              n_press,
         double           x,
         double           y,
         GtkWidget       *area)
{
  clear_surface ();
  gtk_widget_queue_draw (area);
}

static void
close_window (void)
{
  if (surface)
    cairo_surface_destroy (surface);
}

static void
activate (GtkApplication *app,
          gpointer        user_data)
{
  GtkWidget *window;
  GtkWidget *frame;
  GtkWidget *drawing_area;
  GtkGesture *drag;
  GtkGesture *press;

  window = gtk_application_window_new (app);
  gtk_window_set_title (GTK_WINDOW (window), "Drawing Area");

  g_signal_connect (window, "destroy", G_CALLBACK (close_window), NULL);

  frame = gtk_frame_new (NULL);
  gtk_window_set_child (GTK_WINDOW (window), frame);

  drawing_area = gtk_drawing_area_new ();
  /* set a minimum size */
  gtk_widget_set_size_request (drawing_area, 100, 100);

  gtk_frame_set_child (GTK_FRAME (frame), drawing_area);

  gtk_drawing_area_set_draw_func (GTK_DRAWING_AREA (drawing_area), draw_cb, NULL, NULL);

  g_signal_connect_after (drawing_area, "resize", G_CALLBACK (resize_cb), NULL);

  drag = gtk_gesture_drag_new ();
  gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (drag), GDK_BUTTON_PRIMARY);
  gtk_widget_add_controller (drawing_area, GTK_EVENT_CONTROLLER (drag));
  g_signal_connect (drag, "drag-begin", G_CALLBACK (drag_begin), drawing_area);
  g_signal_connect (drag, "drag-update", G_CALLBACK (drag_update), drawing_area);
  g_signal_connect (drag, "drag-end", G_CALLBACK (drag_end), drawing_area);

  press = gtk_gesture_click_new ();
  gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (press), GDK_BUTTON_SECONDARY);
  gtk_widget_add_controller (drawing_area, GTK_EVENT_CONTROLLER (press));

  g_signal_connect (press, "pressed", G_CALLBACK (pressed), drawing_area);

  gtk_widget_show (window);
}

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

  app = gtk_application_new ("org.gtk.example", G_APPLICATION_DEFAULT_FLAGS);
  g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);
  status = g_application_run (G_APPLICATION (app), argc, argv);
  g_object_unref (app);

  return status;
}

You can compile the program above with GCC using:

gcc $( pkg-config --cflags gtk4 ) -o example-3 example-3.c $( pkg-config --libs gtk4 )

Building user interfaces

When constructing a more complicated user interface, with dozens
or hundreds of widgets, doing all the setup work in C code is
cumbersome, and making changes becomes next to impossible.

Thankfully, GTK supports the separation of user interface
layout from your business logic, by using UI descriptions in an
XML format that can be parsed by the GtkBuilder class.

Packing buttons with GtkBuilder

Create a new file with the following content named example-4.c.

#include <gtk/gtk.h>
#include <glib/gstdio.h>

static void
print_hello (GtkWidget *widget,
             gpointer   data)
{
  g_print ("Hello World\n");
}

static void
quit_cb (GtkWindow *window)
{
  gtk_window_close (window);
}

static void
activate (GtkApplication *app,
          gpointer        user_data)
{
  /* Construct a GtkBuilder instance and load our UI description */
  GtkBuilder *builder = gtk_builder_new ();
  gtk_builder_add_from_file (builder, "builder.ui", NULL);

  /* Connect signal handlers to the constructed widgets. */
  GObject *window = gtk_builder_get_object (builder, "window");
  gtk_window_set_application (GTK_WINDOW (window), app);

  GObject *button = gtk_builder_get_object (builder, "button1");
  g_signal_connect (button, "clicked", G_CALLBACK (print_hello), NULL);

  button = gtk_builder_get_object (builder, "button2");
  g_signal_connect (button, "clicked", G_CALLBACK (print_hello), NULL);

  button = gtk_builder_get_object (builder, "quit");
  g_signal_connect_swapped (button, "clicked", G_CALLBACK (quit_cb), window);

  gtk_widget_show (GTK_WIDGET (window));

  /* We do not need the builder any more */
  g_object_unref (builder);
}

int
main (int   argc,
      char *argv[])
{
#ifdef GTK_SRCDIR
  g_chdir (GTK_SRCDIR);
#endif

  GtkApplication *app = gtk_application_new ("org.gtk.example", G_APPLICATION_DEFAULT_FLAGS);
  g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);

  int status = g_application_run (G_APPLICATION (app), argc, argv);
  g_object_unref (app);

  return status;
}

Create a new file with the following content named builder.ui.

<?xml version="1.0" encoding="UTF-8"?>
<interface>
  <object id="window" class="GtkWindow">
    <property name="title">Grid</property>
    <child>
      <object id="grid" class="GtkGrid">
        <child>
          <object id="button1" class="GtkButton">
            <property name="label">Button 1</property>
            <layout>
              <property name="column">0</property>
              <property name="row">0</property>
            </layout>
          </object>
        </child>
        <child>
          <object id="button2" class="GtkButton">
            <property name="label">Button 2</property>
            <layout>
              <property name="column">1</property>
              <property name="row">0</property>
            </layout>
          </object>
        </child>
        <child>
          <object id="quit" class="GtkButton">
            <property name="label">Quit</property>
            <layout>
              <property name="column">0</property>
              <property name="row">1</property>
              <property name="column-span">2</property>
            </layout>
          </object>
        </child>
      </object>
    </child>
  </object>
</interface>

You can compile the program above with GCC using:

gcc $( pkg-config --cflags gtk4 ) -o example-4 example-4.c $( pkg-config --libs gtk4 )

Note that GtkBuilder can also be used to construct objects that are
not widgets, such as tree models, adjustments, etc. That is the reason
the method we use here is called gtk_builder_get_object() and
returns a GObject instead of a GtkWidget.

Normally, you would pass a full path to gtk_builder_add_from_file() to
make the execution of your program independent of the current directory.
A common location to install UI descriptions and similar data is
/usr/share/appname.

It is also possible to embed the UI description in the source code as a
string and use gtk_builder_add_from_string() to load it. But keeping
the UI description in a separate file has several advantages:

  • it is possible to make minor adjustments to the UI without recompiling your program
  • it is easier to isolate the UI code from the business logic of your application
  • it is easier to restructure your UI into separate classes using composite
    widget templates

Using GResource it is possible
to combine the best of both worlds: you can keep the UI definition files
separate inside your source code repository, and then ship them embedded into
your application.

Building applications

An application consists of a number of files:

The binary
This gets installed in /usr/bin.
A desktop file
The desktop file provides important information about the application to
the desktop shell, such as its name, icon, D-Bus name, commandline to launch
it, etc. It is installed in /usr/share/applications.
An icon
The icon gets installed in /usr/share/icons/hicolor/48x48/apps, where it
will be found regardless of the current theme.
A settings schema
If the application uses GSettings, it will install its schema in
/usr/share/glib-2.0/schemas, so that tools like dconf-editor can find it.
Other resources
Other files, such as GtkBuilder ui files, are best loaded from
resources stored in the application binary itself. This eliminates the
need for most of the files that would traditionally be installed in
an application-specific location in /usr/share.

GTK includes application support that is built on top of GApplication. In this
tutorial we’ll build a simple application by starting from scratch, adding more
and more pieces over time. Along the way, we’ll learn about GtkApplication,
templates, resources, application menus, settings, GtkHeaderBar, GtkStack,
GtkSearchBar, GtkListBox, and more.

The full, buildable sources for these examples can be found in the
examples directory of the GTK source distribution, or
online in the GTK
source code repository. You can build each example separately by using make
with the Makefile.example file. For more information, see the README
included in the examples directory.

A trivial application

When using GtkApplication, the main() function can be very simple. We just call
g_application_run() and give it an instance of our application class.

#include <gtk/gtk.h>

#include "exampleapp.h"

int
main (int argc, char *argv[])
{
  return g_application_run (G_APPLICATION (example_app_new ()), argc, argv);
}

All the application logic is in the application class, which is a subclass of
GtkApplication. Our example does not yet have any interesting functionality.
All it does is open a window when it is activated without arguments, and open
the files it is given, if it is started with arguments.

To handle these two cases, we override the activate() vfunc, which gets
called when the application is launched without commandline arguments, and
the open() virtual function, which gets called when the application is
launched with commandline arguments.

To learn more about GApplication entry points, consult the GIO
documentation.

#include <gtk/gtk.h>

#include "exampleapp.h"
#include "exampleappwin.h"

struct _ExampleApp
{
  GtkApplication parent;
};

G_DEFINE_TYPE(ExampleApp, example_app, GTK_TYPE_APPLICATION);

static void
example_app_init (ExampleApp *app)
{
}

static void
example_app_activate (GApplication *app)
{
  ExampleAppWindow *win;

  win = example_app_window_new (EXAMPLE_APP (app));
  gtk_window_present (GTK_WINDOW (win));
}

static void
example_app_open (GApplication  *app,
                  GFile        **files,
                  int            n_files,
                  const char    *hint)
{
  GList *windows;
  ExampleAppWindow *win;
  int i;

  windows = gtk_application_get_windows (GTK_APPLICATION (app));
  if (windows)
    win = EXAMPLE_APP_WINDOW (windows->data);
  else
    win = example_app_window_new (EXAMPLE_APP (app));

  for (i = 0; i < n_files; i++)
    example_app_window_open (win, files[i]);

  gtk_window_present (GTK_WINDOW (win));
}

static void
example_app_class_init (ExampleAppClass *class)
{
  G_APPLICATION_CLASS (class)->activate = example_app_activate;
  G_APPLICATION_CLASS (class)->open = example_app_open;
}

ExampleApp *
example_app_new (void)
{
  return g_object_new (EXAMPLE_APP_TYPE,
                       "application-id", "org.gtk.exampleapp",
                       "flags", G_APPLICATION_HANDLES_OPEN,
                       NULL);
}

Another important class that is part of the application support in GTK is
GtkApplicationWindow. It is typically subclassed as well. Our
subclass does not do anything yet, so we will just get an empty window.

#include <gtk/gtk.h>

#include "exampleapp.h"
#include "exampleappwin.h"

struct _ExampleAppWindow
{
  GtkApplicationWindow parent;
};

G_DEFINE_TYPE(ExampleAppWindow, example_app_window, GTK_TYPE_APPLICATION_WINDOW);

static void
example_app_window_init (ExampleAppWindow *app)
{
}

static void
example_app_window_class_init (ExampleAppWindowClass *class)
{
}

ExampleAppWindow *
example_app_window_new (ExampleApp *app)
{
  return g_object_new (EXAMPLE_APP_WINDOW_TYPE, "application", app, NULL);
}

void
example_app_window_open (ExampleAppWindow *win,
                         GFile            *file)
{
}

As part of the initial setup of our application, we also
create an icon and a desktop file.

An icon

[Desktop Entry]
Type=Application
Name=Example
Icon=exampleapp
StartupNotify=true
Exec=@bindir@/exampleapp

Note that `bindir@` needs to be replaced with the actual path to the binary
before this desktop file can be used.

Here is what we’ve achieved so far:

An application

This does not look very impressive yet, but our application is already
presenting itself on the session bus, it has single-instance semantics,
and it accepts files as commandline arguments.

Populating the window

In this step, we use a GtkBuilder template to associate a
GtkBuilder ui file with our application window class.

Our simple ui file gives the window a title, and puts a GtkStack
widget as the main content.

<?xml version="1.0" encoding="UTF-8"?>
<interface>
  <template class="ExampleAppWindow" parent="GtkApplicationWindow">
    <property name="title" translatable="yes">Example Application</property>
    <property name="default-width">600</property>
    <property name="default-height">400</property>
    <child>
      <object class="GtkBox" id="content_box">
        <property name="orientation">vertical</property>
        <child>
          <object class="GtkStack" id="stack"/>
        </child>
      </object>
    </child>
  </template>
</interface>

To make use of this file in our application, we revisit our
GtkApplicationWindow subclass, and call
gtk_widget_class_set_template_from_resource() from the class init
function to set the ui file as template for this class. We also
add a call to gtk_widget_init_template() in the instance init
function to instantiate the template for each instance of our class.

 ...

static void
example_app_window_init (ExampleAppWindow *win)
{
  gtk_widget_init_template (GTK_WIDGET (win));
}

static void
example_app_window_class_init (ExampleAppWindowClass *class)
{
  gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (class),
                                               "/org/gtk/exampleapp/window.ui");
}

 ...

(full source)

You may have noticed that we used the _from_resource() variant of the function
that sets a template. Now we need to use
GLib’s resource functionality
to include the ui file in the binary. This is commonly done by listing all resources
in a .gresource.xml file, such as this:

<?xml version="1.0" encoding="UTF-8"?>
<gresources>
  <gresource prefix="/org/gtk/exampleapp">
    <file preprocess="xml-stripblanks">window.ui</file>
  </gresource>
</gresources>

This file has to be converted into a C source file that will be compiled and linked
into the application together with the other source files. To do so, we use the
glib-compile-resources utility:

glib-compile-resources exampleapp.gresource.xml --target=resources.c --generate-source

The gnome module of the Meson build system
provides the gnome.compile_resources()
method for this task.

Our application now looks like this:

The application

Opening files

In this step, we make our application show the content of all the files
that it is given on the commandline.

Note: Providing filenames (e.g. ./exampleapp examplewin.c examplewin.h) at
the command line is a requirement for example apps 3-9 to display as shown in
the screenshots below.

To this end, we add a member to the struct of our application window subclass
and keep a reference to the GtkStack there. The first member of the struct
should be the parent type from which the class is derived. Here,
ExampleAppWindow is derived from GtkApplicationWindow. The
gtk_widget_class_bind_template_child() function arranges things so that after
instantiating the template, the stack member of the struct will point to the
widget of the same name from the template.

...

struct _ExampleAppWindow
{
  GtkApplicationWindow parent;

  GtkWidget *stack;
};

G_DEFINE_TYPE (ExampleAppWindow, example_app_window, GTK_TYPE_APPLICATION_WINDOW)

...

static void
example_app_window_class_init (ExampleAppWindowClass *class)
{
  gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (class),
                                               "/org/gtk/exampleapp/window.ui");
  gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), ExampleAppWindow, stack);
}

...

(full source)

Now we revisit the example_app_window_open() function that is called for each
commandline argument, and construct a GtkTextView that we then add as a page
to the stack:

...

void
example_app_window_open (ExampleAppWindow *win,
                         GFile            *file)
{
  char *basename;
  GtkWidget *scrolled, *view;
  char *contents;
  gsize length;

  basename = g_file_get_basename (file);

  scrolled = gtk_scrolled_window_new ();
  gtk_widget_set_hexpand (scrolled, TRUE);
  gtk_widget_set_vexpand (scrolled, TRUE);
  view = gtk_text_view_new ();
  gtk_text_view_set_editable (GTK_TEXT_VIEW (view), FALSE);
  gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (view), FALSE);
  gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scrolled), view);
  gtk_stack_add_titled (GTK_STACK (win->stack), scrolled, basename, basename);

  if (g_file_load_contents (file, NULL, &contents, &length, NULL, NULL))
    {
      GtkTextBuffer *buffer;

      buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
      gtk_text_buffer_set_text (buffer, contents, length);
      g_free (contents);
    }

  g_free (basename);
}

...

(full source)

Lastly, we add a GtkStackSwitcher to the titlebar area in the UI file, and we
tell it to display information about our stack.

The stack switcher gets all its information it needs to display tabs from
the stack that it belongs to. Here, we are passing the label to show for
each file as the last argument to the gtk_stack_add_titled() function.

Our application is beginning to take shape:

Application window

The menu is shown at the right side of the headerbar. It is meant to collect
infrequently used actions that affect the whole application.

Just like the window template, we specify our menu in a ui file, and add it
as a resource to our binary.

<?xml version="1.0" encoding="UTF-8"?>
<interface>
  <menu id="menu">
    <section>
      <item>
        <attribute name="label" translatable="yes">_Preferences</attribute>
        <attribute name="action">app.preferences</attribute>
      </item>
    </section>
    <section>
      <item>
        <attribute name="label" translatable="yes">_Quit</attribute>
        <attribute name="action">app.quit</attribute>
      </item>
    </section>
  </menu>
</interface>

To make the menu appear, we have to load the ui file and associate the
resulting menu model with the menu button that we’ve added to the headerbar.
Since menus work by activating GActions, we also have to add a suitable set
of actions to our application.

Adding the actions is best done in the startup() vfunc, which is guaranteed
to be called once for each primary application instance:

...

static void
preferences_activated (GSimpleAction *action,
                       GVariant      *parameter,
                       gpointer       app)
{
}

static void
quit_activated (GSimpleAction *action,
                GVariant      *parameter,
                gpointer       app)
{
  g_application_quit (G_APPLICATION (app));
}

static GActionEntry app_entries[] =
{
  { "preferences", preferences_activated, NULL, NULL, NULL },
  { "quit", quit_activated, NULL, NULL, NULL }
};

static void
example_app_startup (GApplication *app)
{
  GtkBuilder *builder;
  GMenuModel *app_menu;
  const char *quit_accels[2] = { "&lt;Ctrl&gt;Q", NULL };

  G_APPLICATION_CLASS (example_app_parent_class)->startup (app);

  g_action_map_add_action_entries (G_ACTION_MAP (app),
                                   app_entries, G_N_ELEMENTS (app_entries),
                                   app);
  gtk_application_set_accels_for_action (GTK_APPLICATION (app),
                                         "app.quit",
                                         quit_accels);
}

static void
example_app_class_init (ExampleAppClass *class)
{
  G_APPLICATION_CLASS (class)->startup = example_app_startup;
  ...
}

...

(full source)

Our preferences menu item does not do anything yet, but the Quit menu item
is fully functional. Note that it can also be activated by the usual Ctrl-Q
shortcut. The shortcut was added with gtk_application_set_accels_for_action().

The application menu looks like this:

Application window

A preference dialog

A typical application will have a some preferences that should be remembered
from one run to the next. Even for our simple example application, we may
want to change the font that is used for the content.

We are going to use GSettings to store our preferences. GSettings requires
a schema that describes our settings:

<?xml version="1.0" encoding="UTF-8"?>
<schemalist>
  <schema path="/org/gtk/exampleapp/" id="org.gtk.exampleapp">
    <key name="font" type="s">
      <default>'Monospace 12'</default>
      <summary>Font</summary>
      <description>The font to be used for content.</description>
    </key>
    <key name="transition" type="s">
      <choices>
        <choice value='none'/>
        <choice value='crossfade'/>
        <choice value='slide-left-right'/>
      </choices>
      <default>'none'</default>
      <summary>Transition</summary>
      <description>The transition to use when switching tabs.</description>
    </key>
  </schema>
</schemalist>

Before we can make use of this schema in our application, we need to compile
it into the binary form that GSettings expects. GIO provides macros to do
this in Autotools-based projects, and the gnome module of the Meson build
system provides the gnome.compile_schemas()
method for this task.

Next, we need to connect our settings to the widgets that they are supposed
to control. One convenient way to do this is to use GSettings bind
functionality to bind settings keys to object properties, as we do here
for the transition setting.

...

static void
example_app_window_init (ExampleAppWindow *win)
{
  gtk_widget_init_template (GTK_WIDGET (win));
  win->settings = g_settings_new ("org.gtk.exampleapp");

  g_settings_bind (win->settings, "transition",
                   win->stack, "transition-type",
                   G_SETTINGS_BIND_DEFAULT);
}

...

(full source)

The code to connect the font setting is a little more involved, since there
is no simple object property that it corresponds to, so we are not going to
go into that here.

At this point, the application will already react if you change one of the
settings, e.g. using the gsettings command line tool. Of course, we expect
the application to provide a preference dialog for these. So lets do that
now. Our preference dialog will be a subclass of GtkDialog, and
we’ll use the same techniques that we’ve already seen: templates, private
structs, settings bindings.

Lets start with the template.

<?xml version="1.0" encoding="UTF-8"?>
<interface>
  <template class="ExampleAppPrefs" parent="GtkDialog">
    <property name="title" translatable="yes">Preferences</property>
    <property name="resizable">0</property>
    <property name="modal">1</property>
    <child internal-child="content_area">
      <object class="GtkBox" id="content_area">
        <child>
          <object class="GtkGrid" id="grid">
            <property name="margin-start">12</property>
            <property name="margin-end">12</property>
            <property name="margin-top">12</property>
            <property name="margin-bottom">12</property>
            <property name="row-spacing">12</property>
            <property name="column-spacing">12</property>
            <child>
              <object class="GtkLabel" id="fontlabel">
                <property name="label">_Font:</property>
                <property name="use-underline">1</property>
                <property name="mnemonic-widget">font</property>
                <property name="xalign">1</property>
                <layout>
                  <property name="column">0</property>
                  <property name="row">0</property>
                </layout>
              </object>
            </child>
            <child>
              <object class="GtkFontButton" id="font">
                <layout>
                  <property name="column">1</property>
                  <property name="row">0</property>
                </layout>
              </object>
            </child>
            <child>
              <object class="GtkLabel" id="transitionlabel">
                <property name="label">_Transition:</property>
                <property name="use-underline">1</property>
                <property name="mnemonic-widget">transition</property>
                <property name="xalign">1</property>
                <layout>
                  <property name="column">0</property>
                  <property name="row">1</property>
                </layout>
              </object>
            </child>
            <child>
              <object class="GtkComboBoxText" id="transition">
                <items>
                  <item translatable="yes" id="none">None</item>
                  <item translatable="yes" id="crossfade">Fade</item>
                  <item translatable="yes" id="slide-left-right">Slide</item>
                </items>
                <layout>
                  <property name="column">1</property>
                  <property name="row">1</property>
                </layout>
              </object>
            </child>
          </object>
        </child>
      </object>
    </child>
  </template>
</interface>

Next comes the dialog subclass.

#include <gtk/gtk.h>

#include "exampleapp.h"
#include "exampleappwin.h"
#include "exampleappprefs.h"

struct _ExampleAppPrefs
{
  GtkDialog parent;

  GSettings *settings;
  GtkWidget *font;
  GtkWidget *transition;
};

G_DEFINE_TYPE (ExampleAppPrefs, example_app_prefs, GTK_TYPE_DIALOG)

static void
example_app_prefs_init (ExampleAppPrefs *prefs)
{
  gtk_widget_init_template (GTK_WIDGET (prefs));
  prefs->settings = g_settings_new ("org.gtk.exampleapp");

  g_settings_bind (prefs->settings, "font",
                   prefs->font, "font",
                   G_SETTINGS_BIND_DEFAULT);
  g_settings_bind (prefs->settings, "transition",
                   prefs->transition, "active-id",
                   G_SETTINGS_BIND_DEFAULT);
}

static void
example_app_prefs_dispose (GObject *object)
{
  ExampleAppPrefs *prefs;

  prefs = EXAMPLE_APP_PREFS (object);

  g_clear_object (&prefs->settings);

  G_OBJECT_CLASS (example_app_prefs_parent_class)->dispose (object);
}

static void
example_app_prefs_class_init (ExampleAppPrefsClass *class)
{
  G_OBJECT_CLASS (class)->dispose = example_app_prefs_dispose;

  gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (class),
                                               "/org/gtk/exampleapp/prefs.ui");
  gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), ExampleAppPrefs, font);
  gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), ExampleAppPrefs, transition);
}

ExampleAppPrefs *
example_app_prefs_new (ExampleAppWindow *win)
{
  return g_object_new (EXAMPLE_APP_PREFS_TYPE, "transient-for", win, "use-header-bar", TRUE, NULL);
}

Now we revisit the preferences_activated() function in our application
class, and make it open a new preference dialog.

...

static void
preferences_activated (GSimpleAction *action,
                       GVariant      *parameter,
                       gpointer       app)
{
  ExampleAppPrefs *prefs;
  GtkWindow *win;

  win = gtk_application_get_active_window (GTK_APPLICATION (app));
  prefs = example_app_prefs_new (EXAMPLE_APP_WINDOW (win));
  gtk_window_present (GTK_WINDOW (prefs));
}

...

(full source)

After all this work, our application can now show a preference dialog
like this:

Preference dialog

Adding a search bar

We continue to flesh out the functionality of our application. For now, we
add search. GTK supports this with GtkSearchEntry and
GtkSearchBar. The search bar is a widget that can slide in from the
top to present a search entry.

We add a toggle button to the header bar, which can be used to slide out
the search bar below the header bar.

<?xml version="1.0" encoding="UTF-8"?>
<interface>
  <template class="ExampleAppWindow" parent="GtkApplicationWindow">
    <property name="title" translatable="yes">Example Application</property>
    <property name="default-width">600</property>
    <property name="default-height">400</property>
    <child type="titlebar">
      <object class="GtkHeaderBar" id="header">
        <child type="title">
          <object class="GtkStackSwitcher" id="tabs">
            <property name="stack">stack</property>
          </object>
        </child>
        <child type="end">
          <object class="GtkMenuButton" id="gears">
            <property name="direction">none</property>
          </object>
        </child>
        <child type="end">
          <object class="GtkToggleButton" id="search">
            <property name="sensitive">0</property>
            <property name="icon-name">edit-find-symbolic</property>
          </object>
        </child>
      </object>
    </child>
    <child>
      <object class="GtkBox" id="content_box">
        <property name="orientation">vertical</property>
        <child>
          <object class="GtkSearchBar" id="searchbar">
            <child>
              <object class="GtkSearchEntry" id="searchentry">
                <signal name="search-changed" handler="search_text_changed"/>
              </object>
            </child>
          </object>
        </child>
        <child>
          <object class="GtkStack" id="stack">
            <signal name="notify::visible-child" handler="visible_child_changed"/>
          </object>
        </child>
      </object>
    </child>
  </template>
</interface>

Implementing the search needs quite a few code changes that we are not
going to completely go over here. The central piece of the search
implementation is a signal handler that listens for text changes in
the search entry.

...

static void
search_text_changed (GtkEntry         *entry,
                     ExampleAppWindow *win)
{
  const char *text;
  GtkWidget *tab;
  GtkWidget *view;
  GtkTextBuffer *buffer;
  GtkTextIter start, match_start, match_end;

  text = gtk_editable_get_text (GTK_EDITABLE (entry));

  if (text[0] == '\0')
    return;

  tab = gtk_stack_get_visible_child (GTK_STACK (win->stack));
  view = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (tab));
  buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));

  /* Very simple-minded search implementation */
  gtk_text_buffer_get_start_iter (buffer, &start);
  if (gtk_text_iter_forward_search (&start, text, GTK_TEXT_SEARCH_CASE_INSENSITIVE,
                                    &match_start, &match_end, NULL))
    {
      gtk_text_buffer_select_range (buffer, &match_start, &match_end);
      gtk_text_view_scroll_to_iter (GTK_TEXT_VIEW (view), &match_start,
                                    0.0, FALSE, 0.0, 0.0);
    }
}

static void
example_app_window_init (ExampleAppWindow *win)
{

...

  gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), search_text_changed);

...

}

...

(full source)

With the search bar, our application now looks like this:

A search bar

Adding a side bar

As another piece of functionality, we are adding a sidebar, which demonstrates
GtkMenuButton, GtkRevealer and GtkListBox.

<?xml version="1.0" encoding="UTF-8"?>
<interface>
  <template class="ExampleAppWindow" parent="GtkApplicationWindow">
    <property name="title" translatable="yes">Example Application</property>
    <property name="default-width">600</property>
    <property name="default-height">400</property>
    <child type="titlebar">
      <object class="GtkHeaderBar" id="header">
        <child type="title">
          <object class="GtkStackSwitcher" id="tabs">
            <property name="stack">stack</property>
          </object>
        </child>
        <child type="end">
          <object class="GtkToggleButton" id="search">
            <property name="sensitive">0</property>
            <property name="icon-name">edit-find-symbolic</property>
          </object>
        </child>
        <child type="end">
          <object class="GtkMenuButton" id="gears">
            <property name="direction">none</property>
          </object>
        </child>
      </object>
    </child>
    <child>
      <object class="GtkBox" id="content_box">
        <property name="orientation">vertical</property>
        <child>
          <object class="GtkSearchBar" id="searchbar">
            <child>
              <object class="GtkSearchEntry" id="searchentry">
                <signal name="search-changed" handler="search_text_changed"/>
              </object>
            </child>
          </object>
        </child>
        <child>
          <object class="GtkBox" id="hbox">
            <child>
              <object class="GtkRevealer" id="sidebar">
                <property name="transition-type">slide-right</property>
                <child>
                  <object class="GtkScrolledWindow" id="sidebar-sw">
                    <property name="hscrollbar-policy">never</property>
                    <child>
                      <object class="GtkListBox" id="words">
                        <property name="selection-mode">none</property>
                      </object>
                    </child>
                  </object>
                </child>
              </object>
            </child>
            <child>
              <object class="GtkStack" id="stack">
                <signal name="notify::visible-child" handler="visible_child_changed"/>
              </object>
            </child>
          </object>
        </child>
      </object>
    </child>
  </template>
</interface>

The code to populate the sidebar with buttons for the words found in each
file is a little too involved to go into here. But we’ll look at the code
to add a checkbutton for the new feature to the menu.

<?xml version="1.0" encoding="UTF-8"?>
<interface>
  <menu id="menu">
    <section>
      <item>
        <attribute name="label" translatable="yes">_Words</attribute>
        <attribute name="action">win.show-words</attribute>
      </item>
      <item>
        <attribute name="label" translatable="yes">_Preferences</attribute>
        <attribute name="action">app.preferences</attribute>
      </item>
    </section>
    <section>
      <item>
        <attribute name="label" translatable="yes">_Quit</attribute>
        <attribute name="action">app.quit</attribute>
      </item>
    </section>
  </menu>
</interface>

To connect the menuitem to the show-words setting, we use
a GAction corresponding to the given GSettings key.

...

static void
example_app_window_init (ExampleAppWindow *win)
{

...

  builder = gtk_builder_new_from_resource ("/org/gtk/exampleapp/gears-menu.ui");
  menu = G_MENU_MODEL (gtk_builder_get_object (builder, "menu"));
  gtk_menu_button_set_menu_model (GTK_MENU_BUTTON (priv->gears), menu);
  g_object_unref (builder);

  action = g_settings_create_action (priv->settings, "show-words");
  g_action_map_add_action (G_ACTION_MAP (win), action);
  g_object_unref (action);
}

...

(full source)

What our application looks like now:

A sidebar

Properties

Widgets and other objects have many useful properties.

Here we show some ways to use them in new and flexible ways, by wrapping
them in actions with GPropertyAction or by binding them with
GBinding.

To set this up, we add two labels to the header bar in our window template,
named lines_label and lines, and bind them to struct members in the
private struct, as we’ve seen a couple of times by now.

We add a new “Lines” menu item to the gears menu, which triggers the
show-lines action:

<?xml version="1.0" encoding="UTF-8"?>
<interface>
  <menu id="menu">
    <section>
      <item>
        <attribute name="label" translatable="yes">_Words</attribute>
        <attribute name="action">win.show-words</attribute>
      </item>
      <item>
        <attribute name="label" translatable="yes">_Lines</attribute>
        <attribute name="action">win.show-lines</attribute>
      </item>
      <item>
        <attribute name="label" translatable="yes">_Preferences</attribute>
        <attribute name="action">app.preferences</attribute>
      </item>
    </section>
    <section>
      <item>
        <attribute name="label" translatable="yes">_Quit</attribute>
        <attribute name="action">app.quit</attribute>
      </item>
    </section>
  </menu>
</interface>

To make this menu item do something, we create a property action for the
visible property of the lines label, and add it to the actions of the
window. The effect of this is that the visibility of the label gets toggled
every time the action is activated.

Since we want both labels to appear and disappear together, we bind
the visible property of the lines_label widget to the same property
of the lines widget.

...

static void
example_app_window_init (ExampleAppWindow *win)
{
  ...

  action = (GAction*) g_property_action_new ("show-lines", win->lines, "visible");
  g_action_map_add_action (G_ACTION_MAP (win), action);
  g_object_unref (action);

  g_object_bind_property (win->lines, "visible",
                          win->lines_label, "visible",
                          G_BINDING_DEFAULT);
}

...

(full source)

We also need a function that counts the lines of the currently active tab,
and updates the lines label. See the full source
if you are interested in the details.

This brings our example application to this appearance:

Full application

Browse Topics

Get set with the topic of your choice and explore the documentation.

Getting Started

Get started now by building a Hello World app and playing around it.

Dev Tools

Learn how to setup your development environment with GTK recommended tools.

Language Bindings

Learn to integrate your favorite programming language with GTK through bindings.

API References

Dig deep into the API references for GTK3 and GTK4.

Architecture

Learn about the sub modules the GTK has been built upon.

Installations

Figure out how to install GTK on different platforms.

Время на прочтение
4 мин

Количество просмотров 127K

На ХабраХабре наконец-то появился новый блог, посвящённый GTK. Присоединяйтесь! :-)

Привет, %username%!

В сети бытуют страшные слухи об этом фреймворке, однако серией статей о нём на ХабраХабре я попытаюсь разрушить сложившиеся стереотипы.

GTK+ — это фреймворк для создания кроссплатформенного графического интерфейса пользователя (GUI). Наряду с Qt он является одной из двух наиболее популярных на сегодняшний день библиотек для X Window System.

Изначально эта библиотека была частью графического редактора GIMP, но позже стала независимой и приобрела популярность. GTK+ — это свободное ПО, распространяемое на условиях GNU LGPL и позволяющее создавать как свободное, так и проприетарное программное обеспечение.

Как это работает

GTK+ написан на языке Си, однако несмотря на это, является объектно-ориентированным. Также можно использовать обёртки для следующих языков: Ada, C, C++, C#, D, Erlang, Fortran, GOB, Genie, Haskell, FreeBASIC, Free Pascal, Java, JavaScript, Lua, OCaml, Perl, PHP, PureBasic, Python, R, Ruby, Smalltalk, Tcl, Vala.

Внутри GTK+ состоит из двух компонентов: GTK, который содержит набор виджетов (кнопка, метка и т.д.) и GDK, который занят выводом результата на экран.

Внешний вид приложений может меняться программистом и/или пользователем. По-умолчанию приложения выглядят нативно, т.е. так же, как и другие приложение в этой системе. Кроме того, начиная с версии 3.0, можно менять внешний вид элементов с помощью CSS.

Делаем «Hello, World»

Для начала за основу возьмём вот такую заготовку:

#include <gtk/gtk.h> /* подключаем GTK+ */

/* эта функция будет выполнена первой */
int main( int argc, char *argv[])
{
        /* тут мы объявим переменные */

        /* запускаем GTK+ */
        gtk_init(&argc, &argv);

        /* тут будет код нашего приложения */

        /* передаём управление GTK+ */
        gtk_main();

        return 0;
}

Пожалуйста, не используйте одинарные комментарии (//), если как и я решили писать на Си.

Давайте для начала создадим окно нашего приложения. В GTK существует несколько типов окошек, но нам понадобится обычный GtkWindow. Вставьте в нашу заготовку следующий код:

        /* это вставьте вначале, хотя на самом деле порядок не так важен */
        GtkWidget *window;

        /* ... */

        /* создать новый виджет - окно */
        window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
        /* дать окну заголовок */
        gtk_window_set_title(GTK_WINDOW(window), "Введение в GTK");
        /* установить внутренние границы (читайте ниже) */
        gtk_container_set_border_width (GTK_CONTAINER(window), 50);

Прежде чем продолжить, несколько слов об упаковке.

В GTK+ виджеты принято размещать не по координатам, а упаковывать в специальные контейнеры. Окно, которое мы создали, также является контейнером, который может содержать только один виджет. Нам этого хватит, но в ином случае нам бы пришлось добавить специальный виджет-контейнер, который может представлять из себя сетку, а также горизонтальные или вертикальные поля. Но об этом я подробнее расскажу в следующем топике.

Пока остановимся на окне. Напомню, что окно — это контейнер, которому мы указали толщину границ в 50 пикселей. Что это значит?

Это значит, что мы создаём своего рода невидимую рамку вокруг этого контейнера и по этому ничего не сможем разместить в этой области.

Теперь рассмотрим сигналы.

Мы можем связать определённое событие, которое должно произойти с виджетом, со своей функцией.

        /* когда пользователь закроет окно, то выйти из приложения */
        g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(gtk_main_quit), NULL);

В данном случае это стандартная фунция gtk_main_quit(), которая безопасно завершит наше приложение.

Теперь создадим кнопку, по нажатию на которую будет появляться окошко с надписью «И тебе привет, %username%!».

Кнопка (GtkButton) — это тоже контейнер, который также может содержать один виджет. Чтобы не усложнять код созданием метки и помещением её в кнопку, сделаем вот так:

        /* это вставьте вначале, хотя на самом деле порядок не так важен */
        GtkWidget *button;

        /* ... */

        /* создать кнопку с меткой */
        button = gtk_button_new_with_label("Привет, ХабраХабр!");
        /* упаковать нашу кнопку в окно */
        gtk_container_add(GTK_CONTAINER(window), button);

        /* когда пользователь кликнет по ней, то вызвать ф-цию welcome */
        g_signal_connect(GTK_BUTTON(button), "clicked", G_CALLBACK(welcome), NULL);

Выглядеть это будет так:

Теперь реализация функции welcome:

/* выводит приветствие */
void welcome (GtkButton *button, gpointer data)
{
        /* виджеты */
        GtkWidget *dialog;
        GtkWidget *label;
        GtkWidget *content_area;

        /* создать диалог */
        dialog = gtk_dialog_new_with_buttons("Ошибка LOL!!!111",
                                             NULL,
                                             GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
                                             GTK_STOCK_OK,
                                             GTK_RESPONSE_ACCEPT,
                                             NULL);

        /* получить контейнер, в который будем пихать метку */
        content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));

        /* сама метка */
        label = gtk_label_new("\n\nИ тебе привет, %username!%");
        gtk_container_add(GTK_CONTAINER(content_area), label);
        gtk_widget_show(label);

        /* запускаем диалог */
        gtk_dialog_run(GTK_DIALOG(dialog));
        /* а потом прячем */
        gtk_widget_destroy(dialog);
}

Результат после нажатия:

Вот так просто и быстро мы создали рабочую программу. В следующих топиках я расскажу про использование Glade (программа, чтобы рисовать виджеты без кода) и создание собственных виджетов.

Компиляция

gcc file_name.c -o file_name `pkg-config --cflags --libs gtk+-3.0`

Не забудьте установить пакет GTK для разработчика (кончается на «-dev»).

Что-нибудь ещё?

Да, пожалуй, приведу несколько ссылок:

www.gtk.org — официальный сайт

www.gtkforums.com — англоязычный форум, посвящённый GTK

developer.gnome.org — информация для разработчиков под среду GNOME. Содержит много полезной информации о GTK, в том числе и на русском языке.

UPD: Полный код примера на PasteBin — pastebin.com/iPttWBne

GTK is a widget toolkit. Each user interface created by GTK consists of
widgets. This is implemented in C using GObject, an object-oriented
framework for C. Widgets are organized in a hierachy. The window widget is
the main container. The user interface is then built by adding buttons,
drop-down menus, input fields, and other widgets to the window. If you are
creating complex user interfaces it is recommended to use GtkBuilder and its
GTK-specific markup description language, instead of assembling the
interface manually. You can also use a visual user interface editor, like Glade.

GTK is event-driven. The toolkit listens for events such as a click on a
button, and passes the event to your application.

This chapter contains some tutorial information to get you started with GTK
programming. It assumes that you have GTK, its dependencies and a C compiler
installed and ready to use. If you need to build GTK itself first, refer to
the Compiling the GTK libraries section in this reference.

Basics

To begin our introduction to GTK, we’ll start with a simple signal-based Gtk
application. This program will create an empty 200×200 pixel window.

Save the following code as a file named example-0.c:

#include <gtk/gtk.h>

static void
activate (GtkApplication* app,
          gpointer        user_data)
{
  GtkWidget *window;

  window = gtk_application_window_new (app);
  gtk_window_set_title (GTK_WINDOW (window), "Window");
  gtk_window_set_default_size (GTK_WINDOW (window), 200, 200);
  gtk_widget_show_all (window);
}

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

  app = gtk_application_new ("org.gtk.example", G_APPLICATION_FLAGS_NONE);
  g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);
  status = g_application_run (G_APPLICATION (app), argc, argv);
  g_object_unref (app);

  return status;
}

You can compile the program above with GCC using:

gcc `pkg-config --cflags gtk+-3.0` -o example-0 example-0.c `pkg-config --libs gtk+-3.0`

For more information on how to compile a GTK application, please refer to
the Compiling GTK Applications section.

All GTK applications must include the gtk/gtk.h header, which declares
functions, types and macros required by GTK applications. Even if GTK
installs multiple header files, only the top level gtk/gtk.h header can be
directly included by third party code. The compiler will abort with an error
if any other header is directly included.

In a GTK application, the purpose of the main() function is to create a
GtkApplication object and run it. In this example a
GtkApplication pointer named app is called and then initialized using
gtk_application_new().

When creating a GtkApplication you need to pick an application identifier
(a name) and input to gtk_application_new() as parameter. For this example
org.gtk.example is used but for choosing an identifier for your
application see the corresponding tutorial on the GNOME developer
documentation website.
Lastly gtk_application_new() takes a GApplicationFlags as input
for your application, if your application would have special needs.

Next the GApplication::activate signal is connected to the
activate() function defined above the main() function. The activate
signal will be sent when your application is launched with
g_application_run() on the line below. The g_application_run()
function also takes as arguments the pointers to the command line arguments
counter and string array; this allows GTK to parse specific command line
arguments that control the behavior of GTK itself.

Within g_application_run(), the activate signal is emitted and we then
proceed into the activate() function of the application. Inside the
activate() function we want to construct our GTK window, so that a window
is shown when the application is launched. The call to
gtk_application_window_new() will create a new GtkWindow and
store it inside the window pointer. The window will have a frame, a title
bar, and window controls depending on the platform.

A window title is set using gtk_window_set_title(). This function
takes a GtkWindow pointer and a string as input. As our window pointer is
a GtkWidget pointer, we need to cast it to GtkWindow. But instead of
casting window via the typical C cast (GtkWindow *), window can be cast
using the macro GTK_WINDOW(). GTK_WINDOW() will check if the pointer is
an instance of the GtkWindow class, before casting, and emit a warning if
the check fails. More information about this convention can be found in the
GObject documentation.

Finally the window size is set using gtk_window_set_default_size()
and the window is then shown by GTK via gtk_widget_show_all().

When you exit the window, by for example pressing the X, the
g_application_run() function returns with a number which is saved inside
an integer named status. Afterwards, the GtkApplication object is freed
from memory with g_object_unref(). Finally the status integer
is returned and the GTK application exits.

While the program is running, GTK is receiving events. These are typically
input events caused by the user interacting with your program, but also
things like messages from the window manager or other applications. GTK
processes these and as a result, signals may be emitted on your widgets.
Connecting handlers for these signals is how you normally make your program
do something in response to user input.

The following example is slightly more complex, and tries to showcase some
of the capabilities of GTK.

In the long tradition of programming languages and libraries, it is called
Hello, World.

Save the following code as a file named example-1.c:

#include <gtk/gtk.h>

static void
print_hello (GtkWidget *widget,
             gpointer   data)
{
  g_print ("Hello World\n");
}

static void
activate (GtkApplication *app,
          gpointer        user_data)
{
  GtkWidget *window;
  GtkWidget *button;
  GtkWidget *button_box;

  window = gtk_application_window_new (app);
  gtk_window_set_title (GTK_WINDOW (window), "Window");
  gtk_window_set_default_size (GTK_WINDOW (window), 200, 200);

  button_box = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL);
  gtk_container_add (GTK_CONTAINER (window), button_box);

  button = gtk_button_new_with_label ("Hello World");
  g_signal_connect (button, "clicked", G_CALLBACK (print_hello), NULL);
  g_signal_connect_swapped (button, "clicked", G_CALLBACK (gtk_widget_destroy), window);
  gtk_container_add (GTK_CONTAINER (button_box), button);

  gtk_widget_show_all (window);
}

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

  app = gtk_application_new ("org.gtk.example", G_APPLICATION_FLAGS_NONE);
  g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);
  status = g_application_run (G_APPLICATION (app), argc, argv);
  g_object_unref (app);

  return status;
}

You can compile the program above with GCC using:

gcc `pkg-config --cflags gtk+-3.0` -o example-1 example-1.c `pkg-config --libs gtk+-3.0`

As seen above, example-1.c builds further upon example-0.c by adding a
button to our window, with the label “Hello World”. Two new GtkWidget
pointers are declared to accomplish this, button and button_box. The
button_box variable is created to store a GtkButtonBox which is
GTK’s way of controlling the size and layout of buttons. The GtkButtonBox
is created and assigned to gtk_button_box_new() which takes a
GtkOrientation enumeration as parameter. The buttons which this box
will contain can either be stored horizontally or vertically but this does
not matter in this particular case as we are dealing with only one button.
After initializing button_box with horizontal orientation, the code adds
the button_box widget to the window widget using
gtk_container_add().

Next the button variable is initialized in similar manner.
gtk_button_new_with_label() is called which returns a
GtkButton to be stored inside button. Afterwards button is
added to our button_box. Using g_signal_connect() the button
is connected to a function in our app called print_hello(), so that when
the button is clicked, GTK will call this function. As the print_hello()
function does not use any data as input, NULL is passed to it.
print_hello() calls g_print() with the string “Hello World” which will
print Hello World in a terminal if the GTK application was started from one.

After connecting print_hello(), another signal is connected to the
clicked signal of the button using
g_signal_connect_swapped(). This functions is similar to
g_signal_connect() with the difference lying in how the callback function
is treated. g_signal_connect_swapped() allows you to specify what the
callback function should take as parameter by letting you pass it as data.
In this case the function being called back is gtk_widget_destroy() and
the window pointer is passed to it. This has the effect that when the button
is clicked, the whole GTK window is destroyed. In contrast, if a normal
g_signal_connect() were used to connect the clicked signal with
gtk_widget_destroy(), then the button itself would have been destroyed,
not the window.

The rest of the code in example-1.c is identical to example-0.c.

The next section will elaborate further on how to add several widgets to
your GTK application.

Packing

When creating an application, you’ll want to put more than one widget inside
a window. When you want to put more than one widget into a window, it
becomes important to control how each widget is positioned and sized. This
is where packing comes in.

GTK comes with a large variety of layout containers whose purpose it is to
control the layout of the child widgets that are added to them.

The following example shows how the GtkGrid container lets you
arrange several buttons.

Save the following code as a file named example-2.c:

#include <gtk/gtk.h>

static void
print_hello (GtkWidget *widget,
             gpointer   data)
{
  g_print ("Hello World\n");
}

static void
activate (GtkApplication *app,
          gpointer        user_data)
{
  GtkWidget *window;
  GtkWidget *grid;
  GtkWidget *button;

  /* create a new window, and set its title */
  window = gtk_application_window_new (app);
  gtk_window_set_title (GTK_WINDOW (window), "Window");
  gtk_container_set_border_width (GTK_CONTAINER (window), 10);

  /* Here we construct the container that is going pack our buttons */
  grid = gtk_grid_new ();

  /* Pack the container in the window */
  gtk_container_add (GTK_CONTAINER (window), grid);

  button = gtk_button_new_with_label ("Button 1");
  g_signal_connect (button, "clicked", G_CALLBACK (print_hello), NULL);

  /* Place the first button in the grid cell (0, 0), and make it fill
   * just 1 cell horizontally and vertically (ie no spanning)
   */
  gtk_grid_attach (GTK_GRID (grid), button, 0, 0, 1, 1);

  button = gtk_button_new_with_label ("Button 2");
  g_signal_connect (button, "clicked", G_CALLBACK (print_hello), NULL);

  /* Place the second button in the grid cell (1, 0), and make it fill
   * just 1 cell horizontally and vertically (ie no spanning)
   */
  gtk_grid_attach (GTK_GRID (grid), button, 1, 0, 1, 1);

  button = gtk_button_new_with_label ("Quit");
  g_signal_connect_swapped (button, "clicked", G_CALLBACK (gtk_widget_destroy), window);

  /* Place the Quit button in the grid cell (0, 1), and make it
   * span 2 columns.
   */
  gtk_grid_attach (GTK_GRID (grid), button, 0, 1, 2, 1);

  /* Now that we are done packing our widgets, we show them all
   * in one go, by calling gtk_widget_show_all() on the window.
   * This call recursively calls gtk_widget_show() on all widgets
   * that are contained in the window, directly or indirectly.
   */
  gtk_widget_show_all (window);

}

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

  app = gtk_application_new ("org.gtk.example", G_APPLICATION_FLAGS_NONE);
  g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);
  status = g_application_run (G_APPLICATION (app), argc, argv);
  g_object_unref (app);

  return status;
}

You can compile the program above with GCC using:

gcc `pkg-config --cflags gtk+-3.0` -o example-2 example-2.c `pkg-config --libs gtk+-3.0`

Building user interfaces

When constructing a more complicated user interface, with dozens or hundreds
of widgets, doing all the setup work in C code is cumbersome, and making
changes becomes next to impossible.

Thankfully, GTK supports the separation of user interface layout from your
business logic, by using UI descriptions in an XML format that can be parsed
by the GtkBuilder class.

Save the following XML as a file named builder.ui:

<interface>
  <object id="window" class="GtkWindow">
    <property name="visible">True</property>
    <property name="title">Grid</property>
    <property name="border-width">10</property>
    <child>
      <object id="grid" class="GtkGrid">
        <property name="visible">True</property>
        <child>
          <object id="button1" class="GtkButton">
            <property name="visible">True</property>
            <property name="label">Button 1</property>
          </object>
          <packing>
            <property name="left-attach">0</property>
            <property name="top-attach">0</property>
          </packing>
        </child>
        <child>
          <object id="button2" class="GtkButton">
            <property name="visible">True</property>
            <property name="label">Button 2</property>
          </object>
          <packing>
            <property name="left-attach">1</property>
            <property name="top-attach">0</property>
          </packing>
        </child>
        <child>
          <object id="quit" class="GtkButton">
            <property name="visible">True</property>
            <property name="label">Quit</property>
          </object>
          <packing>
            <property name="left-attach">0</property>
            <property name="top-attach">1</property>
            <property name="width">2</property>
          </packing>
        </child>
      </object>
      <packing>
      </packing>
    </child>
  </object>
</interface>

Save the following code as a file named example-3.c:

#include <gtk/gtk.h>

static void
print_hello (GtkWidget *widget,
             gpointer   data)
{
  g_print ("Hello World\n");
}

int
main (int   argc,
      char *argv[])
{
  GtkBuilder *builder;
  GObject *window;
  GObject *button;
  GError *error = NULL;

  gtk_init (&argc, &argv);

  /* Construct a GtkBuilder instance and load our UI description */
  builder = gtk_builder_new ();
  if (gtk_builder_add_from_file (builder, "builder.ui", &error) == 0)
    {
      g_printerr ("Error loading file: %s\n", error->message);
      g_clear_error (&error);
      return 1;
    }

  /* Connect signal handlers to the constructed widgets. */
  window = gtk_builder_get_object (builder, "window");
  g_signal_connect (window, "destroy", G_CALLBACK (gtk_main_quit), NULL);

  button = gtk_builder_get_object (builder, "button1");
  g_signal_connect (button, "clicked", G_CALLBACK (print_hello), NULL);

  button = gtk_builder_get_object (builder, "button2");
  g_signal_connect (button, "clicked", G_CALLBACK (print_hello), NULL);

  button = gtk_builder_get_object (builder, "quit");
  g_signal_connect (button, "clicked", G_CALLBACK (gtk_main_quit), NULL);

  gtk_main ();

  return 0;
}

You can compile the program above with GCC using:

gcc `pkg-config --cflags gtk+-3.0` -o example-3 example-3.c `pkg-config --libs gtk+-3.0`

Note that GtkBuilder can also be used to construct objects that are not
widgets, such as tree models, adjustments, etc. That is the reason the
method we use here is called gtk_builder_get_object() and returns a
GObject instead of a GtkWidget.

GtkBuilder will load the file from the current directory; for installed
applications, you can pass a full path to
gtk_builder_add_from_file() to make the execution of your program
independent of the current directory. A common location to install UI
descriptions and similar data is /usr/share/appname, but it will require
installing the file separately from your binary.

It is also possible to embed the UI description in the source code as a
string and use gtk_builder_add_from_string() to load it; keeping
the UI description in a separate file has several advantages: it is then
possible to make minor adjustments to the UI without recompiling your
program, and, more importantly, graphical UI editors such as Glade can load
the file and allow you to create and modify your UI by point-and-click.

It is possible to have the UI description as a separate file during
development, and then embed it in the compiled executable at build time by
using GResource.

Building applications

A GTK application consists of a number of files:

A binary:
The main executable of your application; it typically is installed in the
binaries directory, e.g. /usr/bin.

A desktop file:
The desktop file provides important information about the application to the
desktop shell, such as its name, icon, D-Bus name, command line to launch it,
etc. It is typically installed in /usr/share/applications.

An icon:
The icon gets installed in the “hicolor” icon theme directory, e.g.
/usr/share/icons/hicolor/48x48/apps, where it will be found regardless of
the current icon theme.

A settings schema:
If the application uses GSettings, it will install its schema in
/usr/share/glib-2.0/schemas and run glib-compile-schemas to ensure
that the schema can be used.

Other resources:
Other files, such as GtkBuilder UI definition files, are best loaded from
resources stored in the application binary itself. This eliminates the need
for most of the files that would traditionally be installed in an
application-specific location in /usr/share.

GTK includes application support that is built on top of GApplication. In
this tutorial we’ll build a simple application by starting from scratch,
adding more and more pieces over time. Along the way, we’ll learn about
GtkApplication, templates, resources, application menus, settings,
GtkHeaderBar, GtkStack, GtkSearchBar,
GtkListBox, and more.

The full, buildable sources for these examples can be found in the
examples directory of the GTK source distribution, or online in the GTK
Git repository. You can build each example separately by using make with the
Makefile.example file. For more information, see the README included in
the examples directory.

A trivial application

When using GtkApplication, the main() function can be very simple. We
just call g_application_run() and give it an instance of our application class.

#include <gtk/gtk.h>

#include "exampleapp.h"

int
main (int argc, char *argv[])
{
  return g_application_run (G_APPLICATION (example_app_new ()), argc, argv);
}

All the application logic is in the application class, which is a subclass
of GtkApplication. Our example does not yet have any interesting
functionality. All it does is open a window when it is activated without
arguments, and open the files it is given, if it is started with arguments.

To handle these two cases, we override the activate() virtual function,
which gets called when the application is launched without commandline
arguments; and the open() virtual function, which gets called when the
application is launched with command line arguments.

To learn more about GApplication entry points, consult the GIO documentation.

#include <gtk/gtk.h>

#include "exampleapp.h"
#include "exampleappwin.h"

struct _ExampleApp
{
  GtkApplication parent;
};

G_DEFINE_TYPE(ExampleApp, example_app, GTK_TYPE_APPLICATION);

static void
example_app_init (ExampleApp *app)
{
}

static void
example_app_activate (GApplication *app)
{
  ExampleAppWindow *win;

  win = example_app_window_new (EXAMPLE_APP (app));
  gtk_window_present (GTK_WINDOW (win));
}

static void
example_app_open (GApplication  *app,
                  GFile        **files,
                  gint           n_files,
                  const gchar   *hint)
{
  GList *windows;
  ExampleAppWindow *win;
  int i;

  windows = gtk_application_get_windows (GTK_APPLICATION (app));
  if (windows)
    win = EXAMPLE_APP_WINDOW (windows->data);
  else
    win = example_app_window_new (EXAMPLE_APP (app));

  for (i = 0; i < n_files; i++)
    example_app_window_open (win, files[i]);

  gtk_window_present (GTK_WINDOW (win));
}

static void
example_app_class_init (ExampleAppClass *class)
{
  G_APPLICATION_CLASS (class)->activate = example_app_activate;
  G_APPLICATION_CLASS (class)->open = example_app_open;
}

ExampleApp *
example_app_new (void)
{
  return g_object_new (EXAMPLE_APP_TYPE,
                       "application-id", "org.gtk.exampleapp",
                       "flags", G_APPLICATION_HANDLES_OPEN,
                       NULL);
}

Another important class that is part of the application support in GTK is
GtkApplicationWindow. It is typically subclassed as well. Our subclass
does not do anything yet, so we will just get an empty window.

#include <gtk/gtk.h>

#include "exampleapp.h"
#include "exampleappwin.h"

struct _ExampleAppWindow
{
  GtkApplicationWindow parent;
};

G_DEFINE_TYPE(ExampleAppWindow, example_app_window, GTK_TYPE_APPLICATION_WINDOW);

static void
example_app_window_init (ExampleAppWindow *app)
{
}

static void
example_app_window_class_init (ExampleAppWindowClass *class)
{
}

ExampleAppWindow *
example_app_window_new (ExampleApp *app)
{
  return g_object_new (EXAMPLE_APP_WINDOW_TYPE, "application", app, NULL);
}

void
example_app_window_open (ExampleAppWindow *win,
                         GFile            *file)
{
}

As part of the initial setup of our application, we also create an icon and
a desktop file:

[Desktop Entry]
Type=Application
Name=Example
Icon=exampleapp
StartupNotify=true
Exec=@bindir@/exampleapp

Note that `bindir@` needs to be replaced with the actual path to the binary
before this desktop file can be used.

Here is what we’ve achieved so far:

This does not look very impressive yet, but our application is already
presenting itself on the session bus, it has single-instance semantics, and
it accepts files as commandline arguments.

Populating the window

In this step, we use a GtkBuilder template to associate a GtkBuilder ui file
with our application window class.

Our simple ui file puts a GtkHeaderBar on top of a GtkStack widget. The
header bar contains a GtkStackSwitcher, which is a standalone widget to show
a row of ‘tabs’ for the pages of a GtkStack.

<?xml version="1.0" encoding="UTF-8"?>
<interface>
  <!-- interface-requires gtk+ 3.8 -->
  <template class="ExampleAppWindow" parent="GtkApplicationWindow">
    <property name="title" translatable="yes">Example Application</property>
    <property name="default-width">600</property>
    <property name="default-height">400</property>
    <child>
      <object class="GtkBox" id="content_box">
        <property name="visible">True</property>
        <property name="orientation">vertical</property>
        <child>
          <object class="GtkHeaderBar" id="header">
            <property name="visible">True</property>
            <child type="title">
              <object class="GtkStackSwitcher" id="tabs">
                <property name="visible">True</property>
                <property name="stack">stack</property>
              </object>
            </child>
          </object>
        </child>
        <child>
          <object class="GtkStack" id="stack">
            <property name="visible">True</property>
          </object>
        </child>
      </object>
    </child>
  </template>
</interface>

To make use of this file in our application, we revisit our
GtkApplicationWindow subclass, and call
gtk_widget_class_set_template_from_resource() from the class init function
to set the ui file as template for this class. We also add a call to
gtk_widget_init_template() in the instance init function to instantiate the
template for each instance of our class.

// ...

static void
example_app_window_init (ExampleAppWindow *win)
{
  gtk_widget_init_template (GTK_WIDGET (win));
}

static void
example_app_window_class_init (ExampleAppWindowClass *class)
{
  gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (class),
                                               "/org/gtk/exampleapp/window.ui");
}

// ...

(Full source)

You may have noticed that we used the _from_resource() variant of the
function that sets a template. Now we need to use GLib’s resource
functionality to include the ui file in the binary. This is commonly done by
listing all resources in a .gresource.xml file, such as this:

<?xml version="1.0" encoding="UTF-8"?>
<gresources>
  <gresource prefix="/org/gtk/exampleapp">
    <file preprocess="xml-stripblanks">window.ui</file>
  </gresource>
</gresources>

This file has to be converted into a C source file that will be compiled and
linked into the application together with the other source files. To do so,
we use the glib-compile-resources utility:

glib-compile-resources exampleapp.gresource.xml --target=resources.c --generate-source

Our application now looks like this:

Opening files

In this step, we make our application show the content of all the files that
it is given on the commandline.

To this end, we add a private struct to our application window subclass and
keep a reference to the GtkStack there. The
gtk_widget_class_bind_template_child_private() function arranges things so
that after instantiating the template, the stack member of the private
struct will point to the widget of the same name from the template.

// ...

struct _ExampleAppWindowPrivate
{
  GtkWidget *stack;
};

G_DEFINE_TYPE_WITH_PRIVATE(ExampleAppWindow, example_app_window, GTK_TYPE_APPLICATION_WINDOW)

...

static void
example_app_window_class_init (ExampleAppWindowClass *class)
{
  gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (class),
                                               "/org/gtk/exampleapp/window.ui");
  gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (class),
                                                ExampleAppWindow,
                        stack);
}

// ...

(Full source)

Now we revisit the example_app_window_open() function that is called for
each commandline argument, and construct a GtkTextView that we then add as a
page to the stack:

// ...

void
example_app_window_open (ExampleAppWindow *win,
                         GFile            *file)
{
  ExampleAppWindowPrivate *priv;
  gchar *basename;
  GtkWidget *scrolled, *view;
  gchar *contents;
  gsize length;

  priv = example_app_window_get_instance_private (win);
  basename = g_file_get_basename (file);

  scrolled = gtk_scrolled_window_new (NULL, NULL);
  gtk_widget_show (scrolled);
  gtk_widget_set_hexpand (scrolled, TRUE);
  gtk_widget_set_vexpand (scrolled, TRUE);
  view = gtk_text_view_new ();
  gtk_text_view_set_editable (GTK_TEXT_VIEW (view), FALSE);
  gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (view), FALSE);
  gtk_widget_show (view);
  gtk_container_add (GTK_CONTAINER (scrolled), view);
  gtk_stack_add_titled (GTK_STACK (priv->stack), scrolled, basename, basename);

  if (g_file_load_contents (file, NULL, &contents, &length, NULL, NULL))
    {
      GtkTextBuffer *buffer;

      buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
      gtk_text_buffer_set_text (buffer, contents, length);
      g_free (contents);
    }

  g_free (basename);
}

// ...

(Full source)

Note that we did not have to touch the stack switcher at all. It gets all
its information from the stack that it belongs to. Here, we are passing the
label to show for each file as the last argument to the
gtk_stack_add_titled() function.

Our application is beginning to take shape:

An application menu is shown by GNOME shell at the top of the screen. It is
meant to collect infrequently used actions that affect the whole application.

Just like the window template, we specify our application menu in a ui file,
and add it as a resource to our binary.

<?xml version="1.0"?>
<interface>
  <!-- interface-requires gtk+ 3.0 -->
  <menu id="appmenu">
    <section>
      <item>
        <attribute name="label" translatable="yes">_Preferences</attribute>
        <attribute name="action">app.preferences</attribute>
      </item>
    </section>
    <section>
      <item>
        <attribute name="label" translatable="yes">_Quit</attribute>
        <attribute name="action">app.quit</attribute>
      </item>
    </section>
  </menu>
</interface>

To associate the app menu with the application, we have to call
gtk_application_set_app_menu(). Since app menus work by activating GActions,
we also have to add a suitable set of actions to our application.

Both of these tasks are best done in the startup() virtual function, which
is guaranteed to be called once for each primary application instance:

// ...

static void
preferences_activated (GSimpleAction *action,
                       GVariant      *parameter,
                       gpointer       app)
{
}

static void
quit_activated (GSimpleAction *action,
                GVariant      *parameter,
                gpointer       app)
{
  g_application_quit (G_APPLICATION (app));
}

static GActionEntry app_entries[] =
{
  { "preferences", preferences_activated, NULL, NULL, NULL },
  { "quit", quit_activated, NULL, NULL, NULL }
};

static void
example_app_startup (GApplication *app)
{
  GtkBuilder *builder;
  GMenuModel *app_menu;
  const gchar *quit_accels[2] = { "<Ctrl>Q", NULL };

  G_APPLICATION_CLASS (example_app_parent_class)->startup (app);

  g_action_map_add_action_entries (G_ACTION_MAP (app),
                                   app_entries, G_N_ELEMENTS (app_entries),
                                   app);
  gtk_application_set_accels_for_action (GTK_APPLICATION (app),
                                         "app.quit",
                                         quit_accels);

  builder = gtk_builder_new_from_resource ("/org/gtk/exampleapp/app-menu.ui");
  app_menu = G_MENU_MODEL (gtk_builder_get_object (builder, "appmenu"));
  gtk_application_set_app_menu (GTK_APPLICATION (app), app_menu);
  g_object_unref (builder);
}

static void
example_app_class_init (ExampleAppClass *class)
{
  G_APPLICATION_CLASS (class)->startup = example_app_startup;
}

// ...

(Full source)

Our preferences menu item does not do anything yet, but the Quit menu item
is fully functional. Note that it can also be activated by the usual
Ctrl+Q shortcut. The shortcut was added with
gtk_application_set_accels_for_action().

The application menu looks like this:

A preference dialog

A typical application will have a some preferences that should be remembered
from one run to the next. Even for our simple example application, we may
want to change the font that is used for the content.

We are going to use GSettings to store our preferences. GSettings requires a
schema that describes our settings:

<?xml version="1.0" encoding="UTF-8"?>
<schemalist>
  <schema path="/org/gtk/exampleapp/" id="org.gtk.exampleapp">
    <key name="font" type="s">
      <default>'Monospace 12'</default>
      <summary>Font</summary>
      <description>The font to be used for content.</description>
    </key>
    <key name="transition" type="s">
      <choices>
        <choice value='none'/>
        <choice value='crossfade'/>
        <choice value='slide-left-right'/>
      </choices>
      <default>'none'</default>
      <summary>Transition</summary>
      <description>The transition to use when switching tabs.</description>
    </key>
  </schema>
</schemalist>

Before we can make use of this schema in our application, we need to compile
it into the binary form that GSettings expects. GIO provides macros to do
this in Autotools-based projects, whereas Meson provides the same
functionality through the gnome module.

Next, we need to connect our settings to the widgets that they are supposed
to control. One convenient way to do this is to use GSettings bind
functionality to bind settings keys to object properties, as we do here for
the transition setting.

// ...

static void
example_app_window_init (ExampleAppWindow *win)
{
  ExampleAppWindowPrivate *priv;

  priv = example_app_window_get_instance_private (win);
  gtk_widget_init_template (GTK_WIDGET (win));
  priv->settings = g_settings_new ("org.gtk.exampleapp");

  g_settings_bind (priv->settings, "transition",
                   priv->stack, "transition-type",
                   G_SETTINGS_BIND_DEFAULT);
}

// ...

(Full source)

The code to connect the font setting is a little more involved, since there
is no simple object property that it corresponds to, so we are not going to
go into that here.

At this point, the application will already react if you change one of the
settings, e.g. using the gsettings commandline tool. Of course, we expect
the application to provide a preference dialog for these. So lets do that
now. Our preference dialog will be a subclass of GtkDialog, and we’ll use
the same techniques that we’ve already seen: templates, private structs,
settings bindings.

Lets start with the template:

<?xml version="1.0" encoding="UTF-8"?>
<interface>
  <!-- interface-requires gtk+ 3.8 -->
  <template class="ExampleAppPrefs" parent="GtkDialog">
    <property name="title" translatable="yes">Preferences</property>
    <property name="resizable">False</property>
    <property name="modal">True</property>
    <child internal-child="vbox">
      <object class="GtkBox" id="vbox">
        <child>
          <object class="GtkGrid" id="grid">
            <property name="visible">True</property>
            <property name="margin">6</property>
            <property name="row-spacing">12</property>
            <property name="column-spacing">6</property>
            <child>
              <object class="GtkLabel" id="fontlabel">
                <property name="visible">True</property>
                <property name="label">_Font:</property>
                <property name="use-underline">True</property>
                <property name="mnemonic-widget">font</property>
                <property name="xalign">1</property>
              </object>
              <packing>
                <property name="left-attach">0</property>
                <property name="top-attach">0</property>
              </packing>
            </child>
            <child>
              <object class="GtkFontButton" id="font">
                <property name="visible">True</property>
              </object>
              <packing>
                <property name="left-attach">1</property>
                <property name="top-attach">0</property>
              </packing>
            </child>
            <child>
              <object class="GtkLabel" id="transitionlabel">
                <property name="visible">True</property>
                <property name="label">_Transition:</property>
                <property name="use-underline">True</property>
                <property name="mnemonic-widget">transition</property>
                <property name="xalign">1</property>
              </object>
              <packing>
                <property name="left-attach">0</property>
                <property name="top-attach">1</property>
              </packing>
            </child>
            <child>
              <object class="GtkComboBoxText" id="transition">
                <property name="visible">True</property>
                <items>
                  <item translatable="yes" id="none">None</item>
                  <item translatable="yes" id="crossfade">Fade</item>
                  <item translatable="yes" id="slide-left-right">Slide</item>
                </items>
              </object>
              <packing>
                <property name="left-attach">1</property>
                <property name="top-attach">1</property>
              </packing>
            </child>
          </object>
        </child>
      </object>
    </child>
  </template>
</interface>

Next comes the dialog class:

#include <gtk/gtk.h>

#include "exampleapp.h"
#include "exampleappwin.h"
#include "exampleappprefs.h"

struct _ExampleAppPrefs
{
  GtkDialog parent;
};

typedef struct _ExampleAppPrefsPrivate ExampleAppPrefsPrivate;

struct _ExampleAppPrefsPrivate
{
  GSettings *settings;
  GtkWidget *font;
  GtkWidget *transition;
};

G_DEFINE_TYPE_WITH_PRIVATE(ExampleAppPrefs, example_app_prefs, GTK_TYPE_DIALOG)

static void
example_app_prefs_init (ExampleAppPrefs *prefs)
{
  ExampleAppPrefsPrivate *priv;

  priv = example_app_prefs_get_instance_private (prefs);
  gtk_widget_init_template (GTK_WIDGET (prefs));
  priv->settings = g_settings_new ("org.gtk.exampleapp");

  g_settings_bind (priv->settings, "font",
                   priv->font, "font",
                   G_SETTINGS_BIND_DEFAULT);
  g_settings_bind (priv->settings, "transition",
                   priv->transition, "active-id",
                   G_SETTINGS_BIND_DEFAULT);
}

static void
example_app_prefs_dispose (GObject *object)
{
  ExampleAppPrefsPrivate *priv;

  priv = example_app_prefs_get_instance_private (EXAMPLE_APP_PREFS (object));
  g_clear_object (&priv->settings);

  G_OBJECT_CLASS (example_app_prefs_parent_class)->dispose (object);
}

static void
example_app_prefs_class_init (ExampleAppPrefsClass *class)
{
  G_OBJECT_CLASS (class)->dispose = example_app_prefs_dispose;

  gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (class),
                                               "/org/gtk/exampleapp/prefs.ui");
  gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (class), ExampleAppPrefs, font);
  gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (class), ExampleAppPrefs, transition);
}

ExampleAppPrefs *
example_app_prefs_new (ExampleAppWindow *win)
{
  return g_object_new (EXAMPLE_APP_PREFS_TYPE, "transient-for", win, "use-header-bar", TRUE, NULL);
}

Now we revisit the preferences_activated() function in our application
class, and make it open a new preference dialog:

// ...

static void
preferences_activated (GSimpleAction *action,
                       GVariant      *parameter,
                       gpointer       app)
{
  ExampleAppPrefs *prefs;
  GtkWindow *win;

  win = gtk_application_get_active_window (GTK_APPLICATION (app));
  prefs = example_app_prefs_new (EXAMPLE_APP_WINDOW (win));
  gtk_window_present (GTK_WINDOW (prefs));
}

// ...

(Full source)

After all this work, our application can now show a preference dialog like this:

Adding a search bar

We continue to flesh out the functionality of our application. For now, we
add search. GTK supports this with GtkSearchEntry and GtkSearchBar. The
search bar is a widget that can slide in from the top of a container to
present a search entry.

We add a toggle button to the header bar, which can be used to slide out the
search bar below the header bar.

<?xml version="1.0" encoding="UTF-8"?>
<interface>
  <!-- interface-requires gtk+ 3.8 -->
  <template class="ExampleAppWindow" parent="GtkApplicationWindow">
    <property name="title" translatable="yes">Example Application</property>
    <property name="default-width">600</property>
    <property name="default-height">400</property>
    <child>
      <object class="GtkBox" id="content_box">
        <property name="visible">True</property>
        <property name="orientation">vertical</property>
        <child>
          <object class="GtkHeaderBar" id="header">
            <property name="visible">True</property>
            <child type="title">
              <object class="GtkStackSwitcher" id="tabs">
                <property name="visible">True</property>
                <property name="stack">stack</property>
              </object>
            </child>
            <child>
              <object class="GtkToggleButton" id="search">
                <property name="visible">True</property>
                <property name="sensitive">False</property>
                <style>
                  <class name="image-button"/>
                </style>
                <child>
                  <object class="GtkImage" id="search-icon">
                    <property name="visible">True</property>
                    <property name="icon-name">edit-find-symbolic</property>
                    <property name="icon-size">1</property>
                  </object>
                </child>
              </object>
              <packing>
                <property name="pack-type">end</property>
              </packing>
            </child>
          </object>
        </child>
        <child>
          <object class="GtkSearchBar" id="searchbar">
            <property name="visible">True</property>
            <child>
              <object class="GtkSearchEntry" id="searchentry">
                <signal name="search-changed" handler="search_text_changed"/>
                <property name="visible">True</property>
              </object>
            </child>
          </object>
        </child>
        <child>
          <object class="GtkStack" id="stack">
            <signal name="notify::visible-child" handler="visible_child_changed"/>
            <property name="visible">True</property>
          </object>
        </child>
      </object>
    </child>
  </template>
</interface>

Implementing the search needs quite a few code changes that we are not going
to completely go over here. The central piece of the search implementation
is a signal handler that listens for text changes in the search entry:

// ...

static void
search_text_changed (GtkEntry         *entry,
                     ExampleAppWindow *win)
{
  ExampleAppWindowPrivate *priv;
  const gchar *text;
  GtkWidget *tab;
  GtkWidget *view;
  GtkTextBuffer *buffer;
  GtkTextIter start, match_start, match_end;

  text = gtk_entry_get_text (entry);

  if (text[0] == '\0')
    return;

  priv = example_app_window_get_instance_private (win);

  tab = gtk_stack_get_visible_child (GTK_STACK (priv->stack));
  view = gtk_bin_get_child (GTK_BIN (tab));
  buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));

  /* Very simple-minded search implementation */
  gtk_text_buffer_get_start_iter (buffer, &start);
  if (gtk_text_iter_forward_search (&start, text, GTK_TEXT_SEARCH_CASE_INSENSITIVE,
                                    &match_start, &match_end, NULL))
    {
      gtk_text_buffer_select_range (buffer, &match_start, &match_end);
      gtk_text_view_scroll_to_iter (GTK_TEXT_VIEW (view), &match_start,
                                    0.0, FALSE, 0.0, 0.0);
    }
}

static void
example_app_window_init (ExampleAppWindow *win)
{
  // ...

  gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), search_text_changed);

  // ...
}

// ...

(Full source)

With the search bar, our application now looks like this:

Adding a side bar

As another piece of functionality, we are adding a sidebar, which
demonstrates GtkMenuButton, GtkRevealer and GtkListBox.

<?xml version="1.0" encoding="UTF-8"?>
<interface>
  <!-- interface-requires gtk+ 3.8 -->
  <template class="ExampleAppWindow" parent="GtkApplicationWindow">
    <property name="title" translatable="yes">Example Application</property>
    <property name="default-width">600</property>
    <property name="default-height">400</property>
    <child>
      <object class="GtkBox" id="content_box">
        <property name="visible">True</property>
        <property name="orientation">vertical</property>
        <child>
          <object class="GtkHeaderBar" id="header">
            <property name="visible">True</property>
            <child type="title">
              <object class="GtkStackSwitcher" id="tabs">
                <property name="visible">True</property>
                <property name="stack">stack</property>
              </object>
            </child>
            <child>
              <object class="GtkToggleButton" id="search">
                <property name="visible">True</property>
                <property name="sensitive">False</property>
                <style>
                  <class name="image-button"/>
                </style>
                <child>
                  <object class="GtkImage" id="search-icon">
                    <property name="visible">True</property>
                    <property name="icon-name">edit-find-symbolic</property>
                    <property name="icon-size">1</property>
                  </object>
                </child>
              </object>
              <packing>
                <property name="pack-type">end</property>
              </packing>
            </child>
            <child>
              <object class="GtkMenuButton" id="gears">
                <property name="visible">True</property>
                <property name="direction">none</property>
                <property name="use-popover">True</property>
                <style>
                  <class name="image-button"/>
                </style>
              </object>
              <packing>
                <property name="pack-type">end</property>
              </packing>
            </child>
          </object>
        </child>
        <child>
          <object class="GtkSearchBar" id="searchbar">
            <property name="visible">True</property>
            <child>
              <object class="GtkSearchEntry" id="searchentry">
                <signal name="search-changed" handler="search_text_changed"/>
                <property name="visible">True</property>
              </object>
            </child>
          </object>
        </child>
        <child>
          <object class="GtkBox" id="hbox">
            <property name="visible">True</property>
            <child>
              <object class="GtkRevealer" id="sidebar">
                <property name="visible">True</property>
                <property name="transition-type">slide-right</property>
                <child>
                 <object class="GtkScrolledWindow" id="sidebar-sw">
                   <property name="visible">True</property>
                   <property name="hscrollbar-policy">never</property>
                   <property name="vscrollbar-policy">automatic</property>
                   <child>
                     <object class="GtkListBox" id="words">
                       <property name="visible">True</property>
                       <property name="selection-mode">none</property>
                     </object>
                   </child>
                 </object>
                </child>
              </object>
            </child>
            <child>
              <object class="GtkStack" id="stack">
                <signal name="notify::visible-child" handler="visible_child_changed"/>
                <property name="visible">True</property>
              </object>
            </child>
          </object>
        </child>
      </object>
    </child>
  </template>
</interface>

The code to populate the sidebar with buttons for the words found in each
file is a little too involved to go into here. But we’ll look at the code to
add the gears menu.

As expected by now, the gears menu is specified in a GtkBuilder UI file:

<?xml version="1.0"?>
<interface>
  <!-- interface-requires gtk+ 3.0 -->
  <menu id="menu">
    <section>
      <item>
        <attribute name="label" translatable="yes">_Words</attribute>
        <attribute name="action">win.show-words</attribute>
      </item>
    </section>
  </menu>
</interface>

To connect the menuitem to the show-words setting, we use a GAction
corresponding to the given GSettings key.

// ...

static void
example_app_window_init (ExampleAppWindow *win)
{
  // ...

  builder = gtk_builder_new_from_resource ("/org/gtk/exampleapp/gears-menu.ui");
  menu = G_MENU_MODEL (gtk_builder_get_object (builder, "menu"));
  gtk_menu_button_set_menu_model (GTK_MENU_BUTTON (priv->gears), menu);
  g_object_unref (builder);

  action = g_settings_create_action (priv->settings, "show-words");
  g_action_map_add_action (G_ACTION_MAP (win), action);
  g_object_unref (action);
}

// ...

(Full source)

What our application looks like now:

Properties

Widgets and other objects have many useful properties.

Here we show some ways to use them in new and flexible ways, by wrapping
them in actions with GPropertyAction or by binding them with GBinding.

To set this up, we add two labels to the header bar in our window template,
named lines_label and lines, and bind them to struct members in the
private struct, as we’ve seen a couple of times by now.

We add a new “Lines” menu item to the gears menu, which triggers the
show-lines action:

<?xml version="1.0"?>
<interface>
  <!-- interface-requires gtk+ 3.0 -->
  <menu id="menu">
    <section>
      <item>
        <attribute name="label" translatable="yes">_Words</attribute>
        <attribute name="action">win.show-words</attribute>
      </item>
      <item>
        <attribute name="label" translatable="yes">_Lines</attribute>
        <attribute name="action">win.show-lines</attribute>
      </item>
    </section>
  </menu>
</interface>

To make this menu item do something, we create a property action for the
visible property of the lines label, and add it to the actions of the
window. The effect of this is that the visibility of the label gets toggled
every time the action is activated.

Since we want both labels to appear and disappear together, we bind the
visible property of the lines_label widget to the same property of the
lines widget.

// ...

static void
example_app_window_init (ExampleAppWindow *win)
{
  // ...

  action = (GAction*) g_property_action_new ("show-lines", priv->lines, "visible");
  g_action_map_add_action (G_ACTION_MAP (win), action);
  g_object_unref (action);

  g_object_bind_property (priv->lines, "visible",
                          priv->lines_label, "visible",
                          G_BINDING_DEFAULT);
}

// ...

(Full source)

We also need a function that counts the lines of the currently active tab,
and updates the lines label. See the full source if you are interested in
the details.

This brings our example application to this appearance:

Our application already uses a GtkHeaderBar, but so far it still gets a
‘normal’ window titlebar on top of that. This is a bit redundant, and we
will now tell GTK to use the header bar as replacement for the titlebar. To
do so, we move it around to be a direct child of the window, and set its
type to be titlebar.

<?xml version="1.0" encoding="UTF-8"?>
<interface>
  <!-- interface-requires gtk+ 3.8 -->
  <template class="ExampleAppWindow" parent="GtkApplicationWindow">
    <property name="title" translatable="yes">Example Application</property>
    <property name="default-width">600</property>
    <property name="default-height">400</property>
        <child type="titlebar">
          <object class="GtkHeaderBar" id="header">
            <property name="visible">True</property>
            <property name="show-close-button">True</property>
            <child>
              <object class="GtkLabel" id="lines_label">
                <property name="visible">False</property>
                <property name="label" translatable="yes">Lines:</property>
              </object>
              <packing>
                <property name="pack-type">start</property>
              </packing>
            </child>
            <child>
              <object class="GtkLabel" id="lines">
                <property name="visible">False</property>
              </object>
              <packing>
                <property name="pack-type">start</property>
              </packing>
            </child>
            <child type="title">
              <object class="GtkStackSwitcher" id="tabs">
                <property name="visible">True</property>
                <property name="stack">stack</property>
              </object>
            </child>
            <child>
              <object class="GtkToggleButton" id="search">
                <property name="visible">True</property>
                <property name="sensitive">False</property>
                <style>
                  <class name="image-button"/>
                </style>
                <child>
                  <object class="GtkImage" id="search-icon">
                    <property name="visible">True</property>
                    <property name="icon-name">edit-find-symbolic</property>
                    <property name="icon-size">1</property>
                  </object>
                </child>
              </object>
              <packing>
                <property name="pack-type">end</property>
              </packing>
            </child>
            <child>
              <object class="GtkMenuButton" id="gears">
                <property name="visible">True</property>
                <property name="direction">none</property>
                <property name="use-popover">True</property>
                <style>
                  <class name="image-button"/>
                </style>
              </object>
              <packing>
                <property name="pack-type">end</property>
              </packing>
            </child>
          </object>
        </child>
    <child>
      <object class="GtkBox" id="content_box">
        <property name="visible">True</property>
        <property name="orientation">vertical</property>
        <child>
          <object class="GtkSearchBar" id="searchbar">
            <property name="visible">True</property>
            <child>
              <object class="GtkSearchEntry" id="searchentry">
                <signal name="search-changed" handler="search_text_changed"/>
                <property name="visible">True</property>
              </object>
            </child>
          </object>
        </child>
        <child>
          <object class="GtkBox" id="hbox">
            <property name="visible">True</property>
            <child>
              <object class="GtkRevealer" id="sidebar">
                <property name="visible">True</property>
                <property name="transition-type">slide-right</property>
                <child>
                 <object class="GtkScrolledWindow" id="sidebar-sw">
                   <property name="visible">True</property>
                   <property name="hscrollbar-policy">never</property>
                   <property name="vscrollbar-policy">automatic</property>
                   <child>
                     <object class="GtkListBox" id="words">
                       <property name="visible">True</property>
                       <property name="selection-mode">none</property>
                     </object>
                   </child>
                 </object>
                </child>
              </object>
            </child>
            <child>
              <object class="GtkStack" id="stack">
                <signal name="notify::visible-child" handler="visible_child_changed"/>
                <property name="visible">True</property>
              </object>
            </child>
          </object>
        </child>
      </object>
    </child>
  </template>
</interface>

A small extra bonus of using a header bar is that we get a fallback
application menu for free. Here is how the application now looks, if this
fallback is used:

If we set up the window icon for our window, the menu button will use that
instead of the generic placeholder icon you see here.

Custom drawing

Many widgets, like buttons, do all their drawing themselves. You just tell
them the label you want to see, and they figure out what font to use, draw
the button outline and focus rectangle, etc. Sometimes, it is necessary to
do some custom drawing. In that case, a GtkDrawingArea might be the right
widget to use. It offers a canvas on which you can draw by connecting to the
GtkWidget::draw signal.

The contents of a widget often need to be partially or fully redrawn, e.g.
when another window is moved and uncovers part of the widget, or when the
window containing it is resized. It is also possible to explicitly cause
part or all of the widget to be redrawn, by calling gtk_widget_queue_draw()
or its variants. GTK takes care of most of the details by providing a
ready-to-use Cairo context to the draw signal handler.

The following example shows a draw signal handler. It is a bit more
complicated than the previous examples, since it also demonstrates input
event handling by means of GtkWidget::button-press-event and
GtkWidget::motion-notify-event signal handlers.

Save the following example in a file name example-4.c:

#include <gtk/gtk.h>

/* Surface to store current scribbles */
static cairo_surface_t *surface = NULL;

static void
clear_surface (void)
{
  cairo_t *cr;

  cr = cairo_create (surface);

  cairo_set_source_rgb (cr, 1, 1, 1);
  cairo_paint (cr);

  cairo_destroy (cr);
}

/* Create a new surface of the appropriate size to store our scribbles */
static gboolean
configure_event_cb (GtkWidget         *widget,
                    GdkEventConfigure *event,
                    gpointer           data)
{
  if (surface)
    cairo_surface_destroy (surface);

  surface = gdk_window_create_similar_surface (gtk_widget_get_window (widget),
                                               CAIRO_CONTENT_COLOR,
                                               gtk_widget_get_allocated_width (widget),
                                               gtk_widget_get_allocated_height (widget));

  /* Initialize the surface to white */
  clear_surface ();

  /* We've handled the configure event, no need for further processing. */
  return TRUE;
}

/* Redraw the screen from the surface. Note that the ::draw
 * signal receives a ready-to-be-used cairo_t that is already
 * clipped to only draw the exposed areas of the widget
 */
static gboolean
draw_cb (GtkWidget *widget,
         cairo_t   *cr,
         gpointer   data)
{
  cairo_set_source_surface (cr, surface, 0, 0);
  cairo_paint (cr);

  return FALSE;
}

/* Draw a rectangle on the surface at the given position */
static void
draw_brush (GtkWidget *widget,
            gdouble    x,
            gdouble    y)
{
  cairo_t *cr;

  /* Paint to the surface, where we store our state */
  cr = cairo_create (surface);

  cairo_rectangle (cr, x - 3, y - 3, 6, 6);
  cairo_fill (cr);

  cairo_destroy (cr);

  /* Now invalidate the affected region of the drawing area. */
  gtk_widget_queue_draw_area (widget, x - 3, y - 3, 6, 6);
}

/* Handle button press events by either drawing a rectangle
 * or clearing the surface, depending on which button was pressed.
 * The ::button-press signal handler receives a GdkEventButton
 * struct which contains this information.
 */
static gboolean
button_press_event_cb (GtkWidget      *widget,
                       GdkEventButton *event,
                       gpointer        data)
{
  /* paranoia check, in case we haven't gotten a configure event */
  if (surface == NULL)
    return FALSE;

  if (event->button == GDK_BUTTON_PRIMARY)
    {
      draw_brush (widget, event->x, event->y);
    }
  else if (event->button == GDK_BUTTON_SECONDARY)
    {
      clear_surface ();
      gtk_widget_queue_draw (widget);
    }

  /* We've handled the event, stop processing */
  return TRUE;
}

/* Handle motion events by continuing to draw if button 1 is
 * still held down. The ::motion-notify signal handler receives
 * a GdkEventMotion struct which contains this information.
 */
static gboolean
motion_notify_event_cb (GtkWidget      *widget,
                        GdkEventMotion *event,
                        gpointer        data)
{
  /* paranoia check, in case we haven't gotten a configure event */
  if (surface == NULL)
    return FALSE;

  if (event->state & GDK_BUTTON1_MASK)
    draw_brush (widget, event->x, event->y);

  /* We've handled it, stop processing */
  return TRUE;
}

static void
close_window (void)
{
  if (surface)
    cairo_surface_destroy (surface);
}

static void
activate (GtkApplication *app,
          gpointer        user_data)
{
  GtkWidget *window;
  GtkWidget *frame;
  GtkWidget *drawing_area;

  window = gtk_application_window_new (app);
  gtk_window_set_title (GTK_WINDOW (window), "Drawing Area");

  g_signal_connect (window, "destroy", G_CALLBACK (close_window), NULL);

  gtk_container_set_border_width (GTK_CONTAINER (window), 8);

  frame = gtk_frame_new (NULL);
  gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
  gtk_container_add (GTK_CONTAINER (window), frame);

  drawing_area = gtk_drawing_area_new ();
  /* set a minimum size */
  gtk_widget_set_size_request (drawing_area, 100, 100);

  gtk_container_add (GTK_CONTAINER (frame), drawing_area);

  /* Signals used to handle the backing surface */
  g_signal_connect (drawing_area, "draw",
                    G_CALLBACK (draw_cb), NULL);
  g_signal_connect (drawing_area,"configure-event",
                    G_CALLBACK (configure_event_cb), NULL);

  /* Event signals */
  g_signal_connect (drawing_area, "motion-notify-event",
                    G_CALLBACK (motion_notify_event_cb), NULL);
  g_signal_connect (drawing_area, "button-press-event",
                    G_CALLBACK (button_press_event_cb), NULL);

  /* Ask to receive events the drawing area doesn't normally
   * subscribe to. In particular, we need to ask for the
   * button press and motion notify events that want to handle.
   */
  gtk_widget_set_events (drawing_area, gtk_widget_get_events (drawing_area)
                                     | GDK_BUTTON_PRESS_MASK
                                     | GDK_POINTER_MOTION_MASK);

  gtk_widget_show_all (window);
}

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

  app = gtk_application_new ("org.gtk.example", G_APPLICATION_FLAGS_NONE);
  g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);
  status = g_application_run (G_APPLICATION (app), argc, argv);
  g_object_unref (app);

  return status;
}

You can compile the program above with GCC using:

gcc `pkg-config --cflags gtk+-3.0` -o example-4 example-4.c `pkg-config --libs gtk+-3.0`
I. Introduction
Начало работы с GTK
Basics
Hello, World
Hello World на языке C
Packing
Packing buttons
Custom Drawing
Рисование в ответ на ввод
Построение пользовательских интерфейсов
Упаковка кнопок с помощью GtkBuilder
Building applications
Тривиальное применение
Заполнение окна
Opening files
A menu
Диалоговое окно предпочтений
Добавление строки поиска
Добавление боковой панели
Properties
Контактная информация и сообщения об ошибках
Открытие запроса об ошибке или функции
Работа над GTK
Common Questions
General Questions
Какой виджет я должен использовать…
Вопросы о GtkWidget
GtkTextView questions
GtkTreeView questions
Использование cairo с GTK
II.Концепции GTK
Обзор модели чертежа
Окна и события
Кадровые часы
Граф сцены
Hierarchical drawing
Обзор ввода и обработки событий в GTK
Устройства и события
Event propagation
Keyboard input
Контроллеры событий и жесты
Gesture states
Обзор действий в GTK
Основы действий
Состояние и параметры действия
Action scopes
Группы действий и карты действий
Подключение действий к виджетам
Activation
Built-in Actions
III.Виджеты и объекты GTK
Object Hierarchy
Widget Gallery
GListModel support
GtkBitset — Наборы целых чисел
GtkExpression — Выражения к значениям
GtkFilterListModel — модель списка, которая фильтрует свои элементы
GtkFilter — Фильтрация элементов
GtkCustomFilter — Фильтрация с обратными вызовами
GtkMultiFilter — Объединение нескольких фильтров
GtkBoolFilter — Фильтрация по логическим выражениям
GtkStringFilter — Фильтрация по строкам
GtkFileFilter — Фильтрация файлов
GtkFlattenListModel — Модель списка, которая сглаживает список списков.
GtkMapListModel — Модель списка, которая преобразует свои элементы
GtkSliceListModel — Модель списка, представляющая срез из большего списка.
GtkSortListModel — модель списка, которая сортирует элементы
GtkSorter — Сортировка элементов
GtkCustomSorter — Сортировка с обратными вызовами
GtkMultiSorter — Объединение нескольких сортировщиков
GtkStringSorter — Сортировка путем сравнения строк
GtkNumericSorter — Сортировка путем сравнения чисел
GtkSelectionModel — Расширение интерфейса модели списка, которое обрабатывает выборки.
GtkNoSelection — Модель выбора, которая не позволяет выбирать что-либо
GtkSingleSelection — Модель выбора, позволяющая выбрать один элемент.
GtkMultiSelection — Модель выбора, позволяющая выбирать несколько элементов.
GtkSelectionFilterModel — Модель списка, которая превращает выбор в модель
GtkBookmarkList — Модель списка недавно использованных файлов
GtkDirectoryList — Модель списка для списков каталогов
GtkStringList — Модель списка для строк
List-based Widgets
Список Обзор виджетов
Terminology
За кулисами
Выбор правильной модели
Displaying trees
List styles
Сравнение с GtkTreeView
GtkListItem — Объект, используемый для представления элементов модели списка
GtkListItemFactory — Сопоставление элементов списка с виджетами
GtkSignalListItemFactory — Фабрика элементов списка, предоставляющая сигналы
GtkBuilderListItemFactory — Фабрика элементов списка с использованием файлов пользовательского интерфейса
GtkListView — Виджет для отображения списков
GtkGridView — Виджет для отображения сетки
GtkColumnView — виджет для отображения списков в нескольких столбцах
GtkColumnViewColumn — столбец, добавленный в GtkColumnView
GtkDropDown — выбрать элемент из списка
Tree support
GtkTreeListModel — модель списка, которая может создавать дочерние модели по запросу.
GtkTreeListRow — Строка в GtkTreeListModel
GtkTreeListRowSorter — Сортировка деревьев по уровням
GtkTreeExpander — кнопка расширения отступа для использования в древовидном списке
Application support
GtkApplication — Класс приложения
GtkApplicationWindow — подкласс GtkWindow с поддержкой GtkApplication.
GtkActionable — интерфейс для виджетов, которые можно связать с действиями.
Interface builder
GtkBuilder — Создание интерфейса из XML-определения пользовательского интерфейса.
GtkBuildable — Интерфейс для объектов, которые могут быть созданы с помощью GtkBuilder.
GtkBuilderScope — привязки для GtkBuilder
Windows
GtkRoot — Интерфейс для корневых виджетов
GtkNative — интерфейс для виджетов с поверхностями
GtkWindow — Верхний уровень, который может содержать другие виджеты.
GtkDialog — Создание всплывающих окон
GtkMessageDialog — Удобное окно сообщений
GtkAboutDialog — Показать информацию о приложении
GtkAssistant — виджет, используемый для помощи пользователям в многоэтапных операциях.
GtkWindowGroup — Ограничение эффекта захватов
GtkNativeDialog — интеграция с нативными диалогами
Layout Containers
GtkBox — контейнер для упаковки виджетов в одну строку или столбец.
GtkCenterBox — Центрирующий контейнер
GtkGrid — упаковка виджетов в строки и столбцы
GtkRevealer — Скрыть и показать с анимацией
GtkListBox — Контейнер списка
GtkFlowBox — контейнер, который позволяет перекомпоновывать свои дочерние элементы.
GtkStack — стекирующий контейнер
GtkStackSwitcher — контроллер для GtkStack
GtkStackSidebar — автоматический виджет боковой панели
GtkActionBar — полоса полной ширины для представления контекстных действий.
GtkHeaderBar — Блок с дочерним элементом по центру
GtkOverlay — Контейнер, который накладывает виджеты друг на друга.
GtkPaned — виджет с двумя настраиваемыми панелями.
GtkNotebook — Контейнер блокнота с вкладками
GtkExpander — Контейнер, который может скрывать дочерний элемент
GtkOrientable — интерфейс для переворачиваемых виджетов
GtkAspectFrame — Кадр, который ограничивает дочерний элемент определенным соотношением сторон.
GtkFixed — контейнер, позволяющий размещать виджеты в фиксированных координатах.
Layout Managers
GtkLayoutManager — Базовый класс для менеджера компоновки
GtkLayoutChild — объект, содержащий свойства макета
GtkBinLayout — Менеджер компоновки для корзиноподобных виджетов
GtkBoxLayout — менеджер компоновки для размещения всех дочерних элементов в одной строке или столбце
GtkCenterLayout — Центрирующий макет
GtkFixedLayout — менеджер компоновки, который позволяет позиционировать в фиксированных координатах.
GtkGridLayout — Менеджер компоновки для виджетов в виде сетки
GtkOverlayLayout — менеджер компоновки, который размещает виджеты как наложения
GtkCustomLayout — Удобный менеджер компоновки
GtkConstraintLayout — Менеджер компоновки, использующий ограничения
GtkConstraint — Описание ограничения
GtkConstraintGuide — Невидимая цель ограничения
Display Widgets
GtkLabel — виджет, отображающий небольшое или среднее количество текста.
GtkImage — виджет, отображающий изображение
GtkPicture — виджет, отображающий GdkPaintable
GtkSpinner — Показать анимацию спиннера
GtkInfoBar — Сообщать пользователю о важных сообщениях
GtkProgressBar — виджет, визуально отображающий прогресс
GtkLevelBar — бар, который можно использовать как индикатор уровня
GtkStatusbar — Сообщать о сообщениях второстепенной важности для пользователя
GtkCalendar — отображает календарь и позволяет пользователю выбрать дату
Media Support
GtkVideo — виджет для отображения видео
GtkMediaControls — виджет, показывающий элементы управления медиапотоком
GtkMediaStream — Показать медиа в GTK
GtkMediaFile — открывает медиафайлы для использования в GTK
Кнопки и переключатели
GtkButton — виджет, который излучает сигнал при нажатии.
GtkCheckButton — Создание виджетов с отдельной кнопкой-переключателем
GtkToggleButton — Создает кнопки, которые сохраняют свое состояние
GtkLinkButton — Создание кнопок, привязанных к URL
GtkMenuButton — виджет, который показывает всплывающее окно при нажатии.
GtkSwitch — переключатель в стиле «выключатель света».
GtkScaleButton — Кнопка, которая вызывает шкалу
GtkVolumeButton — Кнопка, которая вызывает регулятор громкости.
GtkLockButton — виджет для разблокировки или блокировки привилегированных операций.
Ввод числовых и текстовых данных
GtkEditable — Интерфейс для виджетов редактирования текста
GtkEntryBuffer — Текстовый буфер для GtkEntry
GtkText — Простое однострочное поле ввода текста
GtkEntry — однострочное поле ввода текста
GtkEntryCompletion — Функция завершения для GtkEntry
GtkPasswordEntry — Запись секретов
GtkScale — Ползунок для выбора значения из диапазона
GtkSpinButton — Получить целое число или число с плавающей запятой от пользователя
GtkSearchEntry — запись со значком поиска
GtkSearchBar — панель инструментов для интеграции записи поиска с
GtkEditableLabel — метка, которую можно редактировать
Многострочный текстовый редактор
Обзор текстового виджета
Simple Example
Пример изменения атрибутов текста
GtkTextIter — Итератор текстового буфера
GtkTextMark — позиция в буфере, сохраняемая при изменениях буфера.
GtkTextBuffer — Сохраняет атрибутированный текст для отображения в GtkTextView
GtkTextTag — Тег, который можно применить к тексту в GtkTextBuffer.
GtkTextTagTable — Коллекция тегов, которые можно использовать вместе
GtkTextView — Виджет, отображающий GtkTextBuffer
Виджеты сетки деревьев,списков и иконок
Обзор виджета «Дерево и список
Создание модели
Создание компонента представления
Колонки и рендеринг ячеек
Selection handling
Simple Example
GtkTreeModel — древовидный интерфейс, используемый GtkTreeView
GtkTreeSelection — Объект выбора для GtkTreeView
GtkTreeViewColumn — видимый столбец в виджете GtkTreeView
GtkTreeView — виджет для отображения как деревьев, так и списков
GtkTreeView drag-and-drop — Интерфейсы для поддержки перетаскивания в GtkTreeView
GtkCellView — виджет, отображающий одну строку GtkTreeModel.
GtkIconView — виджет, отображающий список иконок в сетке.
GtkTreeSortable — Интерфейс для сортируемых моделей, используемый GtkTreeView.
GtkTreeModelSort — GtkTreeModel, который делает базовую древовидную модель сортируемой.
GtkTreeModelFilter — GtkTreeModel, который скрывает части базовой модели дерева.
GtkCellLayout — Интерфейс для упаковки ячеек
GtkCellArea — абстрактный класс для размещения GtkCellRenderers.
GtkCellAreaBox — область ячейки, которая отображает GtkCellRenderers в строку или столбец.
GtkCellAreaContext — сохраняет геометрическую информацию для ряда строк в GtkCellArea.
GtkCellRenderer — Объект для рендеринга одной ячейки
GtkCellEditable — Интерфейс для виджетов, которые можно использовать для редактирования ячеек.
GtkCellRendererAccel — Визуализирует клавиатурный ускоритель в ячейке
GtkCellRendererCombo — Визуализирует поле со списком в ячейке
GtkCellRendererPixbuf — Отрисовывает картинку в ячейке
GtkCellRendererProgress — Визуализирует числа в виде индикаторов выполнения
GtkCellRendererSpin — Визуализирует кнопку прокрутки в ячейке
GtkCellRendererText — Визуализирует текст в ячейке
GtkCellRendererToggle — Визуализирует кнопку-переключатель в ячейке
GtkCellRendererSpinner — Визуализирует анимацию вращения в ячейке
GtkListStore — Структура данных, похожая на список, которую можно использовать с GtkTreeView.
GtkTreeStore — древовидная структура данных, которую можно использовать с GtkTreeView.
Меню,комбинированный блок
GtkComboBox — виджет, используемый для выбора из списка элементов
GtkComboBoxText — простое текстовое поле со списком
GtkPopover — Пузыри, зависящие от контекста
GtkPopoverMenu — всплывающие окна для использования в качестве меню
GtkPopoverMenuBar — Строка меню с всплывающими окнами
GtkDropDown — выбрать элемент из списка
Виджеты и диалоги селектора
GtkColorChooser — Интерфейс, реализованный виджетами для выбора цвета
GtkColorButton — Кнопка для запуска диалогового окна выбора цвета
GtkColorChooserWidget — Виджет для выбора цвета
GtkColorChooserDialog — Диалог выбора цветов
GtkFileChooser — Интерфейс выбора файлов, используемый GtkFileChooserWidget и GtkFileChooserDialog
GtkFileChooserNative — собственный диалог выбора файла, подходящий для команд «Открыть файл» или «Сохранить файл».
GtkFileChooserDialog — диалоговое окно выбора файла, подходящее для команд «Открыть файл» или «Сохранить файл».
GtkFileChooserWidget — Виджет выбора файлов
GtkFontChooser — Интерфейс, реализованный виджетами, отображающими шрифты.
GtkFontButton — Кнопка для запуска диалогового окна выбора шрифта
GtkFontChooserWidget — Виджет для выбора шрифтов
GtkFontChooserDialog — Диалог для выбора шрифтов
GtkEmojiChooser — всплывающее окно для выбора символа эмодзи
Виджеты для пользовательского рисования
GtkDrawingArea — Простой виджет для рисования с помощью cairo
GtkGLArea — виджет для пользовательского рисования с помощью OpenGL.
Ornaments
GtkFrame — виджет с декоративной рамкой и опциональной меткой.
GtkSeparator — Виджет-разделитель
Scrolling
GtkScrollbar — полоса прокрутки
GtkScrolledWindow — Добавляет полосы прокрутки к своему дочернему виджету
GtkScrollable — Интерфейс для прокручиваемых виджетов.
GtkViewport — адаптер, делающий виджеты прокручиваемыми.
Printing
GtkPrintOperation — высокоуровневый API печати
GtkPrintContext — Инкапсулирует контекст для страниц рисования
GtkPrintSettings — сохраняет настройки печати
GtkPageSetup — сохраняет информацию о настройке страницы
GtkPaperSize — Поддержка именованных размеров бумаги
GtkPrinter — представляет принтер
GtkPrintJob — Представляет задание на печать
GtkPrintUnixDialog — диалоговое окно печати
GtkPageSetupUnixDialog — Диалог настройки страницы
Shortcuts Overview
GtkShortcutsWindow — Верхний уровень, который показывает справку по ярлыкам.
GtkShortcutsSection — представляет режим приложения в GtkShortcutsWindow.
GtkShortcutsGroup — представляет группу ярлыков в GtkShortcutsWindow.
GtkShortcutsShortcut — Представляет сочетание клавиш в GtkShortcutsWindow.
GtkShortcutLabel — Отображает сочетание клавиш
Accessibility
GTK Accessibility
Стандартный интерфейс доступности
Доступные роли и атрибуты
Правила разработки приложений
Implementations
Authoring practices
Скрытие элементов пользовательского интерфейса из доступного дерева
Шаблоны проектирования и пользовательские виджеты
GtkAccessible — Доступный интерфейс
GtkATContext — объект, взаимодействующий со вспомогательными технологиями.
Miscellaneous
GtkAdjustment — Представление регулируемого ограниченного значения
GtkIMContextSimple — Контекст метода ввода, поддерживающий табличные методы ввода.
GtkIMMulticontext — контекст метода ввода, поддерживающий несколько загружаемых методов ввода.
GtkSizeGroup — группирует виджеты, чтобы они запрашивали одинаковый размер
GtkSnapshot — Вспомогательный объект для снимков
GtkTooltip — Добавляйте подсказки к своим виджетам
GtkWidgetPaintable — Рисование виджета в другом месте
GtkWindowControls — Виджет, отображающий оконные кнопки
GtkWindowHandle — Виджет области заголовка
Абстрактные базовые классы
GtkWidget — Базовый класс для всех виджетов
GtkRange — Базовый класс для виджетов, визуализирующих корректировку
GtkIMContext — Базовый класс для контекстов методов ввода
GtkNativeDialog — интеграция с нативными диалогами
GtkAccessible — Доступный интерфейс
Недавно использованные документы
GtkRecentManager — Управление недавно использованными файлами
Выбор из установленных приложений
GtkAppChooser — Интерфейс, реализованный виджетами для выбора приложения
GtkAppChooserButton — Кнопка для запуска диалогового окна выбора приложений.
GtkAppChooserDialog — диалог выбора приложений
GtkAppChooserWidget — Виджет выбора приложений, который может быть встроен в другие виджеты.
Жесты и обработка событий
GtkEventController — Автономный обработчик серии событий
GtkEventControllerKey — Контроллер событий для ключевых событий
GtkEventControllerFocus — Контроллер событий для фокуса
GtkEventControllerLegacy — Контроллер событий для разных событий
GtkEventControllerScroll — Контроллер событий для событий прокрутки
GtkEventControllerMotion — Контроллер событий для событий движения
GtkGesture — Базовый класс для жестов
GtkGestureSingle — Базовый класс для жестов мыши/в одно касание
GtkGestureDrag — Жест перетаскивания
GtkGestureLongPress — жест «Нажать и удерживать»
GtkGestureClick — Жест многократного нажатия
GtkGesturePan — жест панорамирования
GtkGestureSwipe — Жест смахивания
GtkGestureRotate — жест поворота
GtkGestureZoom — жест масштабирования
GtkGestureStylus — Жест для ввода стилусом
GtkPadController — Контроллер для рисования планшетов
GtkShortcutController — Контроллер событий для ярлыков
Keyboard shortcuts
Keyboard Accelerators — Утилиты для акселераторов
GtkShortcut — объект, описывающий сочетание клавиш
GtkShortcutTrigger — Триггеры для отслеживания, должны ли быть активированы ярлыки
GtkShortcutAction — отслеживание необходимости активации ярлыков
GtkShortcutManager — Интерфейс для управления ярлыками
Обмен данными,буферы обмена и Drag-and-Drop
GtkDragSource — Контроллер событий, чтобы инициировать операции DND
GtkDragIcon — Верхний уровень для использования в качестве значка перетаскивания
GtkDropTarget — Контроллер событий для получения сбросов DND
GtkDropTargetAsync — Контроллер событий для получения сбросов DND
GtkDropControllerMotion — Контроллер событий для событий движения во время падения
IV.Справочник по ядру GTK
Initialization — Инициализация библиотеки и основной цикл
Информация о версии — переменные и функции для проверки версии GTK.
Настройки — обмен настройками между приложениями.
Стандартные перечисления — общедоступные перечисляемые типы, используемые в GTK.
Тестирование — Утилиты для тестирования приложений GTK.
Утилиты файловой системы — Функции для работы с GIO
V.Теминг в GTK
CSS в GTK
CSS nodes
CSS-узлы шкалы GtkScale
Selectors
Селекторы GTK CSS
Свойства CSS GTK
Basic types
Colors
Images
Свойства CSS GTK
GtkStyleContext — Отрисовка элементов пользовательского интерфейса
GtkCssProvider — CSS-стиль для виджетов
GtkStyleProvider — Интерфейс для предоставления информации о стиле в GtkStyleContext
GtkIconTheme — Поиск иконок по имени
VI.Переход с предыдущих версий GTK
Переход от GTK 2.x к GTK 4
Переход от GTK 3.x к GTK 4
Подготовка в GTK 3.x
Не используйте устаревшие символы
Включить диагностические предупреждения
Не используйте специфические для GTK аргументы командной строки
Не используйте свойства стиля виджета
Просмотрите флаги создания окна
Прекратите использовать прямой доступ к структурам GdkEvent
Прекратите использовать gdk_pointer_warp()
Прекратите использовать не-RGBA визуальные эффекты
Перестаньте использовать дочерние свойства GtkBox padding,fill и expand
Перестаньте использовать аргумент state в геттерах GtkStyleContext
Прекратите использовать gdk_pixbuf_get_from_window() и gdk_cairo_set_source_surface()
Прекратите использовать сигналы событий GtkWidget
Установите правильный идентификатор приложения
Прекратите использовать gtk_main() и связанные API
Уменьшите использование gtk_widget_destroy()
Сократить использование общих API для контейнеров
Проанализируйте использование ресурсов иконы
Изменения,которые необходимо произвести во время переключения
Larger changes
Прекратите использование GdkScreen
Прекратите использовать корневое окно
Прекратите использование GdkVisual
Прекратите использование GdkDeviceManager
Адаптация к изменениям API GdkWindow
Состояние окна «с иконкой» было переименовано в «свернутое»
Адаптация к изменениям API GdkEvent
Перестаньте использовать захват
Адаптация к координации изменений API
Адаптация к изменениям API GdkKeymap
Адаптация к изменениям в работе с модификаторами клавиатуры
Замените GtkClipboard на GdkClipboard.
Прекратите использовать gtk_get_current_... API
Преобразование файлов ui
Адаптироваться к изменениям GtkBuilder API
Адаптация к изменениям API контроллера событий
Изменения в работе с фокусом
Используйте новые apis для клавиатурных сокращений
Прекратите использовать GtkEventBox
Прекратите использовать GtkButtonBox
Адаптация к изменениям API GtkBox
Адаптация к изменениям API GtkWindow
Адаптация к изменениям API GtkHeaderBar и GtkActionBar
Адаптация к изменениям API GtkStack,GtkAssistant и GtkNotebook
Адаптация к изменениям иерархии классов кнопок
Адаптация к изменениям API GtkScrolledWindow
Адаптация к удалению GtkBin
Адаптация к удалению GtkContainer
Перестаньте использовать GtkContainer::border-width
Адаптироваться к удалению gtk_widget_destroy()
Адаптация к координации изменений API
Адаптация к изменениям API GtkStyleContext
Адаптация к изменениям API GtkCssProvider
Прекратите использовать свойства GtkShadowType и GtkRelief
Адаптация к изменениям запроса размера GtkWidget
Адаптация к изменениям в распределении размеров GtkWidget
Переход на дочерние API GtkWidget
Не используйте -gtk-gradient в вашем CSS
Не используйте -gtk-icon-effect в своем CSS
Не используйте -gtk-icon-theme в своем CSS
Не используйте -gtk-outline-…-radius в своем CSS
Адаптация к изменениям модели чертежа
Прекратите использовать API для запроса GdkSurfaces
Виджеты теперь видны по умолчанию
Адаптация к изменениям в анимированном скрытии и показе виджетов
Прекратите передавать аргументы командной строки в gtk_init
GdkPixbuf деэмалирован
Сигналы событий GtkWidget удалены
Обработка недействительности изменилась
Перестаньте использовать GtkWidget::draw
Наблюдение за содержимым окна изменилось
Работа с монитором изменилась
Адаптация для отслеживания изменений в API
Адаптация к изменениям API курсора
Адаптация к изменениям API размера иконок
Адаптация к изменениям в API GtkAssistant
Адаптация к изменениям в API GtkEntry,GtkSearchEntry и GtkSpinButton
Адаптация к изменениям в API GtkOverlay
Используйте GtkFixed вместо GtkLayout
Адаптация к изменениям в поисковой выдаче
Адаптация к изменениям GtkScale
Прекратите использовать gtk_window_activate_default()
Прекратите использовать gtk_widget_grab_default()
Прекратите устанавливать ::has-default и ::has-focus в .ui файлах
Перестаньте использовать сигнал GtkWidget::display-changed
GtkPopover::modal был переименован в autohide
gtk_widget_get_surface был удален
gtk_widget_is_toplevel был удален
gtk_widget_get_toplevel был удален
GtkEntryBuffer ::deleted-text изменился
GtkMenu,GtkMenuBar и GtkMenuItem исчезли
GtkToolbar был удален
GtkAspectFrame больше не является фреймом
Прекратите использовать пользовательские окна всплывающих подсказок
Переход на новый Drag-and-Drop api
Адаптация к изменениям API GtkIconTheme
Обновление изменений в API GtkFileChooser
Прекратите использовать блокирующие диалоговые функции
Прекратите использовать API GtkBuildable
Адаптация к изменениям API GtkAboutDialog
Адаптация к изменениям контекста всплывающей подсказки GtkTreeView и GtkIconView
Прекратите использовать GtkFileChooserButton
Адаптироваться к измененным свойствам GtkSettings
Изменения,которые следует учитывать после перехода
Рассмотрите возможность переноса на новые виджеты списков
VII.Инструменты GTK
gtk4-demo — Демонстрация виджетов GTK
gtk4-demo-application — Демонстрация GtkApplication
gtk4-widget-factory — Демонстрация виджетов GTK
gtk4-icon-browser — Список тематических иконок
gtk4-update-icon-cache — Утилита кэширования тем значков
gtk4-encode-symbolic-svg — Утилита преобразования символических иконок
gtk4-builder-tool — файловая утилита GtkBuilder
gtk4-launch — Запустить приложение
gtk4-query-settings — Утилита для печати имени и значения всех свойств GtkSettings.
gtk4-broadwayd — сервер отображения Broadway
VIII.Поддержка платформ GTK
Компиляция библиотек GTK
Building GTK
Build types
Debug builds
Release builds
Dependencies
Сборка и тестирование GTK
Дополнительные параметры конфигурации
x11-backend , win32-backend , broadway-backend , wayland-backend и macos-backend
vulkan
xinerama
media
print
cloudproviders
sysprof
tracker
colord
gtk_doc и справочные man-pages
introspection
build-tests, install-tests, demos
Компиляция приложений GTK на UNIX
Запуск и отладка приложений GTK
Environment variables
GTK_DEBUG
GTK_PATH
GTK_IM_MODULE
GTK_MEDIA
GTK_EXE_PREFIX
GTK_DATA_PREFIX
GTK_THEME
GDK_PIXBUF_MODULE_FILE
GDK_DEBUG
GSK_DEBUG
GDK_BACKEND
GDK_VULKAN_DEVICE
GSK_RENDERER
GTK_CSD
GTK_A11Y
XDG_DTA_HOME, XDG_DATA_DIRS
DESKTOP_STARTUP_ID
Interactive debugging
Profiling
GTK для X Window System
Переменные среды,специфичные для X11
GDK_SYNCHRONIZE
GDK_SCALE
Понимание архитектуры X11
Сервер,клиент,оконный менеджер
Использование GTK в Windows
Переменные среды,специфичные для Windows
GDK_IGNORE_WINTAB
GDK_USE_WINTAB
Работа с курсорами,специфичная для Windows
Использование GTK на Apple macOS
Использование GTK с Broadway
Переменные среды,специфичные для Бродвея
BROADWAY_DISPLAY
Использование GTK с Wayland
Переменные среды,специфичные для Wayland
WAYLAND_DISPLAY
XDG_RUNTIME_DIR
Указатель всех символов
Annotation Glossary


GTK

4.0

  • Accessibility

  • Application development rules

  • Overview of actions in GTK

  • Build types

Понравилась статья? Поделить с друзьями:
  • Руководство одкб сегодня
  • Фссп кто осуществляет руководство органом
  • Star 60050 телескоп инструкция на русском
  • Nolotil 575 mg capsulas duras инструкция на русском
  • Инструкция по самоохране радиационных объектов образец