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

Денис Колисниченко

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

Linux coutner
Hosted by uCoz