8. Глава 20: Деревья и Таблицы (GtkTreeView)

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

Использование GtkTreeView требует понимания следующих ключевых компонентов:

  • GtkTreeView: Сам виджет, который отображает данные.

  • GtkListStore: Модель данных для табличной структуры (списка строк). Каждая строка в GtkListStore не имеет родителей или дочерних элементов.

  • GtkTreeStore: Модель данных для иерархической (древовидной) структуры. Каждая строка может иметь дочерние элементы, формируя дерево.

  • GtkTreeViewColumn: Определяет отдельный столбец в GtkTreeView, включая его заголовок и способ отображения данных.

  • GtkCellRenderer: Абстрактный базовый класс для объектов, которые отвечают за отрисовку (рендеринг) содержимого отдельной ячейки в GtkTreeView. Например, GtkCellRendererText отрисовывает текст, GtkCellRendererPixbuf — изображения.

### Пример 20.1: Табличный GtkTreeView с GtkListStore

Название исходного файла: treeview_liststore_example.c

В этом примере мы создадим простую таблицу, отображающую данные о пользователях (Имя, Возраст, Профессия). Для хранения этих данных мы будем использовать GtkListStore, поскольку данные здесь не имеют иерархической структуры.

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

// Перечисление для удобства определения индексов столбцов в GtkListStore.
// Это делает код более читабельным и менее подверженным ошибкам.
enum {
    COL_NAME,       // Столбец для имени (G_TYPE_STRING)
    COL_AGE,        // Столбец для возраста (G_TYPE_INT)
    COL_OCCUPATION, // Столбец для профессии (G_TYPE_STRING)
    NUM_COLS        // Общее количество столбцов
};

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

    // 1. Создание главного окна.
    GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title(GTK_WINDOW(window), "Пример GtkTreeView (ListStore)");
    gtk_window_set_default_size(GTK_WINDOW(window), 400, 300);
    gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
    g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);

    // 2. Создание GtkListStore (модели данных).
    // gtk_list_store_new() создает новую модель данных.
    // Первый аргумент: NUM_COLS - общее количество столбцов.
    // Остальные аргументы: типы данных для каждого столбца.
    GtkListStore *store = gtk_list_store_new(NUM_COLS, G_TYPE_STRING, G_TYPE_INT, G_TYPE_STRING);
    GtkTreeIter iter; // Итератор для указания на текущую строку.

    // 3. Добавление данных в GtkListStore.
    // Каждая пара gtk_list_store_append() и gtk_list_store_set() добавляет одну строку.

    // Первая строка: Алиса, 30, Инженер
    gtk_list_store_append(store, &iter); // Добавляем новую строку и получаем итератор.
    gtk_list_store_set(store, &iter,
                       COL_NAME, "Алиса",      // Устанавливаем имя
                       COL_AGE, 30,           // Устанавливаем возраст
                       COL_OCCUPATION, "Инженер", // Устанавливаем профессию
                       -1);                   // -1 означает конец списка аргументов.

    // Вторая строка: Боб, 25, Дизайнер
    gtk_list_store_append(store, &iter);
    gtk_list_store_set(store, &iter,
                       COL_NAME, "Боб",
                       COL_AGE, 25,
                       COL_OCCUPATION, "Дизайнер",
                       -1);

    // Третья строка: Анна, 35, Врач
    gtk_list_store_append(store, &iter);
    gtk_list_store_set(store, &iter,
                       COL_NAME, "Анна",
                       COL_AGE, 35,
                       COL_OCCUPATION, "Врач",
                       -1);

    // Четвертая строка: Чарли, 28, Программист
    gtk_list_store_append(store, &iter);
    gtk_list_store_set(store, &iter,
                       COL_NAME, "Чарли",
                       COL_AGE, 28,
                       COL_OCCUPATION, "Программист",
                       -1);


    // 4. Создание GtkTreeView с созданной моделью данных.
    // GtkTreeView - это виджет-представление, которое отображает данные из GtkTreeModel.
    GtkWidget *view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));

    // Отключаем автоматический инкремент счетчика ссылок на модель.
    // GtkTreeView сам увеличивает счетчик ссылок при связывании,
    // поэтому мы можем безопасно уменьшить наш.
    g_object_unref(store);

    // 5. Добавление столбцов (колонок) в GtkTreeView.
    // Для каждого столбца в таблице мы создаем GtkTreeViewColumn.
    // GtkTreeViewColumn связывает столбец модели с рендерером, который отображает данные.
    GtkCellRenderer *renderer;
    GtkTreeViewColumn *column;

    // --- Колонка "Имя" ---
    // Создаем рендерер для отображения текста.
    renderer = gtk_cell_renderer_text_new();
    // Создаем столбец с заголовком "Имя".
    // "text", COL_NAME: связываем свойство "text" рендерера с данными из столбца COL_NAME модели.
    column = gtk_tree_view_column_new_with_attributes("Имя", renderer, "text", COL_NAME, NULL);
    // Добавляем столбец в GtkTreeView.
    gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
    // Включение сортировки по нажатию на заголовки колонок.
    gtk_tree_view_column_set_sort_column_id(column, COL_NAME);


    // --- Колонка "Возраст" ---
    renderer = gtk_cell_renderer_text_new();
    column = gtk_tree_view_column_new_with_attributes("Возраст", renderer, "text", COL_AGE, NULL);
    gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
    gtk_tree_view_column_set_sort_column_id(column, COL_AGE); // Включение сортировки.


    // --- Колонка "Профессия" ---
    renderer = gtk_cell_renderer_text_new();
    column = gtk_tree_view_column_new_with_attributes("Профессия", renderer, "text", COL_OCCUPATION, NULL);
    gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
    gtk_tree_view_column_set_sort_column_id(column, COL_OCCUPATION); // Включение сортировки.

    // 6. Размещение GtkTreeView в окне.
    // Чтобы GtkTreeView можно было прокручивать, его обычно помещают в GtkScrolledWindow.
    GtkWidget *scrolled_window = gtk_scrolled_window_new(NULL, NULL);
    gtk_container_add(GTK_CONTAINER(scrolled_window), view);
    // Устанавливаем политику прокрутки (всегда показывать полосы прокрутки)
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);

    // Добавляем GtkScrolledWindow в главное окно.
    gtk_container_add(GTK_CONTAINER(window), scrolled_window);

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

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

    return 0;
}

### Пример 20.2: Древовидный GtkTreeView с GtkTreeStore

Название исходного файла: treeview_treestore_example.c

В этом примере мы создадим иерархический список, например, дерево категорий и подкатегорий. Для этого используется GtkTreeStore, которая, в отличие от GtkListStore, может хранить дочерние элементы для каждой строки.

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

// Перечисление для удобства определения индексов столбцов в GtkTreeStore.
enum {
    COL_DISPLAY_TEXT, // Столбец для отображаемого текста
    NUM_COLS_TREE     // Общее количество столбцов в этой модели
};

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

    // 1. Создание главного окна.
    GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title(GTK_WINDOW(window), "Пример GtkTreeView (TreeStore)");
    gtk_window_set_default_size(GTK_WINDOW(window), 400, 300);
    gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
    g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);

    // 2. Создание GtkTreeStore (модели данных для дерева).
    // gtk_tree_store_new() создает иерархическую модель.
    // Первый аргумент: NUM_COLS_TREE - количество столбцов.
    // Остальные аргументы: типы данных для каждого столбца.
    GtkTreeStore *treestore = gtk_tree_store_new(NUM_COLS_TREE, G_TYPE_STRING);
    GtkTreeIter parent_iter, child_iter; // Итераторы для родительских и дочерних узлов.

    // 3. Добавление данных в GtkTreeStore.
    // Данные добавляются иерархически.

    // --- Родительская категория: "Животные" ---
    // gtk_tree_store_append(treestore, &parent_iter, NULL): добавляет новый корневой узел
    // (NULL в качестве родителя) и устанавливает parent_iter на него.
    gtk_tree_store_append(treestore, &parent_iter, NULL);
    // Устанавливаем текст для корневого узла.
    gtk_tree_store_set(treestore, &parent_iter, COL_DISPLAY_TEXT, "Животные", -1);

    // Дочерние элементы для "Животные":
    // gtk_tree_store_append(treestore, &child_iter, &parent_iter): добавляет новый дочерний узел
    // к узлу, на который указывает parent_iter, и устанавливает child_iter на него.
    gtk_tree_store_append(treestore, &child_iter, &parent_iter);
    gtk_tree_store_set(treestore, &child_iter, COL_DISPLAY_TEXT, "Собака", -1);

    gtk_tree_store_append(treestore, &child_iter, &parent_iter);
    gtk_tree_store_set(treestore, &child_iter, COL_DISPLAY_TEXT, "Кот", -1);

    gtk_tree_store_append(treestore, &child_iter, &parent_iter);
    gtk_tree_store_set(treestore, &child_iter, COL_DISPLAY_TEXT, "Птица", -1);

    // --- Еще одна родительская категория: "Растения" ---
    gtk_tree_store_append(treestore, &parent_iter, NULL);
    gtk_tree_store_set(treestore, &parent_iter, COL_DISPLAY_TEXT, "Растения", -1);

    // Дочерние элементы для "Растения":
    gtk_tree_store_append(treestore, &child_iter, &parent_iter);
    gtk_tree_store_set(treestore, &child_iter, COL_DISPLAY_TEXT, "Цветы", -1);

    // Подкатегория для "Цветы"
    GtkTreeIter sub_child_iter;
    gtk_tree_store_append(treestore, &sub_child_iter, &child_iter); // parent_iter теперь - "Цветы"
    gtk_tree_store_set(treestore, &sub_child_iter, COL_DISPLAY_TEXT, "Розы", -1);

    gtk_tree_store_append(treestore, &sub_child_iter, &child_iter);
    gtk_tree_store_set(treestore, &sub_child_iter, COL_DISPLAY_TEXT, "Тюльпаны", -1);

    gtk_tree_store_append(treestore, &child_iter, &parent_iter);
    gtk_tree_store_set(treestore, &child_iter, COL_DISPLAY_TEXT, "Деревья", -1);

    // 4. Создание GtkTreeView с созданной моделью GtkTreeStore.
    GtkWidget *tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(treestore));
    g_object_unref(treestore); // Уменьшаем счетчик ссылок на модель.

    // 5. Добавление колонки в GtkTreeView.
    // Для дерева обычно требуется только одна колонка для отображения иерархии.
    GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
    // Заголовок столбца "Категории".
    // "text", COL_DISPLAY_TEXT: связываем свойство "text" рендерера с данными из столбца COL_DISPLAY_TEXT.
    GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes("Категории", renderer, "text", COL_DISPLAY_TEXT, NULL);
    gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);

    // Дополнительные настройки для GtkTreeView:
    // Разрешить сворачивание/разворачивание узлов.
    gtk_tree_view_set_expander_column(GTK_TREE_VIEW(tree), column);
    // Разрешить пользователю изменять размер колонок.
    gtk_tree_view_column_set_resizable(column, TRUE);
    // Автоматически развернуть все корневые узлы при запуске.
    // gtk_tree_view_expand_all(GTK_TREE_VIEW(tree));

    // 6. Размещение GtkTreeView в окне (через GtkScrolledWindow для прокрутки).
    GtkWidget *scrolled_window = gtk_scrolled_window_new(NULL, NULL);
    gtk_container_add(GTK_CONTAINER(scrolled_window), tree);
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);

    gtk_container_add(GTK_CONTAINER(window), scrolled_window);

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

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

    return 0;
}

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

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

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

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

# Для примера с GtkListStore (таблица):
gcc treeview_liststore_example.c -o treeview_liststore_example `pkg-config --cflags --libs gtk+-3.0`

# Для примера с GtkTreeStore (дерево):
gcc treeview_treestore_example.c -o treeview_treestore_example `pkg-config --cflags --libs gtk+-3.0`

### Запуск:

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

# Для примера с GtkListStore:
./treeview_liststore_example

# Для примера с GtkTreeStore:
./treeview_treestore_example

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

  • `treeview_liststore_example`:
    • Откроется окно с заголовком «Пример GtkTreeView (ListStore)».

    • Внутри окна вы увидите таблицу с тремя столбцами: «Имя», «Возраст», «Профессия».

    • Таблица будет содержать четыре строки данных, которые мы добавили в GtkListStore (Алиса, Боб, Анна, Чарли).

    • Вы сможете изменять размер колонок и сортировать данные, кликая по заголовкам.

  • `treeview_treestore_example`:
    • Откроется окно с заголовком «Пример GtkTreeView (TreeStore)».

    • Внутри окна вы увидите древовидную структуру (иерархический список).

    • Будут корневые узлы «Животные» и «Растения».

    • Вы сможете раскрывать «Животные», чтобы увидеть «Собака», «Кот», «Птица».

    • Вы сможете раскрывать «Растения», а затем «Цветы», чтобы увидеть «Розы» и «Тюльпаны».

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