10. Глава 22: Диалоговые Окна и Окна Сообщений

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

GTK предлагает два основных подхода для создания диалоговых окон:

  • GtkMessageDialog: Это специализированный и простой в использовании диалог, предназначенный для отображения стандартных сообщений (информация, предупреждение, ошибка, вопрос). Он быстро создается и настраивается для типовых сценариев.

  • GtkDialog: Это более универсальный и настраиваемый базовый класс для всех диалоговых окон в GTK. Он позволяет создавать собственные диалоги с любым содержимым, заголовком, набором кнопок и поведением.

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

  • `gtk_message_dialog_new()`: Создаёт новый GtkMessageDialog с заданными параметрами.

  • `gtk_dialog_run()`: Отображает диалог и запускает локальный цикл событий, ожидая действия пользователя. Возвращает ответ пользователя (например, OK, Cancel).

  • `gtk_widget_destroy()`: Удаляет виджет из памяти. Важно вызывать для диалогов после gtk_dialog_run().

  • `gtk_dialog_new_with_buttons()`: Создаёт новый GtkDialog с заданным заголовком и предустановленными кнопками.

  • `gtk_dialog_get_content_area()`: Получает указатель на область содержимого GtkDialog, куда можно добавлять свои виджеты.

  • `GTK_DIALOG_DESTROY_WITH_PARENT`: Флаг для GtkMessageDialog, означающий, что диалог будет уничтожен вместе с родительским окном.

  • `GTK_DIALOG_MODAL`: Флаг для GtkDialog, делающий его модальным, то есть блокирующим взаимодействие с родительским окном до своего закрытия.

  • `GTK_MESSAGE_INFO`, `GTK_MESSAGE_WARNING`, `GTK_MESSAGE_ERROR`, `GTK_MESSAGE_QUESTION`: Типы сообщений для GtkMessageDialog, влияющие на его иконку.

  • `GTK_BUTTONS_OK`, `GTK_BUTTONS_OK_CANCEL`, `GTK_BUTTONS_YES_NO`, `GTK_BUTTONS_NONE`: Предустановленные наборы кнопок для GtkMessageDialog.

  • `GTK_RESPONSE_OK`, `GTK_RESPONSE_CANCEL`, `GTK_RESPONSE_YES`, `GTK_RESPONSE_NO`: Возвращаемые значения от gtk_dialog_run(), указывающие, какая кнопка была нажата.

  • `gtk_label_new()`: Создаёт новый виджет метки (GtkLabel) с заданным текстом.

  • `gtk_button_new_with_label()`: Создаёт новую кнопку (GtkButton) с текстовой меткой.

10.1.

### Пример 22.1: Использование GtkMessageDialog

Файл: message_dialog_example.c

Этот пример демонстрирует, как создать и отобразить стандартное информационное сообщение с помощью GtkMessageDialog. Приложение состоит из основного окна с кнопкой. Нажатие этой кнопки вызывает диалоговое окно.

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

/**
 * @brief Callback-функция, вызываемая при нажатии кнопки в главном окне.
 * Отвечает за создание и отображение GtkMessageDialog.
 *
 * @param widget Указатель на кнопку, которая была нажата.
 * @param window Указатель на главное окно (родительское окно для диалога),
 * переданный через параметр `data` при подключении сигнала.
 */
static void on_button_clicked(GtkWidget *widget, gpointer window) {
    GtkWidget *dialog; // Декларация указателя на диалоговое окно.

    // Создаем новый GtkMessageDialog.
    // Параметры:
    // 1. GTK_WINDOW(window): Родительское окно для диалога.
    //    Это обеспечивает правильное позиционирование диалога и его уничтожение при закрытии родителя.
    // 2. GTK_DIALOG_DESTROY_WITH_PARENT: Флаг поведения диалога.
    //    Означает, что диалог будет автоматически уничтожен, если его родительское окно будет закрыто.
    // 3. GTK_MESSAGE_INFO: Тип сообщения. Определяет иконку, которая будет отображаться в диалоге (информационная).
    //    Другие варианты: GTK_MESSAGE_WARNING, GTK_MESSAGE_ERROR, GTK_MESSAGE_QUESTION.
    // 4. GTK_BUTTONS_OK: Набор кнопок, которые будут присутствовать в диалоге.
    //    Здесь будет только кнопка "OK". Другие: GTK_BUTTONS_OK_CANCEL, GTK_BUTTONS_YES_NO.
    // 5. "Это информационное сообщение.": Формат строки сообщения. Работает как printf.
    dialog = gtk_message_dialog_new(GTK_WINDOW(window),
                                    GTK_DIALOG_DESTROY_WITH_PARENT,
                                    GTK_MESSAGE_INFO,
                                    GTK_BUTTONS_OK,
                                    "Это информационное сообщение.");

    // Запускаем диалог. Эта функция отображает диалог и блокирует
    // основное окно до тех пор, пока пользователь не закроет диалог (нажав кнопку).
    // Она возвращает значение GTK_RESPONSE_..., соответствующее нажатой кнопке.
    // В данном случае, поскольку есть только кнопка "OK", она вернет GTK_RESPONSE_OK.
    gtk_dialog_run(GTK_DIALOG(dialog));

    // Уничтожаем диалог после того, как он был закрыт.
    // Это освобождает выделенные для него ресурсы.
    gtk_widget_destroy(dialog);
}

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

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

    // 1. Создание главного окна.
    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title(GTK_WINDOW(window), "Пример MessageDialog");
    gtk_window_set_default_size(GTK_WINDOW(window), 300, 150);
    gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);

    // Подключаем сигнал "destroy" окна к функции gtk_main_quit.
    g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);

    // 2. Создание кнопки.
    button = gtk_button_new_with_label("Показать сообщение");
    // Подключаем сигнал "clicked" кнопки к нашей функции on_button_clicked.
    // В качестве пользовательских данных (data) передаем указатель на главное окно,
    // чтобы функция on_button_clicked могла использовать его как родителя для диалога.
    g_signal_connect(button, "clicked", G_CALLBACK(on_button_clicked), window);

    // 3. Добавление кнопки в окно.
    gtk_container_add(GTK_CONTAINER(window), button);

    // 4. Отображение всех виджетов и запуск главного цикла GTK.
    gtk_widget_show_all(window);
    gtk_main();

    return 0;
}

### Пример 22.2: Создание Кастомного GtkDialog

Файл: custom_dialog_example.c

В этом примере мы создаем более гибкий, пользовательский диалог с помощью GtkDialog. Мы добавляем в него метку с вопросом и две кнопки («OK» и «Отмена»). Программа отслеживает, какая из кнопок была нажата пользователем, и выводит соответствующее сообщение в консоль.

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

/**
 * @brief Callback-функция, вызываемая при нажатии кнопки в главном окне.
 * Отвечает за создание, настройку и отображение кастомного GtkDialog.
 *
 * @param widget Указатель на кнопку, которая была нажата.
 * @param window Указатель на главное окно (родительское окно для диалога).
 */
static void on_button_clicked(GtkWidget *widget, gpointer window) {
    GtkWidget *dialog;       // Указатель на диалоговое окно.
    gint result;             // Переменная для хранения результата (какая кнопка была нажата).
    GtkWidget *content_area; // Область содержимого диалога, куда мы добавим нашу метку.
    GtkWidget *label;        // Метка для отображения текста в диалоге.

    // Создаем новый GtkDialog с предопределенными кнопками.
    // Параметры:
    // 1. "Кастомный диалог": Заголовок диалогового окна.
    // 2. GTK_WINDOW(window): Родительское окно.
    // 3. GTK_DIALOG_MODAL: Флаг, делающий диалог модальным. Это означает, что
    //    пользователь не сможет взаимодействовать с родительским окном,
    //    пока этот диалог открыт.
    // 4. "_OK": Текст первой кнопки. Подчеркивание `_` используется для создания мнемонического доступа
    //    (например, Alt+O, если это поддерживается системой).
    // 5. GTK_RESPONSE_OK: Значение, которое `gtk_dialog_run()` вернет, если будет нажата эта кнопка.
    // 6. "_Отмена": Текст второй кнопки.
    // 7. GTK_RESPONSE_CANCEL: Значение, возвращаемое при нажатии этой кнопки.
    // 8. NULL: Обозначает конец списка кнопок.
    dialog = gtk_dialog_new_with_buttons("Кастомный диалог",
                                         GTK_WINDOW(window),
                                         GTK_DIALOG_MODAL,
                                         "_OK",
                                         GTK_RESPONSE_OK,
                                         "_Отмена",
                                         GTK_RESPONSE_CANCEL,
                                         NULL);

    // Получаем область содержимого диалога.
    // Это GtkBox, куда можно добавлять любые виджеты, формируя пользовательский интерфейс диалога.
    content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));

    // Создаем метку с вопросом.
    label = gtk_label_new("Вы хотите продолжить?");
    // Добавляем метку в область содержимого диалога.
    gtk_container_add(GTK_CONTAINER(content_area), label);

    // Отображаем все виджеты в диалоге (метку и кнопки).
    gtk_widget_show_all(dialog);

    // Запускаем диалог и получаем ответ пользователя.
    // Эта функция блокирует выполнение программы до закрытия диалога.
    result = gtk_dialog_run(GTK_DIALOG(dialog));

    // Анализируем результат, чтобы определить, какая кнопка была нажата.
    if (result == GTK_RESPONSE_OK) {
        g_print("Пользователь нажал OK\n");
    } else if (result == GTK_RESPONSE_CANCEL) {
        g_print("Пользователь нажал Отмена\n");
    } else {
        // Может быть GTK_RESPONSE_NONE, если диалог был закрыт другим способом (например, кнопкой закрытия окна).
        g_print("Диалог был закрыт без явного выбора OK/Отмена\n");
    }

    // Уничтожаем диалог после его использования.
    // Это освобождает ресурсы, которые он занимал.
    gtk_widget_destroy(dialog);
}

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

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

    // 1. Создание главного окна.
    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title(GTK_WINDOW(window), "Пример Кастомного Диалога");
    gtk_window_set_default_size(GTK_WINDOW(window), 350, 150);
    gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);

    // Подключаем сигнал "destroy" окна.
    g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);

    // 2. Создание кнопки.
    button = gtk_button_new_with_label("Открыть диалог");
    // Подключаем сигнал "clicked" кнопки к нашей функции on_button_clicked,
    // передавая главное окно в качестве пользовательских данных.
    g_signal_connect(button, "clicked", G_CALLBACK(on_button_clicked), window);

    // 3. Добавление кнопки в окно.
    gtk_container_add(GTK_CONTAINER(window), button);

    // 4. Отображение всех виджетов и запуск главного цикла GTK.
    gtk_widget_show_all(window);
    gtk_main();

    return 0;
}

10.2. Компиляция и Запуск Примеров

Сохраните каждый пример кода в соответствующий файл: message_dialog_example.c и custom_dialog_example.c.

### Компиляция:

Для сборки программ используйте следующие команды в терминале, находясь в директории с исходными файлами. Убедитесь, что у вас установлен GTK 3.

# Для примера с GtkMessageDialog:
gcc message_dialog_example.c -o message_dialog_example `pkg-config --cflags --libs gtk+-3.0`

# Для примера с Кастомным GtkDialog:
gcc custom_dialog_example.c -o custom_dialog_example `pkg-config --cflags --libs gtk+-3.0`

### Запуск:

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

# Для примера с GtkMessageDialog:
./message_dialog_example

# Для примера с Кастомным GtkDialog:
./custom_dialog_example

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

  • `message_dialog_example`:
    • Откроется основное окно с заголовком «Пример MessageDialog» и одной кнопкой «Показать сообщение».

    • При нажатии на эту кнопку появится всплывающее диалоговое окно с заголовком, иконкой информации, текстом «Это информационное сообщение.» и одной кнопкой «OK».

    • После нажатия «OK» диалог закроется, и управление вернется к основному окну.

  • `custom_dialog_example`:
    • Откроется основное окно с заголовком «Пример Кастомного Диалога» и кнопкой «Открыть диалог».

    • При нажатии на эту кнопку появится модальное диалоговое окно с заголовком «Кастомный диалог».

    • Внутри диалога будет отображаться текст «Вы хотите продолжить?» и две кнопки: «OK» и «Отмена».

    • Если вы нажмете «OK», диалог закроется, и в консоли появится сообщение «Пользователь нажал OK».

    • Если вы нажмете «Отмена», диалог закроется, и в консоли появится сообщение «Пользователь нажал Отмена».

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