SpyLOG Rambler's Top100

Создание GUI в Linux. Часть 1

Автор: Денис Колисниченко, dhsilabs@mail.ru
Опубликовано: 5.12.2002


© 2002, Издательский дом «КОМПЬЮТЕРРА» | http://www.computerra.ru/
Журнал «СОФТЕРРА» | http://www.softerra.ru/
Этот материал Вы всегда сможете найти по его постоянному адресу: http://www.softerra.ru/program/22309/

В этой статье мы поговорим о создании графического интерфейса для вашей 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. Напомню вам формат этого файла:

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

Рис. 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.


Телефон редакции: (095) 232-2261
E-mail редакции: inform@softerra.ru
По вопросам размещения рекламы обращаться к Алене Шагиной по телефону +7 (095) 232-2263 или электронной почте reclama@computerra.ru