13. Глава 25: Glade – Визуальный Редактор Интерфейсов

Glade — это мощный визуальный редактор интерфейсов для GTK. Он позволяет создавать сложные пользовательские интерфейсы методом «drag-and-drop», без необходимости писать каждую строку кода для размещения и настройки виджетов. Созданный в Glade интерфейс сохраняется в XML-файле (обычно с расширением .glade), который затем может быть загружен и использован в вашем C-коде через объект GtkBuilder.

Использование Glade значительно ускоряет разработку GUI, делает код более чистым, отделяя логику приложения от описания интерфейса, и позволяет дизайнерам работать над внешним видом независимо от программистов.

Основные темы этой главы:

  • Установка Glade на Raspberry Pi и других системах Linux.

  • Создание ``.glade``-файла: Как создать простой интерфейс в Glade, назначить ID виджетам и связать сигналы.

  • Лучшие практики создания интерфейсов в Glade (логичные ID, адаптивные контейнеры).

  • Загрузка интерфейса в C через GtkBuilder: Как прочитать .glade-файл и получить ссылки на виджеты.

  • Автоматическое связывание сигналов с обработчиками в C-коде.

  • Передача пользовательских данных в функции обратного вызова сигналов.

  • Отладка GTK-интерфейсов с помощью GtkInspector.

  • Компиляция и запуск GTK-приложения, использующего Glade.

### Основные понятия и функции

  • ``Glade``: Визуальный редактор интерфейсов для GTK, позволяющий создавать GUI методом «drag-and-drop».

  • ``.glade``-файл: XML-файл, сгенерированный Glade, который описывает структуру пользовательского интерфейса.

  • ``GtkBuilder``: Объект GTK, который парсит .glade-файл и создает соответствующие виджеты в памяти.

  • ``gtk_builder_new_from_file()``: Создает новый объект GtkBuilder и загружает в него интерфейс из указанного .glade-файла.

  • ``gtk_builder_get_object()``: Получает указатель на виджет из загруженного интерфейса по его ID (строковому идентификатору), который был задан в Glade.

  • ``gtk_builder_connect_signals()``: Автоматически подключает все сигналы, определенные в .glade-файле, к функциям обратного вызова в вашем C-коде (если имена функций совпадают).

  • ID виджета: Уникальный строковый идентификатор, который вы присваиваете каждому виджету в Glade, чтобы можно было получить к нему доступ из кода. Используйте логичные и описательные ID (например, btn_submit, entry_username, label_status).

  • Имя обработчика сигнала (Handler Name): Имя функции в вашем C-коде, которая будет вызываться при срабатывании определенного сигнала виджета (например, on_button_clicked для сигнала clicked кнопки).

### Исходные файлы в этой главе

В этой главе мы рассмотрим один комплексный пример:

  • glade_example.c — Демонстрирует полный процесс создания простого интерфейса в Glade, сохранения его в .glade-файл, а затем загрузки и использования в GTK-приложении на C, включая передачу пользовательских данных.

  • interface.glade — Файл интерфейса, который вы создадите с помощью Glade.

### Установка Glade

Для установки Glade на большинстве дистрибутивов Linux (включая Raspberry Pi OS, Ubuntu, Debian) используйте следующие команды в терминале:

sudo apt update           # Обновляем список пакетов
sudo apt install glade    # Устанавливаем Glade

После успешной установки вы сможете запустить Glade из терминала командой:

glade

Или найдите его в меню приложений вашей операционной системы.

### Создание .glade-интерфейса

Мы создадим простой интерфейс с главным окном, полем ввода текста (GtkEntry), меткой (GtkLabel) и кнопкой (GtkButton). При нажатии на кнопку текст из поля ввода будет копироваться в метку.

  1. Запустите Glade. Вы увидите пустое окно Glade.

  2. В палитре виджетов (обычно слева) найдите ``GtkWindow`` (в категории «Top Level») и перетащите его в центральную область. Это будет ваше основное окно.

  3. На правой панели, во вкладке «General», в поле «ID» (идентификатор), задайте уникальное имя для окна: main_window. В поле «Title» (заголовок) введите "Glade Пример".

  4. Добавьте ``GtkBox`` (Gtk-контейнер, обычно «Containers» -> «GtkBox») внутрь main_window. Выберите вертикальную ориентацию (GtkOrientation Vertical) и установите небольшой отступ (например, 10px) между элементами в свойстве «Spacing». Это поможет расположить виджеты аккуратно. Вкладка «Packing» (Паковка) для GtkBox позволяет настроить отступы между его содержимым и отступы от краев контейнера.

  5. Добавьте ``GtkEntry`` (поле ввода текста) внутрь GtkBox. Выберите GtkEntry и на правой панели, во вкладке «General», задайте его ID как entry_input. В поле «Placeholder Text» (текст-заполнитель) введите "Введите текст здесь...".

  6. Добавьте ``GtkButton`` внутрь того же GtkBox. Выберите GtkButton и на правой панели, во вкладке «General», задайте его ID как btn_copy. В поле «Label» (метка) напишите "Скопировать текст".

  7. Все еще выбрав GtkButton, перейдите на вкладку «Signals» (Сигналы) на правой панели.

  8. В разделе «GtkButton» найдите сигнал ``clicked``. Дважды щелкните по нему или нажмите кнопку «+» рядом с ним.

  9. В появившемся окне в поле «Handler» (Обработчик) введите имя функции, которая будет вызвана при нажатии кнопки. Назовите её on_btn_copy_clicked. Нажмите Enter или «OK».

  10. Добавьте ``GtkLabel`` (метку для вывода текста) внутрь того же GtkBox, после кнопки. Задайте ее ID как label_output. В поле «Label» пока ничего не пишите или напишите "Здесь будет ваш текст".

  11. Сохраните проект. Перейдите в меню «File» → «Save» (или «Save As…»). Сохраните файл как interface.glade в той же директории, где вы планируете хранить свой C-код.

Теперь у вас есть interface.glade файл, описывающий ваш GUI.

### Лучшие практики создания интерфейсов в Glade

  • Используйте логичные ID: Присваивайте каждому виджету уникальные и осмысленные ID (например, btn_submit, entry_username, label_error). Это значительно упрощает поиск и доступ к виджетам из вашего C-кода.

  • Организуйте интерфейс с контейнерами: Избегайте использования GtkFixed для сложных интерфейсов, так как он не является адаптивным. Вместо этого используйте:
    • ``GtkBox``: Для последовательного расположения виджетов по горизонтали или вертикали.

    • ``GtkGrid``: Для табличного расположения виджетов, где вы можете точно указать строки и столбцы.

    • ``GtkStack``: Для создания интерфейсов с несколькими «страницами» или видами, где только одна страница видна одновременно.

  • Используйте вложенные контейнеры для адаптивного дизайна: Комбинируйте различные типы контейнеров. Например, GtkBox внутри ячейки GtkGrid позволит вам создать сложный, но при этом легко масштабируемый и адаптивный макет.

### Пример 25.1: Загрузка Glade-интерфейса и Связывание Сигналов с Пользовательскими Данными

Файл: glade_example.c

Этот пример демонстрирует, как загрузить интерфейс, созданный в Glade (interface.glade), получить доступ к виджетам по их ID и автоматически подключить обработчики сигналов. Самое главное, он показывает, как передать структуру пользовательских данных в функцию обратного вызова, чтобы обработчик мог взаимодействовать с несколькими виджетами.

#include <gtk/gtk.h> // Подключаем библиотеку GTK.

/**
 * @brief Структура для хранения указателей на виджеты и других данных,
 * которые должны быть доступны в функциях обратного вызова.
 */
typedef struct {
    GtkWidget *entry_input;  // Указатель на поле ввода GtkEntry.
    GtkWidget *label_output; // Указатель на метку GtkLabel для вывода.
} AppData;

/**
 * @brief Обработчик сигнала 'clicked' для кнопки 'btn_copy'.
 * Копирует текст из поля ввода в метку.
 *
 * @param button Указатель на объект GtkButton, который сгенерировал сигнал.
 * @param user_data Пользовательские данные, приведенные к типу AppData*.
 */
void on_btn_copy_clicked(GtkButton *button, gpointer user_data) {
    // Приводим gpointer к нашему типу AppData*.
    AppData *app = (AppData *)user_data;

    // Получаем текст из поля ввода.
    const gchar *text = gtk_entry_get_text(GTK_ENTRY(app->entry_input));

    // Устанавливаем полученный текст в метку.
    gtk_label_set_text(GTK_LABEL(app->label_output), text);

    g_print("Текст скопирован: %s\n", text); // Выводим в консоль для отладки.
}

/**
 * @brief Главная функция программы.
 * Инициализирует GTK, загружает интерфейс из Glade-файла,
 * подключает сигналы и запускает главный цикл GTK.
 *
 * @param argc Количество аргументов командной строки.
 * @param argv Массив строк аргументов командной строки.
 * @return Код завершения программы.
 */
int main(int argc, char *argv[]) {
    GtkBuilder *builder; // Объявляем указатель на GtkBuilder.
    GtkWidget *window;   // Объявляем указатель на главное окно.
    AppData app;         // Объявляем структуру для хранения данных приложения.

    gtk_init(&argc, &argv); // Инициализируем библиотеку GTK.

    // Создаем новый объект GtkBuilder и загружаем в него интерфейс из файла "interface.glade".
    if (!g_file_test("interface.glade", G_FILE_TEST_EXISTS)) {
        g_critical("Ошибка: файл interface.glade не найден в текущей директории.");
        g_critical("Убедитесь, что вы создали его с помощью Glade и сохранили.");
        return 1;
    }
    builder = gtk_builder_new_from_file("interface.glade");

    // Если builder не смог загрузить файл, выходим.
    if (!builder) {
        g_critical("Не удалось загрузить Glade-файл: interface.glade. Проверьте его синтаксис.");
        return 1;
    }

    // Получаем указатель на главное окно по его ID "main_window".
    window = GTK_WIDGET(gtk_builder_get_object(builder, "main_window"));
    if (!window) {
        g_critical("Ошибка: не удалось получить виджет 'main_window' из Glade-файла.");
        return 1;
    }

    // Получаем указатели на поле ввода и метку по их ID.
    app.entry_input = GTK_WIDGET(gtk_builder_get_object(builder, "entry_input"));
    app.label_output = GTK_WIDGET(gtk_builder_get_object(builder, "label_output"));

    // Проверяем, что все нужные виджеты были найдены.
    if (!app.entry_input || !app.label_output) {
        g_critical("Ошибка: не удалось получить один или несколько виджетов (entry_input, label_output).");
        g_critical("Убедитесь, что их ID правильно заданы в Glade-файле.");
        return 1;
    }

    // Подключаем все сигналы, определенные в Glade-файле, к соответствующим функциям в коде.
    // Передаем адрес нашей структуры 'app' в качестве пользовательских данных.
    gtk_builder_connect_signals(builder, &app);

    // Соединяем сигнал "destroy" главного окна с функцией завершения GTK.
    // Это гарантирует, что приложение закроется при закрытии окна.
    g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);

    // Отображаем все виджеты в окне.
    gtk_widget_show_all(window);

    // Запускаем главный цикл GTK. Программа будет ожидать событий (нажатий кнопок и т.д.).
    gtk_main();

    // Освобождаем ресурсы GtkBuilder.
    // Важно освободить builder после того, как все виджеты и сигналы подключены,
    // так как builder больше не нужен, но виджеты остаются в памяти.
    g_object_unref(builder);

    return 0; // Возвращаем 0, показывая успешное завершение.
}

### Компиляция и Запуск

Перед компиляцией убедитесь, что вы выполнили шаги по «Созданию ``.glade``-интерфейса» и сохранили файл как ``interface.glade`` в той же директории, что и ``glade_example.c``.

  1. Сохраните код из [примера 25.1](glade_example.c) в файл с названием glade_example.c.

  2. Убедитесь, что файл interface.glade находится в той же директории.

  3. Откройте терминал и перейдите в директорию, где вы сохранили файлы.

  4. Скомпилируйте программу, используя pkg-config для автоматического подключения необходимых флагов GTK: .. code-block:: bash

    gcc glade_example.c -o glade_example pkg-config –cflags –libs gtk+-3.0

    Примечание: Если вы используете Glade с GTK4, флаг будет ``gtk4``.

    Важно: Исполняемый файл (glade_example) должен иметь доступ к interface.glade во время выполнения. Обычно это означает, что они должны быть в одной директории, или interface.glade должен быть установлен в известное системе место (например, через make install или CMake). Для CMake-проектов, чтобы убедиться, что .glade файл копируется рядом с исполняемым файлом или в нужное место, можно использовать: .. code-block:: cmake

    # В вашем CMakeLists.txt install(FILES interface.glade DESTINATION bin) # Копирует interface.glade в директорию bin

  5. Запустите скомпилированную программу: .. code-block:: bash

    ./glade_example

### Ожидаемый Результат

  • После запуска программы появится окно с заголовком «Glade Пример».

  • В окне будет поле ввода текста (GtkEntry) с текстом-заполнителем «Введите текст здесь…».

  • Под полем ввода будет кнопка «Скопировать текст».

  • Под кнопкой будет метка (GtkLabel), изначально отображающая «Здесь будет ваш текст» или похожий текст.

  • Введите любой текст в поле ввода.

  • Нажмите кнопку «Скопировать текст».

  • Текст, который вы ввели в поле ввода, мгновенно появится в метке.

  • В терминале, из которого вы запустили программу, будет выведено сообщение: «Текст скопирован: [ваш_текст]».

  • Закрытие окна приведет к завершению программы.

### Отладка GTK-интерфейсов с GtkInspector

GtkInspector — это мощный инструмент для отладки и проверки GTK-приложений. Он позволяет на лету изменять свойства виджетов, просматривать их иерархию, стили CSS и многое другое. Это незаменимый помощник при работе с Glade и GTK.

Чтобы запустить ваше GTK-приложение с активированным GtkInspector:

GTK_DEBUG=interactive ./glade_example

После запуска приложения: * Нажмите комбинацию клавиш Ctrl + Shift + I (латинская «i»). Откроется отдельное окно GtkInspector. * Используйте вкладки Inspector для просмотра:

  • Objects: Иерархия виджетов в вашем приложении.

  • CSS: Применяемые CSS-стили к выбранному виджету.

  • Properties: Все свойства выбранного виджета и их текущие значения. Вы можете изменять их на лету!

  • Measuring: Инструменты для измерения отступов и размеров виджетов.

## Дополнительные Ресурсы

Для более глубокого изучения Glade и GtkBuilder обратитесь к официальной документации: