2. Глава 28: Применение CSS-стилей в GTK-приложениях с Glade
В этой главе мы собрали и отладили все компоненты для создания GTK-приложения, которое использует Glade для определения пользовательского интерфейса и CSS для его стилизации. Мы решили общие проблемы, связанные с загрузкой CSS, приоритетом стилей и сопоставлением ID виджетов.
В итоге, ваше приложение теперь демонстрирует настраиваемые стили для всех элементов интерфейса.
2.1. Файлы проекта:
2.1.1. style.css
Этот файл содержит все CSS-правила, которые определяют внешний вид вашего приложения.
window {
background-color: #f5f5f5; /* Общий светло-серый фон для всех окон. */
}
#main_window {
background-color: #eaf5ea; /* Светло-зеленый фон для главного окна. */
border-radius: 8px; /* Закруглённые углы окна. */
padding: 10px; /* Внутренние отступы вокруг содержимого окна. */
}
#entry_input {
background-color: #ffffff; /* Белый фон для поля ввода. */
border: 1px solid #c0c0c0; /* Тонкая серая рамка. */
padding: 6px; /* Внутренние отступы текста в поле ввода. */
border-radius: 6px; /* Закруглённые углы поля ввода. */
color: #333; /* Тёмно-серый цвет текста. */
}
#entry_input:focus {
border-color: #87c087; /* Более тёмная зелёная рамка при фокусе. */
}
#my_button {
background: #87c087; /* Ярко-зелёный фон кнопки. */
color: white; /* Белый текст кнопки. */
padding: 6px 12px; /* Внутренние отступы текста кнопки. */
border-radius: 6px; /* Закруглённые углы кнопки. */
font-weight: bold; /* Жирный шрифт текста кнопки. */
}
#my_button:hover {
background: #76b176; /* Более тёмный зелёный фон при наведении. */
}
#label_output {
color: #444; /* Тёмно-серый цвет текста метки. */
font-size: 13px; /* Размер шрифта метки. */
padding-top: 6px; /* Отступ сверху для метки. */
}
Комментарии к `style.css`:
Назначение: Этот файл содержит все визуальные правила.
Селекторы: Используются селекторы по типу виджета (window) и по ID (#main_window, #entry_input, #my_button, #label_output).
Псевдоклассы: Применяются псевдоклассы :focus для поля ввода и :hover, :active для кнопки для интерактивных состояний.
Проблема Specificity (`!important`): В процессе отладки мы обнаружили, что системные темы могут перебивать обычные CSS-правила. В конечной версии мы решили проблему либо путём программной установки CSS-имён (что повышает надёжность), либо путём убедительности CSS-селекторов и отсутствием синтаксических ошибок. Если бы проблема не решилась, пришлось бы прибегнуть к !important, но это обычно считается «костылём» и его избегают, если есть другие решения. В данном финальном коде !important отсутствует, так как другие методы доказали свою эффективность.
2.1.2. glade_example.c
Этот C-код является сердцем нашего приложения, который инициализирует GTK, загружает UI из Glade-файла, применяет CSS-стили и подключает логику работы.
#include <gtk/gtk.h> // Основной заголовочный файл GTK.
#include <libgen.h> // Для функции dirname(), которая извлекает путь к директории из полного пути.
#include <unistd.h> // Для функции readlink(), используемой для получения пути к исполняемому файлу.
#include <limits.h> // Для PATH_MAX, константы, определяющей максимальную длину пути.
// Структура для хранения указателей на важные виджеты.
// Это позволяет легко передавать их между функциями.
typedef struct {
GtkWidget *entry_input; // Поле ввода текста.
GtkWidget *label_output; // Метка для вывода скопированного текста.
GtkWidget *button; // Кнопка для запуска копирования.
} AppData;
// Функция для загрузки CSS-файла.
// Она динамически определяет путь к style.css, который должен находиться в той же директории, что и исполняемый файл.
static void load_css(void) {
// Создаем новый провайдер CSS.
GtkCssProvider *provider = gtk_css_provider_new();
// Получаем дисплей по умолчанию, необходимый для применения CSS к экрану.
GdkDisplay *display = gdk_display_get_default();
// Получаем экран по умолчанию.
GdkScreen *screen = gdk_display_get_default_screen(display);
char exe_path[PATH_MAX], css_path[PATH_MAX];
// Получаем полный путь к исполняемому файлу.
ssize_t len = readlink("/proc/self/exe", exe_path, sizeof(exe_path) - 1);
if (len != -1) {
exe_path[len] = '\0'; // Убеждаемся, что строка нуль-терминирована.
char *dir = dirname(exe_path); // Извлекаем директорию исполняемого файла.
// Формируем полный путь к CSS-файлу.
snprintf(css_path, sizeof(css_path), "%s/style.css", dir);
// Загружаем CSS из файла.
if (gtk_css_provider_load_from_path(provider, css_path, NULL))
g_print("CSS успешно загружен: %s\\n", css_path); // Сообщение об успешной загрузке.
else
g_warning("Ошибка загрузки CSS: %s", css_path); // Предупреждение об ошибке.
// Применяем CSS-провайдер ко всему экрану с высоким приоритетом (приложение переопределяет системные стили).
gtk_style_context_add_provider_for_screen(
screen,
GTK_STYLE_PROVIDER(provider),
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION
);
} else {
g_warning("Не удалось определить путь к исполняемому файлу.");
}
// Освобождаем ресурс провайдера CSS.
g_object_unref(provider);
}
// Обработчик нажатия кнопки.
// Копирует текст из поля ввода в метку.
void on_btn_copy_clicked(GtkButton *button, gpointer user_data) {
AppData *app = (AppData *)user_data; // Приводим gpointer к нашему типу AppData.
const gchar *text = gtk_entry_get_text(GTK_ENTRY(app->entry_input)); // Получаем текст из поля ввода.
gtk_label_set_text(GTK_LABEL(app->label_output), text); // Устанавливаем текст в метку.
}
// Главная функция программы.
int main(int argc, char *argv[]) {
gtk_init(&argc, &argv); // Инициализация GTK.
load_css(); // Загрузка CSS-стилей.
// Создание GtkBuilder и загрузка UI из Glade-файла.
GtkBuilder *builder = gtk_builder_new_from_file("interface.glade");
if (!builder) {
g_critical("Не удалось загрузить Glade-файл."); // Критическая ошибка, если файл не найден/поврежден.
return 1;
}
// Получение ссылок на виджеты по их ID из Glade-файла.
GtkWidget *window = GTK_WIDGET(gtk_builder_get_object(builder, "main_window"));
GtkWidget *entry = GTK_WIDGET(gtk_builder_get_object(builder, "entry_input"));
GtkWidget *label = GTK_WIDGET(gtk_builder_get_object(builder, "label_output"));
GtkWidget *button = GTK_WIDGET(gtk_builder_get_object(builder, "my_button"));
// Проверка, что все необходимые виджеты были найдены.
if (!window || !entry || !label || !button) {
g_critical("Не удалось получить виджеты.");
g_object_unref(builder);
return 1;
}
// !!! ВАЖНОЕ ДОБАВЛЕНИЕ ДЛЯ РЕШЕНИЯ ПРОБЛЕМ СО СТИЛЯМИ !!!
// Программное назначение CSS-имён (ID) виджетам.
// Это гарантирует, что CSS-селекторы по ID (#) всегда найдут эти виджеты,
// даже если Glade-файл каким-то образом не передал ID корректно при загрузке.
gtk_widget_set_name(window, "main_window");
gtk_widget_set_name(entry, "entry_input");
gtk_widget_set_name(label, "label_output");
gtk_widget_set_name(button, "my_button");
// Инициализация структуры AppData для передачи в обработчик сигнала.
AppData app = { entry, label, button };
// Подключение сигнала "clicked" кнопки к обработчику on_btn_copy_clicked.
g_signal_connect(button, "clicked", G_CALLBACK(on_btn_copy_clicked), &app);
// Подключение сигнала "destroy" главного окна для выхода из приложения при его закрытии.
g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
// Отображение всех виджетов в окне.
gtk_widget_show_all(window);
// Запуск главного цикла GTK. Приложение ожидает событий.
gtk_main();
// Освобождение ресурсов GtkBuilder при завершении работы приложения.
g_object_unref(builder);
return 0; // Успешное завершение.
}
Комментарии к `glade_example.c`:
`load_css()`: Функция теперь надёжно определяет путь к исполняемому файлу, чтобы найти style.css, что делает приложение более портативным. Применение GTK_STYLE_PROVIDER_PRIORITY_APPLICATION гарантирует, что ваши стили имеют высокий приоритет.
`AppData`: Структура AppData инкапсулирует указатели на виджеты, что упрощает передачу их в обработчики сигналов через user_data.
`gtk_widget_set_name()` (КЛЮЧЕВОЕ ИЗМЕНЕНИЕ): Это было самое важное добавление. Мы обнаружили, что иногда ID, заданные в Glade, могли быть не распознаны GTK-движком для применения CSS. Программная установка ID с помощью gtk_widget_set_name() гарантирует, что виджеты будут иметь правильные CSS ID, к которым могут быть применены стили из style.css. Это надёжный способ устранить неочевидные проблемы с CSS-применением.
Обработчик сигналов: Обработчик on_btn_copy_clicked и подключение сигнала clicked для кнопки my_button возвращены, обеспечивая функциональность копирования текста.
2.1.3. interface.glade
Этот XML-файл, созданный с помощью Glade, определяет структуру и первоначальные свойства пользовательского интерфейса. Он является основой для создания виджетов, которые затем будут стилизованы с помощью CSS.
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk+" version="3.24"/> <object class="GtkWindow" id="main_window"> <property name="default-width">400</property> <property name="default-height">200</property> <property name="title">Mint Style Demo</property> <child>
<object class="GtkBox" id="main_vbox"> <property name="orientation">vertical</property> <property name="spacing">12</property> <property name="margin-start">16</property> <property name="margin-end">16</property>
<property name="margin-top">16</property>
<property name="margin-bottom">16</property>
<child>
<object class="GtkEntry" id="entry_input"> <property name="placeholder-text">Введите текст...</property> </object>
</child>
<child>
<object class="GtkButton" id="my_button"> <property name="label">Скопировать</property> <signal name="clicked" handler="on_btn_copy_clicked" swapped="no"/> </object>
</child>
<child>
<object class="GtkLabel" id="label_output"> <property name="label">Здесь появится текст</property> <property name="wrap">True</property> </object>
</child>
</object>
</child>
</object>
</interface>
Комментарии к `interface.glade`:
Структура: Определяет главное окно (GtkWindow), вертикальный контейнер (GtkBox) и три основных виджета: поле ввода (GtkEntry), кнопка (GtkButton) и метка (GtkLabel).
ID виджетов: Все ключевые виджеты имеют уникальные id (main_window, entry_input, my_button, label_output), которые используются в C-коде для получения ссылок на виджеты и в CSS для применения стилей.
Свойства: Настроены основные свойства, такие как размеры окна, отступы, текст заполнителя и текст меток.
Сигналы: Кнопка my_button имеет сигнал clicked, связанный с функцией on_btn_copy_clicked в C-коде, обеспечивая интерактивность.
2.2. Инструкции по сборке и запуску:
Сохраните все три файла (style.css, glade_example.c, interface.glade) в одной директории (например, ~/myprg/28/).
Откройте терминал и перейдите в эту директорию:
`bash cd ~/myprg/28/ `
Скомпилируйте C-код с использованием pkg-config для автоматического подключения необходимых флагов GTK:
`bash gcc glade_example.c -o glade_example -rdynamic `pkg-config --cflags --libs gtk+-3.0` `
Запустите скомпилированное приложение:
`bash ./glade_example `
После запуска вы должны увидеть окно GTK с полностью применёнными стилями из style.css, а кнопка и поле ввода будут функциональны.
—