9. Глава 21: Меню Приложений (GtkMenuBar, GtkMenu, GtkMenuItem)

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

Мы рассмотрим иерархию и взаимодействие следующих ключевых компонентов меню:

  • GtkMenuBar: Горизонтальная панель меню, которая обычно располагается в верхней части окна. Она содержит основные пункты меню (например, «Файл», «Правка», «Вид»).

  • GtkMenuItem: Отдельный элемент меню. Это может быть как корневой пункт в GtkMenuBar (например, «Файл»), так и пункт внутри выпадающего подменю (например, «Открыть», «Сохранить»). GtkMenuItem может также содержать подменю.

  • GtkMenu: Выпадающее подменю, которое появляется при активации GtkMenuItem. Оно содержит набор других GtkMenuItem, которые выполняют конкретные действия.

  • Обработка сигналов активации пунктов меню: Как подключить функции-обработчики к пунктам меню, чтобы они выполняли желаемые действия при клике пользователя.

### Пример 21.1: Простейшее Меню («Файл» -> «Выход»)

Файл: menu_basic_example.c

Этот пример демонстрирует создание минимального меню: окно с панелью меню, на которой расположен пункт «Файл». При нажатии на «Файл» появляется подменю с единственным пунктом «Выход». Выбор «Выход» завершает работу приложения.

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

/**
 * @brief Callback-функция, вызываемая при активации пункта меню "Выход".
 *
 * @param widget Указатель на виджет, который сгенерировал сигнал (в данном случае GtkMenuItem).
 * @param data Дополнительные данные, переданные при подключении сигнала (NULL в этом примере).
 */
static void on_menu_item_activate(GtkWidget *widget, gpointer data) {
    g_print("Выход из приложения\n"); // Печатаем сообщение в консоль.
    gtk_main_quit();                   // Завершаем главный цикл GTK, что закрывает приложение.
}

/**
 * @brief Главная функция программы.
 *
 * Инициализирует GTK, создает окно и добавляет к нему простейшее меню.
 *
 * @param argc Количество аргументов командной строки.
 * @param argv Массив строк аргументов командной строки.
 * @return Код завершения программы.
 */
int main(int argc, char *argv[]) {
    GtkWidget *window;    // Основное окно приложения.
    GtkWidget *vbox;      // Контейнер GtkBox для вертикального размещения виджетов.
    GtkWidget *menubar;   // Горизонтальная панель меню (GtkMenuBar).
    GtkWidget *file_menu; // Подменю "Файл" (GtkMenu).
    GtkWidget *file_item; // Пункт меню "Файл" (GtkMenuItem), который открывает file_menu.
    GtkWidget *exit_item; // Пункт меню "Выход" (GtkMenuItem).

    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), 300, 200); // Устанавливаем размер по умолчанию.
    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. Создание главного контейнера для окна.
    // GtkBox с вертикальной ориентацией и без промежутков между виджетами.
    vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
    gtk_container_add(GTK_CONTAINER(window), vbox); // Добавляем vbox в окно.

    // 3. Создание компонентов меню.
    menubar = gtk_menu_bar_new(); // Создаем пустую панель меню.
    file_menu = gtk_menu_new();   // Создаем пустое подменю для пункта "Файл".

    // Создаем пункты меню с текстовыми метками.
    file_item = gtk_menu_item_new_with_label("Файл");
    exit_item = gtk_menu_item_new_with_label("Выход");

    // 4. Сборка меню.
    // Добавляем 'exit_item' в 'file_menu'. GtkMenu является GtkMenuShell.
    gtk_menu_shell_append(GTK_MENU_SHELL(file_menu), exit_item);

    // Связываем 'file_menu' как подменю для 'file_item'.
    gtk_menu_item_set_submenu(GTK_MENU_ITEM(file_item), file_menu);

    // Добавляем 'file_item' (который теперь содержит подменю) в 'menubar'.
    // GtkMenuBar также является GtkMenuShell.
    gtk_menu_shell_append(GTK_MENU_SHELL(menubar), file_item);

    // 5. Размещение панели меню в окне.
    // Размещаем 'menubar' в верхней части 'vbox'.
    // FALSE, FALSE: не расширять и не заполнять доступное пространство.
    gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);

    // 6. Подключение сигнала к пункту меню "Выход".
    // При активации (клике) 'exit_item' будет вызвана функция 'on_menu_item_activate'.
    g_signal_connect(exit_item, "activate", G_CALLBACK(on_menu_item_activate), NULL);

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

    // 8. Запуск главного цикла событий GTK.
    gtk_main();

    return 0; // Возвращаем 0, если программа завершилась успешно.
}

### Пример 21.2: Меню с Несколькими Пунктами и Разделителем

Файл: menu_multiple_example.c

Этот пример расширяет предыдущий, добавляя несколько пунктов в подменю «Файл» («Открыть», «Сохранить», «Выход») и используя разделитель между ними для лучшей организации. Каждый пункт меню связан со своим обработчиком.

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

/**
 * @brief Callback-функция для пункта меню "Открыть".
 */
static void on_open(GtkWidget *widget, gpointer data) {
    g_print("Открытие файла...\n"); // Вывод сообщения в консоль.
}

/**
 * @brief Callback-функция для пункта меню "Сохранить".
 */
static void on_save(GtkWidget *widget, gpointer data) {
    g_print("Сохранение файла...\n"); // Вывод сообщения в консоль.
}

/**
 * @brief Callback-функция для пункта меню "Выход".
 * Завершает работу приложения.
 */
static void on_quit(GtkWidget *widget, gpointer data) {
    gtk_main_quit(); // Завершаем главный цикл GTK.
}

/**
 * @brief Главная функция программы.
 *
 * Инициализирует GTK, создает окно и добавляет к нему меню с несколькими пунктами.
 *
 * @param argc Количество аргументов командной строки.
 * @param argv Массив строк аргументов командной строки.
 * @return Код завершения программы.
 */
int main(int argc, char *argv[]) {
    GtkWidget *window;       // Основное окно.
    GtkWidget *vbox;         // Вертикальный контейнер.
    GtkWidget *menubar;      // Панель меню.
    GtkWidget *file_menu;    // Подменю "Файл".
    GtkWidget *file_item;    // Пункт меню "Файл".
    GtkWidget *open_item;    // Пункт меню "Открыть".
    GtkWidget *save_item;    // Пункт меню "Сохранить".
    GtkWidget *separator;    // Разделитель в меню.
    GtkWidget *exit_item;    // Пункт меню "Выход".

    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), 400, 200);
    gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
    g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);

    // 2. Создание вертикального контейнера и добавление его в окно.
    vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
    gtk_container_add(GTK_CONTAINER(window), vbox);

    // 3. Создание панели меню и подменю "Файл".
    menubar = gtk_menu_bar_new();
    file_menu = gtk_menu_new();

    // 4. Создание пунктов меню.
    file_item = gtk_menu_item_new_with_label("Файл");
    open_item = gtk_menu_item_new_with_label("Открыть");
    save_item = gtk_menu_item_new_with_label("Сохранить");
    separator = gtk_separator_menu_item_new(); // Создаем горизонтальный разделитель.
    exit_item = gtk_menu_item_new_with_label("Выход");

    // 5. Добавление пунктов в подменю "Файл".
    // Порядок добавления определяет порядок их отображения.
    gtk_menu_shell_append(GTK_MENU_SHELL(file_menu), open_item);
    gtk_menu_shell_append(GTK_MENU_SHELL(file_menu), save_item);
    gtk_menu_shell_append(GTK_MENU_SHELL(file_menu), separator); // Добавляем разделитель.
    gtk_menu_shell_append(GTK_MENU_SHELL(file_menu), exit_item);

    // 6. Связывание подменю "Файл" с пунктом "Файл" и добавление его в панель меню.
    gtk_menu_item_set_submenu(GTK_MENU_ITEM(file_item), file_menu);
    gtk_menu_shell_append(GTK_MENU_SHELL(menubar), file_item);

    // 7. Размещение панели меню в вертикальном контейнере.
    gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);

    // 8. Подключение callback-функций к пунктам меню.
    g_signal_connect(open_item, "activate", G_CALLBACK(on_open), NULL);
    g_signal_connect(save_item, "activate", G_CALLBACK(on_save), NULL);
    g_signal_connect(exit_item, "activate", G_CALLBACK(on_quit), NULL);

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

    return 0;
}

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

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

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

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

# Для Простейшего Меню:
gcc menu_basic_example.c -o menu_basic_example `pkg-config --cflags --libs gtk+-3.0`

# Для Меню с Несколькими Пунктами:
gcc menu_multiple_example.c -o menu_multiple_example `pkg-config --cflags --libs gtk+-3.0`

### Запуск:

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

# Для Простейшего Меню:
./menu_basic_example

# Для Меню с Несколькими Пунктами:
./menu_multiple_example

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

  • `menu_basic_example`:
    • Откроется небольшое окно с заголовком «Пример Простейшего Меню».

    • В верхней части окна будет видна панель меню с одним пунктом «Файл».

    • При клике на «Файл» откроется выпадающее подменю, содержащее только пункт «Выход».

    • Выбор пункта «Выход» приведёт к закрытию окна и завершению работы программы. В консоли, из которой вы запустили программу, появится сообщение «Выход из приложения».

  • `menu_multiple_example`:
    • Откроется окно с заголовком «Меню с Несколькими Пунктами».

    • В верхней части окна также будет панель меню с пунктом «Файл».

    • При клике на «Файл» откроется подменю, содержащее пункты «Открыть», «Сохранить», горизонтальный разделитель, и «Выход».

    • Выбор «Открыть» или «Сохранить» выведет соответствующее сообщение в консоль.

    • Выбор «Выход» закроет приложение, как и в предыдущем примере.

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