Денис Колисниченко
Здравствуйте, уважаемые читатели! В этой статье мы поговорим о создании
графического интерфейса для вашей Linux-программы. Как вы знаете, средствами
одного С нормальный GUI (Graphical User Interface - Графический интерфейс
пользователя, ГИП) не построишь, тем более что после Windows пользователь очень
требователен не только к наличию этого самого GUI, но еще и к дизайну формы
(окна программы). Поэтому без дополнительных библиотек вам не обойтись. Самыми
распространенными библиотеками для создания GUI являются библиотеки GTK и Qt.
Рекомендуется использовать только эти библиотеки, поскольку велика вероятность
того, что они уже будут установлены у пользователя (уж GNOME и KDE установлены
почти у всех), чтобы не сложилась такая ситуация, когда размер вашей программы
300К, а используемая нею библиотека "весит" 20М. Вот подумайте, зачем
пользователю ваша программа и станет ли он закачивать ее с Инета? Конечно, если
для вашего шедевра нет аналогов в мире, вы можете изрядно поиздеваться над
пользователем, используя нестандартную библиотеку GUI. В первой части этой
статьи будет рассмотрена библиотека GTK, а во второй - Qt. Сразу следует
заметить, что эта статья - не русскоязычное руководство по библиотеках GTK и Qt.
Это, скорее, небольшой обзор возможностей библиотек.
Скорее всего, GTK уже будет у вас установлена, но вам нужно будет установить
пакет gtk+-devel, содержащий необходимые файлы для разработки GTK-программ.
Не хочется в этой статье рассматривать банальный пример окошка с кнопкой hello
world!. Уж слишком уж это просто, да и этот пример вы сможете найти в
документации по Gtk.
Сейчас напишем небольшой конфигуратор, который будет вносить изменения в файл
/etc/resolv.conf. Напомню вам формат этого файла:
Создание GUI в Linux. Часть 1
domain firma.ru
nameserver 192.168.0.1
nameserver 192.168.0.2
Директива domain определяет наш домен, а две директивы nameserver - первый и второй DNS-серверы, соответственно. Вместо директивы domain можно использовать директиву search, но это кому как нравится. Файл может содержать до 4 директив nameserver, но обычно указываются только два сервера DNS, поэтому мы не будем перегружать себя лишней работой. Но файл resolv.conf не главное в нашей статье - ведь мы разрабатываем GUI. Наш конфигуратор не будет вносить изменения в настоящий файл /etc/resolv.conf - для этого нужны права root, можно, конечно, вызвать auth для аутентификации, но мы не будем этого делать, чтобы не усложнять код программы.
Теперь небольшое вступление в GTK. Элементы ГИП - кнопки, поля ввода, переключатели и тому подобное называется виджитами. Если вы когда-нибудь работали в Delphi, виджиты подобны визуальным компонентам Delphi.
Как и в Delphi, основным элементом GUI является окно (форма в Delphi). Виджиты для размещения в окне помещаются в контейнер. В самом окне выравнивать виджиты можно с помощью вертикальных/горизонтальных боксов или же таблиц. Мне больше нравится второй способ, поэтому мы будем использовать именно таблицы.
Виджиты могут реагировать на сигналы, например, щелчок мышью. При этом вызывается функция-обработчик события (сигнала), если вы определили ее.
В качестве примера нарисуем кнопку и определим обработчик ее нажатия:
/* Рисуем кнопочку с надписью Hello, All */ button = gtk_button_new_with_label ("Hello, All"); /* При нажатии кнопки будет вызвана функция hello() */ gtk_signal_connect (GTK_OBJECT (button), "clicked", GTK_SIGNAL_FUNC (hello), NULL); /* Помещаем кнопку в контейнер */ gtk_container_add (GTK_CONTAINER (window), button); /* Отображаем кнопку. Без этого вы ее просто не увидите. Кстати, не забудьте отобразить окно. Порядок отображения виджитов особой роли не играет, но рекомендуется окно отображать в последнюю очередь */ gtk_widget_show (button);
А вот функция hello():
/* Тип gpointer - это указатель. Тип gint - целое число, gchar - символ и т.д. */ void hello( GtkWidget *widget, gpointer data ) { g_print ("Hello, All\n"); }
Хватит теории, перейдем к практике. На рисунке 1 изображена уже готовая программа. Работает она так. Когда пользователь введет что-нибудь в поле ввода и нажмет Enter, программа отобразит введенный им текст на консоли. Когда пользователь нажмет Ок, введенная им информация будет еще раз выведена на консоль и записана в файл. При нажатии кнопки Quit программа завершит свою работу. Она должна также завершить работу при нажатии кнопки закрытия окна - в GTK программист сам определяет реакции на стандартные кнопки.
Рис. 1. Resolver
Вот текст программы. Внимательно читайте комментарии.
#include <gtk/gtk.h> #include <stdlib.h> #include <stdio.h> gchar *domain, *dns1, *dns2; /* Массив из трех полей ввода. Первое предназначено для ввода имени домена, два вторых - [1] и [2] - для ввода IP-адресов серверов DNS*/ GtkWidget *edit[3]; /* Наш файл */ FILE *resolv; /* Функция записи в файл */ void writetofile( GtkWidget *widget, gpointer data ) { /* С помощью функции gtk_entry_get_text() мы получаем введенный пользователем текст из полей ввода */ domain = gtk_entry_get_text(GTK_ENTRY(edit[0])); dns1 = gtk_entry_get_text(GTK_ENTRY(edit[1])); dns2 = gtk_entry_get_text(GTK_ENTRY(edit[2])); /* Выводим прочитанный текст на консоль */ g_print ("Domain %s\n", domain); g_print ("DNS1 %s\n", dns1); g_print ("DNS2 %s\n", dns2); /* Перезаписываем файл resolv.conf в текущем каталоге */ if ((resolv = fopen("resolv.conf","w")) == NULL) { /* Наверное, нет места на диске или прав маловато... */ g_print ("ERR: Cannot to open resolve.conf file\n"); gtk_main_quit (); } /* Запись в файл */ fprintf(resolv,"domain %s\n",domain); fprintf(resolv,"nameserver %s\n",dns1); fprintf(resolv,"nameserver %s\n",dns2); fclose(resolv); } /* Эта функция будет запущена, когда пользователь нажмет кнопку закрытия окна или кнопку Quit */ gint delete_event( GtkWidget *widget, GdkEvent *event, gpointer data ) { /* Функция gtk_main_quit() используется для завершения работы GTK-проргаммы. Не нужно для этого использовать exit() */ gtk_main_quit (); return(FALSE); } /* Когда пользователь введет текст и нажмет Enter, введенный им текст будет выведен на консоль */ void enter_callback( GtkWidget *widget, GtkWidget *entry ) { domain = gtk_entry_get_text(GTK_ENTRY(entry)); printf("Domain: %s\n", domain); } int main( int argc, char *argv[] ) { GtkWidget *window; /* Окно */ GtkWidget *button; /* Кнопка */ GtkWidget *table; /* Таблица для размещения виджитов */ GtkWidget *label; /* Надпись */ /* Как видите, все виджиты одного типа - GtkWidget, поэтому мы могли бы обойтись даже тремя виджитами - для окна, таблицы и для всех остальных элементов GUI*/ int i; /* Инициализация любой GTK-программы */ gtk_init (&argc, &argv); /* Создаем новое окно */ window = gtk_window_new (GTK_WINDOW_TOPLEVEL); /* Устанавливаем заголовок окна */ gtk_window_set_title (GTK_WINDOW (window), "Resolver"); /* Устанавливаем реакцию на кнопку закрытия окна. Сигнал - delete_event Вызываем функцию delete_event(), которая описана выше */ gtk_signal_connect (GTK_OBJECT (window), "delete_event", GTK_SIGNAL_FUNC (delete_event), NULL); /* Устанавливаем рамку окна */ gtk_container_set_border_width (GTK_CONTAINER (window), 20); /* Создаем таблицу 3x3 */ table = gtk_table_new (3, 3, TRUE); /* Помещаем таблицу в контейнер. Обязательно! */ gtk_container_add (GTK_CONTAINER (window), table); /* Рисуем надписи, помещаем их в ТАБЛИЦУ и отображаем. Обратите внимание, что в этом случае нам не нужно объявлять отдельную переменную для каждой надписи*/ label = gtk_label_new("Domain: "); /* О координатах ячеек поговорим после этого листинга */ gtk_table_attach_defaults (GTK_TABLE(table), label, 0, 1, 0, 1); gtk_widget_show (label); label = gtk_label_new("DNS #1: "); gtk_table_attach_defaults (GTK_TABLE(table), label, 0, 1, 1, 2); gtk_widget_show (label); label = gtk_label_new("DNS #2: "); gtk_table_attach_defaults (GTK_TABLE(table), label, 0, 1, 2, 3); gtk_widget_show (label); /* Заполняем наш массив полей ввода. По аналогии с Delphi, я назвал массив edit[]*/ for(i=0; i<3; i++) { /* Новое поле */ edit[i] = gtk_entry_new(); /* Если забыть этот оператор, пользователь ничего не сможет ввести */ gtk_entry_set_editable(GTK_ENTRY(edit[i]), 1); /* Определяем одну для всех реакцию на сигнал activate - нажатие Enter*/ gtk_signal_connect(GTK_OBJECT(edit[i]), "activate", GTK_SIGNAL_FUNC(enter_callback), edit[i]); /* Помещаем edit[i] в таблицу */ gtk_table_attach_defaults (GTK_TABLE(table), edit[i], 1, 2, i, i+1); /* Показываем */ gtk_widget_show (edit[i]); } /* Создаем кнопку "Ok", помещаем в таблицу, определяем реакцию на нажатие и показываем */ button = gtk_button_new_with_label ("Ok"); gtk_table_attach_defaults (GTK_TABLE(table), button, 2, 3, 0, 1); gtk_signal_connect(GTK_OBJECT(button),"clicked",GTK_SIGNAL_FUNC(writetofile),NULL); gtk_widget_show (button); /* Тоже самое для кнопки Quit */ button = gtk_button_new_with_label ("Quit"); gtk_table_attach_defaults (GTK_TABLE(table), button, 2, 3, 2, 3); gtk_signal_connect(GTK_OBJECT(button),"clicked",GTK_SIGNAL_FUNC(delete_event),NULL); gtk_widget_show (button); gtk_widget_show (table); /* Показываем таблицу */ gtk_widget_show (window); /* Показываем окно */ /* Запускаем GTK-программу */ gtk_main (); return 0; }Я старался писать подробные комментарии, но все же кое-что осталось в тумане. Это координаты ячеек. Рассмотрим нашу таблицу 3х3:
0 1 2 3 Domain Поле Ок 1 DNS1 Поле 2 DNS2 Поле Quit 3
Сначала указываются координаты по X, затем - по Y. Вот координаты кнопки Ok: 2,3,0,1. Это означает, что кнопка будет расположена в последнем столбике (2,3), но в первом ряду (0,1). Чтобы было понятнее: ОК по Х находится между 2 и 3, а по Y - между 0 и 1.
Теперь откомпилируем нашу программу:
gcc -g resolv.c -o resolv `gtk-config --cflags` `gtk-config --libs`
Можно не использовать опцию -g, добавляющую отладочную информацию - размер файла станет меньше. Программа gtk-config сообщает компилятору всю необходимую информацию о библиотеке gtk. Обратите внимание на директиву #include <gtk/gtk.h>. Обычно файлы заголовков gtk находятся в другом каталоге, например, gtk-1.2, но это совсем не имеет значения - все необходимые параметры укажет программа gtk-config.
У вас некорректно отображаются русские названия надписей и кнопок? Эта проблема очень быстро устраняется с помощью GNOME Control Center - вам всего лишь нужно выбрать другой шрифт.
Вот теперь этот небольшой обзор можно считать полным. Конечно, я не рассмотрел еще много чего - переключатели, графические кнопки (аналог SpeedButton в Delphi), ..., но помните - это всего лишь статья, а не руководство по GTK! Если вы заинтересовались, прочитайте документацию по GTK - file:/usr/share/doc/gtk+-devel-1.2.10/html/gtk_tut-2.html#ss2.1 Ваши вопросы и комментарии можно отправлять по адресу dhsilabs@mail.ru.