Linux. Полное руководство.
Глава 23. Разработка графического приложения: библиотека GTK+
Сейчас мы поговорим о создании графического интерфейса для вашей Linux-программы. Как вы знаете, средствами одного С нормальный GUI не построишь, тем более что привычный пользователь Windows очень требователен не просто к наличию GUI, но и к дизайну формы (окна программы). Поэтому без дополнительных библиотек вам не обойтись. Самыми распространенными библиотеками для создания GUI являются библиотеки GTK+ и Qt. Рекомендуется использовать только эти библиотеки, поскольку велика вероятность того, что они уже будут установлены у пользователя (уж GNOME и KDE есть почти у всех). Рассмотрим подробно библиотеку GTK+, поскольку она, на мой взгляд, проще, чем Qt.
Скорее всего, GTK+ у вас уже установлена, но вам понадобится дополнительно установить пакет gtk+-devel, содержащий необходимые файлы для разработки GTK-программ.
23.1. Введение в GTK+
Первоначально библиотека GTK+ (далее GTK) была разработана для использования программой GIMP, отсюда и название —The GIMP Toolkit. Со временем библиотека стала использоваться для разработки других приложений для среды GNOME. Сейчас GTK — это объектно-ориентированный и кросс-платформенный инструмент для создания графического интерфейса приложений, причем созданные с использованием GTK приложения независимы от среды GNOME. Ваше приложение, написанное на GTK, будет отлично работать в KDE или любой другой среде — главное, чтобы библиотека GTK была установлена на компьютере.
Сама библиотека GTK+ стоит на трех китах:
♦ Библиотека Glib — предназначена для оперирования с различными типами данных. Данная библиотека будет полезной для разработки любых приложений, даже не GTK. Эта же библиотека содержит функции для работы с памятью и для обработки ошибок. О типах данных библиотеки Glib мы поговорим позже.
♦ Библиотека GDK (The GIMP Drawing Kit) — библиотека низкого уровня, ее функции взаимодействуют с функциями оконной системы (X Window). Используется для построения графических примитивов.
♦ Библиотека GTK — используется для создания графического интерфейса.
23.2. Библиотека Glib.
23.2.1. Стандартные типы данных библиотеки Glib.
Библиотека Glib содержит типы данных, аналогичные типам данных языка С, а также такие структуры, как деревья, списки; функции для работы с памятью и обработки ошибок. Это сделано для того, чтобы ваше приложение могло быть легко портировано на другую платформу. Например, на одних компьютерах тип int может быть 32-разрядным, а на других — 64-разрядным (это зависит от архитектуры центрального процессора), А если вы используете тип gint, объявленный в библиотеке Glib, то можете дальше разрабатывать свое приложение и не беспокоиться о том, как оно будет работать на RISC-машине под управлением Windows NT. В таблице 23.1 перечислены типы данных библиотеки GLib, которые соответствуют типам данных С.
Таблица соответствия типов данных Glib и С Таблица 23.1.
| Тип данных С | Тип данных Glib |
|---|---|
| char | gchar |
| short | gshort |
| long | glong |
| int | gint |
| bool | gboolean |
| unsigned char | guchar |
| unsigned short | gushort |
| unsigned long | gulong |
| unsigned int | guint |
| float | gfloat |
| double | gdouble |
| long double | gldouble |
| void* | gpointer |
Для использования этих типов данных, как и прочих возможностей библиотеки Glib, необходимо подключить заголовочный файл glib.h.
23.2.2. Функции для работы с памятью.
Функции для работы с памятью библиотеки Glib выполняют те же действия, что и соответствующие им функции языка С. Вот их прототипы:
Gpointer g_malloc(gulong size);
Gpointer g_realloc(gpointer mem, gulong size);
Void g_free(gpointer mem);
23.2.3. Строки и Glib.
Библиотека Glib содержит довольно много функций для работы со строками, но я перечислю лишь самые с моей точки зрения интересные (таблица 23.2).
Некоторые строковые функции библиотеки Glib Таблица 23.2.
| Прототип | Описание |
|---|---|
| gchar* g_strchug(gchar* s) | Функция удаляет все пробелы в строке s, стоящие в начале строки (до первого печатного символа). Данная функция может пригодиться для контроля введенной пользователем информации |
| gchar* g_strchomp(gchar* s) | Функций удаляет все пробелы в строке s, стоящие в конце строки |
| gchar* g_strstrip(gchar* s) | Функция удаляет пробелы а начале и в конце строки |
| void g_strdown(gchar* s) | Функция переводит все буквы строки s в нижний регистр |
| void g_strup(gchar* s) | Функция переводит все буквы строки s в верхний регистр |
| void g_strreverse(gchar* s) | Функция преобразовывает прописные буквы в строчные и наоборот |
23.2.4. Списки.
Библиотека Glib содержит средства для работы с одно- и двусвязными списками. Особенность двусвязного списка заключается в том, что по нему можно перемещаться в обоих направлениях — назад и вперед. В файле gslist.h (Glib Single List) описаны средства для работы с односвязными списками, а в файле glist.h — для работы с двусвязным списком.
Вот структуры односвязного и двусвязного списков:
// односвязный список.
Typedef struct _GSList GSList;
Struct _GSList {
gpointer data;
GSList *next; // указатель на следующий элемент списка.
};
// двусвязный список.
Typedef struct _GList GList;
Struct _GList {
gpointer data;
GList *next; // указатель на следующий элемент списка.
GList *prev; // указатель на предыдущий элемент списка.
};
Поле data предназначено для хранения данных списка, причем они могут быть любого типа, ведь gpointer — это тип void*.
Работать со списками очень просто. Для начала нужно объявить список:
GList *list = NULL;
GSList *slist = NULL;
Затем добавить элементы в список. Это можно сделать с помощью двух функций — g_list_append() или g_slist_prepend() — в зависимости от используемого типа списка:
Gchar *el = g_strdup("это первый элемент");
List = g_list_append(list, el);
Функции g_list_append() и g_slist_append() добавляют элемент в конец списка. Если вы хотите добавить элемент в начало списка, нужно использовать функции:
G_list_prepend(GList *list, gpointer data);
G_slist_prepend{GSList *list, gpointer data);
Чтобы вставить новый элемент в определенную позицию, нужно использовать функции:
GList *g_list_insert(GList *list, gpointer data, gint position);
GSList *g_slist_insert(GSList *list, gpointer data, gint position);
Здесь position — это номер элемента, перед которым нужно вставить новый элемент. Если position=0, то элемент будет добавлен в начало списка, то есть перед бывшим первым элементом.
Для удаления элемента используются функции:
GList *g_list_remove(GList *list, gpointer data);
GSList *g_slist_remove(GSList *list, gpointer data);
Для передвижения по списку используются функции:
G_list_next(), g_slist_next() — на "шаг" вперед.
G_list_prev() — назад.
Вот небольшой пример работы со списком — вывод на консоль всех его элементов:
// double_list должен быть определен и содержать элементы.
GList *list = double_list;
While (list!=NULL) {
printf("%s\n",list->data);
list = g_list_next(list);
}
По окончании работы со списком не забудьте освободить память:
Void g_list_free(GList *list);
Void g_slist_free(GSList *slist);
Для сортировки списка используется функция:
GSList *g_slist_sort(GSList * slist, GCompareFunc f);
Первый параметр — это список, который нужно отсортировать. Второй — это функция сравнения двух элементов. Вот ее прототип:
Typedef gint (*GCompareFunc) (gconstpointer a, gconstpointer b); Данную функцию вы должны написать самостоятельно. Она должна принимать два параметра и возвращать целое значение:
♦ если a<b, то -1 (точнее, любое число меньше 0);
♦ если a==b, то 0;
♦ если a>b, то 1 (любое число больше 0).
Библиотека Glib также содержит средства для работы с деревьями — как бинарными, так и произвольными, но мы эти средства рассматривать не будем.
23.2.5. Таймеры в Glib.
Библиотека Glib позволяет использовать таймеры в наших программах.
Для этого нужно:
♦ подключить заголовочный файл gtimer.h;
♦ создать таймер функцией GTimer *g_timer_new();
♦ запустить таймер функцией g_timer_start(GTimer *timer);
♦ узнать время, отсчитанное таймером — g_timer_elapsed();
♦ при необходимости перезапустить таймер с помощью функции g_timer_reset(GTimer *timer);
♦ остановить таймер функцией g_timer_stop(GTimer * timer);
♦ уничтожить таймер — g_timer_destroy(GTimer *timer).
Стоит остановиться подробнее лишь на функции g_timer_elapsed(GTimer *timer, gulong *mcs). Данная функция возвращает число секунд, отсчитанное таймером. По адресу указателя *mcs записывается число микросекунд.
Пример использования таймера представлен в листинге 23.1.
Листинг 23.1. Использование таймера.
#include <stdio.h>
#include <glib.h>
#include <gtimer.h>
Int main() {
double sec;
gulong ms;
int i;
GTimer *timer = g_timer_new();
printf("Данный цикл будет работать не более 10 секунд\n");
g_timer_start(timer);
for (i=1; i>0;) {
sec = g_timer_elapsed(timer,&ms);
if (sec >=10) {
g_timer_stop(timer);
printf("Таймер остановлен. Мкс: %d\n",ms);
break;
}
}
g_timer_destroy(timer);
return 0;
}
23.3. Первая программа на GTK+
23.3.1. Виджиты.
Перед написанием самой простой GTK-программы нужно разобраться с терминологией GTK. Элементы графического интерфейса пользователя — окна, кнопки, поля ввода, переключатели и тому подобное — называются виджитами.
Основным элементом графического интерфейса является окно. Виджиты для размещения в окне помещаются в контейнер. В самом окне выравнивать виджиты можно с помощью вертикальных/горизонтальных боксов или же таблиц. Второй способ более гибок, хотя он может показаться вам сложнее.
Виджиты могут реагировать на сигналы, например, щелчок мышью. При этом вызывается функция-обработчик события (сигнала), если вы определили ее.
Работа с виджитами происходит по такой схеме:
1. создание виджита с помощью одной из функций библиотеки GTK;
2. определение свойств виджита;
3. определение сигналов виджита, если он должен реагировать на сигналы;
4. размещение виджита в контейнере, то есть привязка его к окну;
5. отображение виджита.
Нужно обязательно отобразить виджит, иначе его никто не увидит. Например, следующий фрагмент кода создает виджит — кнопку с текстом — и отображает ее.
GtkWidget *button;
...
/* Рисуем кнопочку с надписью 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);
Первый оператор создает кнопку (button), второй — добавляет кнопку в контейнер. В данном случае контейнером является наше окно. Виджит window должен быть создан раньше: нельзя создать кнопку без окна. Точнее, можно, но тогда она не будет привязана к какому-либо окну и мы ее не увидим. Функция gtk_widget_show() отображает нашу кнопку. Не забудьте отобразить и само окно. Порядок отображения виджитов особой роли не играет, но рекомендуется главное окно отображать в последнюю очередь.
23.3.2. Окна.
Сейчас мы напишем программу, которая будет формировать небольшое графическое окошко. Начнем сразу с исходного кода — так будет проще понять, что есть что.
Листинг 23.2. Простое окно (файл first.с).
#include <gtk/gtk.h>
Int main(int argc, char *argv[]) {
GtkWidget *window1;
gtk_init(&argc, &argv);
window1 = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(window1), "Заголовок");
gtk_widget_show(window1);
gtk_main();
return 0;
}
Сначала мы подключаем заголовочный файл gtk/gtk.h — это необходимое условие для того, чтобы начать работу с библиотекой GTK. В первой строке программы мы объявляем наш основной (и единственный в этой программе) виджит — виджит основного окна:
GtkWidget *window1;
Обратите внимание, что виджит объявлен, но работать с ним пока нельзя. Сначала (обязательно до вызова первой GTK-функции) нужно вызвать инициализирующую функцию gtk_init() и передать ей два два параметра — аргументы функции main(). После того, как библиотека инициализирована, нужно вызвать функцию gtk_window_new(), которая создает окно (напомню, что пока окно объявлено, но не создано). Теперь, когда виджит окна создан, можно установить его свойства и определить реакцию на сигналы. Установим свойство Title (заголовок) окна. Это делается с помощью функции gtk_window_set_title():
Gtk_window_set_title(GTK_WINDOW(window1), "Заголовок");
Теперь можно отобразить наше окно:
Gtk_widget_show(window1);
Чтобы наше приложение могло реагировать на события оконной среды (например, щелчок мыши), нужно вызвать функцию gtk_main(). Функции gtk_init() и gtk_main() должны присутствовать в любой GTK-программе.
Теперь откомпилируем наше приложение. Для этого введем следующую команду в командной строке:
$ gcc first.с -о first `gtk-config --cflags` `gtk-config --libs`
Флаги `gtk config --cflags` `gtk-config --libs` нужно использовать при компиляции любой GTK-программы. Если компиляция не удается, то проверьте, что вы используете апострофы ( `), а не одинарные кавычки ( '), и что программа gtk-config у вас установлена.
Запустим нашу программу в эмуляторе терминала X Window (или оконной среды GNOME/KDE):
$ ./first.
Вы увидите окно, изображенное на рис. 23.1.
Рис. 23.1. Простое окно.
Теперь закроем окно и перейдем к терминалу: окно закрыто, мы его больше не видим, а терминал не освобожден. Наша программа не реагирует на событие закрытия окна. По идее, когда графическая среда закрывает окно, программа должна завершить свою работу. А наша программа этого не делает. Значит, нужно «научить» ее реагировать на события (сигналы) оконной системы. Для этого нажмите в терминале Ctrl+С и отредактируйте исходный текст программы следующим образом:
Листинг 23.3. Добавим реакцию на закрытие окна.
#include <gtk/gtk.h>
Int main(int argc, char *argv[]) {
GtkWidget *window1;
gtk_init(*argc, &argv);
window1 = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_signal_connect(GTK_OBJECT(window1), "destroy",
GTK_SIGNAL_FUNC(gtk_main_quit), NULL);
gtk_widget_show(window1);
gtk_main();
return 0;
}
Функция gtk_signal_connect() устанавливает реакцию объекта window1 на сигнал destroy и вызывает функцию qtk_main_quit() для завершения работы программы.
А что если нам при завершении работы программы нужно выполнить какие-нибудь специфические действия, например, удалить временные файлы? Тогда нужно написать свою функцию-обработчик события destroy (листинг 23.4).
Эта функция будет называется destroy_window1(), и мы «пропишем» ее в функции gtk_signal_connect() в качестве обработчика события закрытия окна вместо gtk_main_quit(). Делать она не будет ничего, просто вызовет стандартную функцию gtk_main_quit().
Листинг 23.4. Добавляем собственную функцию-обработчик завершения работы.
#include <gtk/gtk.h>
Void destroy_window1(GtkWidget *widget, gpointer data);
Int main(int argc, char *argv[]) {
GtkWidget *window1;
gtk_init(&argc, &argv);
window1 = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_signal_connect(GTK_OBJECT(window1), "destroy",
(GtkSignalFunc)destroy_window1, &window1);
gtk_widget_show(window1);
gtk_main();
return(0);
}
Void destroy_window1(GtkWidget *widget, gpointer data) {
gtk_main_quit();
}
23.3.3. Изменение размеров окна.
Вам кажется, что окно слишком маленькое и не подходит для нашей программы? Для изменения размеров окна лучше всего использовать функцию.
Void gtk_window_set_default_size(GtkWindow *window,
gint width, gint height);
Эта функция устанавливает ширину окна window равной width, а высоту — height.
Для изменения позиции окна на экране используется функция.
Void gtk_widget_set_uposition(GtkWidget *widget,
gint coord_x, gint coord_y);
Эту же функцию можно использовать для изменения расположения любого виджита — не обязательно, чтобы последний был окном.
Первая функция объявлена в файле gtk/gtkwindow.h, а вторая — в файле gtk/gtkwidget.h:
#include <gtk/gtkwindow.h>
#include <gtk/gtkwidget.h>
...
Gtk_window_set_default_size(window1, 200, 300);
Gtk_wigdet_set_uposition(window1, 50, 50);
23.3.4. Обработка сигналов.
Перед тем, как перейти к следующему пункту, нужно еще раз рассмотреть функцию gtk_signal_connect(). Данной функции нужно передать четыре параметра:
♦ GtkObject *object — объект, которому может быть послан сигнал;
♦ const gchar *name — имя сигнала, например, «destroy»;
♦ GtkSignalFunc func — имя функции обратного вызова, то есть функции, которая будет вызвана для обработки сигнала;
♦ gpointer data — любые данные, которые будут переданы функции-обработчику.
Что такое сигнал? Как только пользователь переместил мышь, оконная среда посылает приложению сигнал, оповещающий о том, что мышь была перемещена. Как только пользователь щелкнул мышью, приложение получит сигнал об этом щелчке. Обрабатывать все сигналы может окно, но удобнее для каждого виджита установить собственную реакцию на события.
Функция, которая обрабатывает сигнал, называется по-разному: функция-обработчик (мы будем использовать именно это название), функция обратного вызова и callback-функция. Такой функции нужно передать два параметра (их передаст сама GTK):
♦ GtkWidget *widget — виджит;
♦ gpointer data — данные.
Параметры, которые нужно передать обработчику, зависят от передаваемого сигнала. Например, если бы мы передавали не сигнал «destroy», а сигнал «delete-event», то нужно было бы указать уже три параметра:
♦ GtkWidget *widget — виджит;
♦ GdkEvent *event — событие;
♦ gpointer data — данные.
Вот наиболее часто используемые сигналы:
♦ button_press_event — нажата левая кнопка мыши;
♦ button_release_event — левая кнопка отпущена;
♦ motion_notify_event — движение мыши;
♦ delete_event — удаление объекта;
♦ destroy_event — уничтожение объекта;
♦ key_press_event — нажата клавиша клавиатуры;
♦ key_release_event — клавиша отпущена;
♦ enter_notify_event — указатель мыши вошел в пределы объекта;
♦ leave_notify_event — указатель мыши вышел за пределы объекта;
♦ focus_in_event — объект стал активным (получил фокус);
♦ focus_out_event — объект не активен;
♦ drag_begin_event — начало перемещения объекта;
♦ drag_request_event — запрос на перемещение объекта;
♦ drag_end_event — перемещение объекта;
♦ drop_enter_event — объект перемещен.
Наиболее часто используемые события GDK (используются в функции-обработчике) перечислены ниже:
♦ GDK_NOTHING — не произошло никакого события;
♦ GDK_DELETE — удаление;
♦ GDK_DESTROY — уничтожение;
♦ GDK_MOTION_NOTIFY — уведомление о перемещении;
♦ GDK_BUTTON_PRESS — нажата любая кнопка мыши;
♦ GDK_1BUTTON_PRESS — нажатие первой кнопки мыши;
♦ GDK_2BUTTON_PRESS — нажатие второй кнопки мыши;
♦ GDK_3BUTTON_PRESS — нажата третья кнопка;
♦ GDK_BUTTON_RELEASE — кнопка (любая) отпущена;
♦ GDK_KEY_PRESS — нажата клавиша;
♦ GDK_KEY_RELEASE — клавиша отпущена;
♦ GDK_ENTER_NOTIFY — указатель мыши в пределах объекта (виджита);
♦ GDK_LEAVE_NOTIFY — указатель мыши вышел за пределы виджита;
♦ GDK_FOCUS_CHANGE — изменения фокуса ввода;
♦ GDK_OTHER_EVENT — другое событие.
23.3.5. Виджит событий — EventBox.
Далеко не все виджиты связаны с окнами. Например, GtkLabel (надпись), GtkTable (контейнер-таблица), GtkHBox (горизонтальный контейнер), GtkVBox (вертикальный контейнер) и некоторые другие с окнами не связаны.
Если нужно, чтобы эти виджиты реагировали на определенные сигналы, нужно использовать виджит EventBox, позволяющий привязать сигнал к не связанному с окном виджиту. Следующая программа демонстрирует привязку события button_press_event к виджиту GtkLabel.
Листинг 23.5. Виджит EventBox.
#include <gtk/gtk.h>
Int main(int argc, char *argv[]) {
GtkWidget *window1; // главное окно.
GtkWidget *event_box1; // eventbox.
GtkWidget *label; // надпись.
/* Инициализируем GTK */
gtk_init( &argc, &argv );
/* Создаем окно с заголовком "Надпись" */
window1 = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(window1), "Надпись");
/* Устанавливаем реакцию на закрытие окна */
gtk_signal_connect(GTK_OBJECT(window1), "destroy",
GTK_SIGNAL_FUNC(gtk_exit), NULL );
/* устанавливаем ширину рамки контейнера — окна */
gtk_container_set_border_width(GTK_CONTAINER(window1), 10);
/* создаем event_box */
event_box1 = gtk_event_box_new();
/* помещаем event_box в контейнер */
gtk_container_add(GTK_CONTAINER(window1), event_box1);
/* отображаем event_box */
gtk_widget_show(event_box1);
/* создаем надпись */
label = gtk_label_new(" -== Click here to exit. ==- ");
/* помещаем надпись в контейнер event_box */
gtk_container_add(GTK_CONTAINER(event_box1), label);
/* отображаем окно */
gtk_widget_show(label);
/* устанавливаем реакцию GtkLabel на щелчок */
/*(при щелчке - выход) */
gtk_widget_set_events(event_box1, GDK_BUTTON_PRESS_MASK);
gtk_signal_connect(GTK_OBJECT(event_box1), "button_press_event",
GTK_SIGNAL_FUNC(gtk_exit), NULL);
gtk_widget_realize(event_box1);
/* изменяем курсор над надписью — курсор превратится в руку */
gdk_window_set_cursor(event_box1->window, gdk_cursor_new(GDK_HAND1));
/* отображаем окно */
gtk_widget_show(window1);
gtk_main();
return 0;
}
Откомпилируйте и запустите программу. Над надписью указатель мыши должен принять вид руки (как в браузере над ссылкой). При щелчке на надписи программа будет закрыта.
Если вы хотите создать надпись на русском языке, то подключите заголовок locale.h и вызовите функцию:
Setlocale(LC_ALL, "ru_RU.KOI8-R");
Конечно, значение локали у вас может быть другим. Эту функцию нужно вызвать ДО инициализации GTK+.
Рис. 23.2. Надпись.
23.4. Виджиты.
23.4.1. Рождение, смерть и состояния виджита.
После создания виджита управление ресурсами и памятью, необходимыми ему, выполняется автоматически. Виджиты уничтожаются также автоматически — при разрушении главного окна. Но иногда бывает нужно самостоятельно уничтожить виджит. Сделать это можно с помощью функции:
Void gtk_widget_destroy(GtkWidget *widget);
Эта функция объявлена в файле gtk/gtkwidget.h. При уничтожении виджита также уничтожаются все его дочерние виджиты.
Если вы освобождаете виджит из контейнера функцией:
Void gtk_container_remove(GtkContainer *cont, GtkWidget *w);
То виджит также будет разрушен.
Иногда нужно переместить виджит из одного контейнера в другой без его уничтожения. Это можно сделать так (мы будем перемещать надпись):
Gtk_widget_ref(GTK_WIDGET(label));
Gtk_container_remove(GTK_CONTAINER(cont1), label);
Gtk_container_add(GTK_CONTAINER(cont2), label);
«Спрятать» виджит можно с помощью функции.
Void gtk_widget_hide(GtkWidget *w);
Отобразить виджит снова поможет функция gtk_widget_show().
Виджит может находиться в одном из состояний:
♦ GTK_STATE_NORMAL — нормальное;
♦ GTK_STATE_ACTIVE — активное (например, нажата кнопка);
♦ GTK_STATE_PRELIGTH — над виджитом находится указатель мыши;
♦ GTK_STATE_SELECTED — виджит выбран (установлен фокус ввода);
♦ GTK_STATE_INSENSITIVE — виджит не реагирует на ввод (сигналы).
Определить состояние виджита можно так:
GTK_WIDGET(w)->state.
Или с помощью макроса:
GTK_WIDGET_STATE(wid).
Описанного в файле gtk/gtkwidget.h.
Сделать виджит неактивным можно так:
Gtk_widget_set_sensitive(widget, FALSE);
Если второй параметр функции gtk_widget_set_sensitive() будет равен TRUE, виджит widget станет активным.
Чтобы наш виджит получил фокус ввода, нужно использовать функцию:
Gtk_widget_grab_focus(widget);
23.4.2. Упаковка виджитов, поля ввода и кнопки.
Для размещения (упаковки) виджита в окне используются контейнеры. Существуют два основных вида контейнеров. Первый вид в качестве прародителя использует объект класса GtkBin, а второй — объект класса GtkContainer. Контейнеры первого вида могут иметь только один дочерний виджит, поэтому они используются для создания специфических интерфейсов: одной кнопки, рамки, окна.
Контейнеры второго вида более функциональны — они могут иметь много дочерних виджитов. Чаще всего используются контейнеры:
♦ GtkHBox — позволяет размещать виджиты горизонтально;
♦ GtkVBox — используется для вертикального размещения виджитов;
♦ GtkFixed — позволяет размещать виджиты в фиксированных координатах;
♦ GtkTable — позволяет упаковывать виджиты в виде таблицы.
Наиболее удачным, на мой взгляд, является контейнер GtkTable, поэтому в этом параграфе мы рассмотрим именно его. GtkTable может с успехом заменить и горизонтальный, и вертикальный контейнеры — что нам стоит задать таблицу, состоящую или одной строки или одного столбца?
Кроме контейнера GtkTable, в этом параграфе будут рассмотрены:
♦ поля для ввода текста и обработка введенной информации;
♦ кнопки;
♦ файловый ввод/вывод.
Сейчас мы напишем небольшой конфигуратор, который будет вносить изменения в файл /etc/resolv.conf. Напомню вам формат этого файла:
Domain firma.ru.
Nameserver 192.168.0.1.
Nameserver 192.168.0.2.
Директива domain определяет наш домен, а две директивы nameserver — первый и второй DNS-серверы, соответственно. Наш конфигуратор не будет вносить изменения в настоящий файл /etc/resolv.conf — для этого нужны права суперпользователя. При желании можно будет потом скопировать файл resolv.conf, сгенерированный нашей программой, в каталог /etc.
На рисунке 23.2 изображена уже готовая программа. Работает она так. Когда пользователь введет что-нибудь в поле ввода и нажмет Enter, программа отобразит введенный им текст на консоли. Когда пользователь нажмет OK, введенная им информация будет еще раз выведена на консоль и записана в файл. При нажатии кнопки Quit программа завершит свою работу. Она должна также завершить работу при нажатии кнопки закрытия окна — в GTK программист сам определяет реакции на стандартные кнопки.
Рис. 23.3. Учебный конфигуратор.
Как видно из рисунка, нам понадобятся три поля ввода, три надписи и две кнопки. Поля ввода мы будем хранить в массиве:
GtkWidget *edit[3];
Создать поле для ввода можно с помощью функции gtk_entry_new():
Edit[i] = gtk_entry_new();
После создания поля необходимо вызвать функцию gtk_entry_set_editable(), иначе пользователь ничего не сможет ввести в это поле.
Gtk_entry_set_editable(GTK_ENTRY(edit[i]), 1);
Ну и, само собой разумеется, нужно установить реакцию на нажатие клавиши Enter — сигнал activate:
Gtk_signal_connect(GTK_OBJECT(edit[i]), "activate",
GTK_SIGNAL_FUNC(enter_callback), edit[i]);
Весьма желательно на этапе отладки программы видеть введенную информацию на консоли. Для этого нужно написать такую функцию enter_callback(), которая выводила бы содержимое поля на консоль. Получить введенную пользователем информацию очень легко:
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]))?
Реакция на нажатие кнопки OK будет следующей:
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 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);
}
Если ваше окно должно содержать много надписей, то я рекомендую вам поступать так: объявить всего одну переменную, затем создать надпись, поместить ее в контейнер, затем опять создать надпись с использованием этой же переменной, поместить ее в контейнер и т.д. Примерно так:
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);
Листинг 23.6 содержит полный код конфигуратора Resolver.
Листинг 23.6. Файл resolver.c.
#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 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:
Table = gtk_table_new(3, 3, TRUE);
0 1 2 3.
Domain Поле OK.
1.
DNS1 Поле.
2.
DNS2 Поле Quit.
3.
Сначала указываются координаты по X, затем — по Y. Вот координаты кнопки OK: 2, 3, 0, 1. Это означает, что кнопка будет расположена в последнем столбце (между 2 и 3), но в первой строке (между 0 и 1).
Gtk_table_attach_defaults(GTK_TABLE(table), button,
2, 3, 0, 1);
Подробнее рассматривать контейнер GtkTable я не вижу смысла: основные операции, я думаю, вам понятны — это создание таблицы с указанием ее размерности и добавление в таблицу виджита функцией gtk_table_attach_defaults(). Еще раз напомню о необходимости отображения виджитов, помещенных в таблицу, и самой таблицы:
Gtk_widget_show (table);
Теперь откомпилируем нашу программу:
$ gcc resolv.с -о resolv `gtk-config --cflags` `gtk-config --libs`
Программа gtk-config сообщает компилятору всю необходимую информацию о библиотеке GTK.
Обратите внимание на директиву.
#include <gtk/gtk.h>
Обычно файлы заголовков GTK находятся в другом каталоге, например, gtk-1.2, но это не имеет значения — все необходимые параметры укажет программа gtk-config.
В заключение этого пункта перечислим события, характерные для кнопок (таблица 23.3).
События кнопок Таблица 23.3.
| Событие | Описание |
|---|---|
| clicked | Щелчок |
| pressed | Кнопка нажата мышью (и пока не отпущена) |
| released | Кнопка отпущена |
| enter | Указатель мыши в пределах кнопки |
| leave | Указатель мыши вышел за пределы кнопки |
23.4.3. Переключатели.
Переключатели бывают двух типов: зависимые (radio buttons) и независимые (checkbuttons). Переключатели являются кнопками, поэтому для них характерны те же события, что и для кнопок.
Начнем с независимых переключателей, так как они проще в реализации. Создать такой переключатель можно с помощью одной из функций:
GtkWidget *gtk_check_button_new(void);
GtkWidget *gtk_check_button_new_with_label(gchar * label);
Первая создает переключатель без надписи (если вы хотите указать надпись отдельно), а вторая — с надписью, которая обычно отображается справа от переключателя. Затем нужно, как всегда, поместить виджиты в контейнер и отобразить.
Зависимые переключатели можно создать тоже с помощью двух аналогичных функций:
GtkWidget *gtk_radio_button_new(GSList *group);
GtkWidget *gtk_radio_button_new_with_label(GSList *group,
gchar *label);
Параметр group указывает на принадлежность переключателя к группе. В пределах группы активным может быть только один переключатель. Группу можно создать функцией:
GSList *gtk_radio_button_group(
GtkRadioButton *radio_button);
Однако существует другой способ, позволяющий обойтись без переменной группы — так мы сэкономим память, если групп много:
Button2 = gtk_radio_button_new_with_label(
gtk_radio_button_group(GTK_RADIO_BUTTON(button1)),
"button2");
С помощью функции.
Void gtk_toggle_button_set_active(
GtkToggleButton *toggle_button, gint state);
Можно сделать одну из кнопок активной.
Следующий листинг демонстрирует работу с тремя зависимыми переключателями и вертикальным контейнером GtkVBox.
Листинг 23.7. Зависимые переключатели.
#include <gtk/gtk.h>
#include <glib.h>
Gint close_application( GtkWidget *widget,
GdkEvent * event, gpointer data) {
gtk_main_quit();
return(FALSE);
}
Int main(int argc, char *argv[]) {
GtkWidget *window = NULL;
GtkWidget *box1;
GtkWidget *box2;
GtkWidget *button;
GtkWidget *separator;
GSList *group;
gtk_init(&argc,&argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_signal_connect(GTK_OBJECT(window), "delete_event",
GTK_SIGNAL_FUNC(close_application), NULL);
gtk_window_set_title(GTK_WINDOW(window),
"Выберите дистрибутив");
gtk_container_set_border_width(GTK_CONTAINER(window), 0);
box1 = gtk_vbox_new(FALSE, 0);
gtk_container_add(GTK_CONTAINER(window), box1);
gtk_widget_show(box1);
box2 = gtk_vbox_new(FALSE, 10);
gtk_container_set_border_width(GTK_CONTAINER (box2), 10);
gtk_box_pack_start(GTK_BOX(box1), box2, TRUE, TRUE, 0);
gtk_widget_show(box2);
button = gtk_radio_button_new_with_label(NULL, "Red Hat");
gtk_box_pack_start(GTK_BOX(box2), button,
TRUE, TRUE, 0);
gtk_widget_show(button);
group = gtk_radio_button_group(GTK_RADIO_BUTTON(button));
button =
gtk_radio_button_new_with_label(group, "Mandrake");
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button),
TRUE);
gtk_box_pack_start(GTK_BOX(box2), button,
TRUE, TRUE, 0);
gtk_widget_show(button);
button = gtk_radio_button_new_with_label(
gtk_radio_button_group(GTK_RADIO_BUTTON(button)),
"ALT Linux");
gtk_box_pack_start(GTK_BOX(box2), button,
TRUE, TRUE, 0);
gtk_widget_show(button);
separator = gtk_hseparator_new();
gtk_box_pack_start(GTK_BOX(box1), separator,
FALSE, TRUE, 0);
gtk_widget_show(separator);
box2 = gtk_vbox_new(FALSE, 10);
gtk_container_set_border_width(GTK_CONTAINER(box2), 10);
gtk_box_pack_start(GTK_BOX(box1), box2, FALSE, TRUE, 0);
gtk_widget_show(box2);
button = gtk_button_new_with_label("OK");
gtk_signal_connect_object(GTK_OBJECT (button),
"clicked", GTK_SIGNAL_FUNC(close_application),
GTK_OBJECT(window));
gtk_box_pack_start(GTK_BOX (box2), button,
TRUE, TRUE, 0);
GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
gtk_widget_grab_default(button);
gtk_widget_show(button);
gtk_widget_show(window);
gtk_main();
return(0);
}
Рис. 23.4. Зависимые переключатели.
23.4.4. Список.
Виджит CList представляет собой список, состоящий из нескольких колонок. Ячейки такого списка могут содержать текстовые значения. Мы можем обратиться отдельно к каждой ячейке списка. Создать список можно одной из функций:
GtkWidget *gtk_clist_new(gint columns);
GtkWidget *gtk_clist_new_with_titles(gint columns,
gchar *titles[]);
Первая функция создает список без заголовков, а вторая с заголовками. Параметр columns задает число колонок.
Добавить элемент в список позволяют функции:
Gint gtk_clist_prepend(GtkCList *clist, gchar *text[]);
Gint gtk_clist_append(GtkCList *clist, gchar *text[]);
Первая функция добавляет новый элемент в начало списка, а вторая — в его конец. Если вам необходимо вставить элемент в определенную позицию, вам нужно использовать функцию:
Void gtk_clist_insert(GtkCList *clist, gint row, gchar *text[]);
Она позволяет вставить новый элемент в строку row. Нумерация строк списка начинается с 0.
Для удаления элементов списка можно использовать одну из функций:
Void gtk_clist_remove(GtkCList *clist, gint row);
Void gtk_clist_clear(GtkCList *clist);
Первая удаляет строку row, а вторая очищает весь список.
Рассмотрим листинг 23.8, в котором демонстрируется работа со списком CList. Программа снабжена подробными комментариями, поэтому рекомендую внимательно читать исходный код.
Листинг 23.8. Применение виджита CList.
#include <gtk/gtk.h>
/* Нужен для функции setlocale() */
#include <locale.h>
/* Добавляет список - обработчик кнопки Добавить */
Void button_add_clicked(gpointer data) {
int indx;
/* Простой список */
gchar *dist[4][2] = { { "1", "Red Hat Linux" },
{ "2", "Mandrake Linux" },
{ "3", "ALT Linux" },
{ "4", "ASP Linux" } };
for(indx=0; indx < 4; indx++).
gtk_clist_append((GtkCList *) data, dist[indx]);
return;
}
/* Обработчик нажатия кнопки Очистить */
Void button_clear_clicked(gpointer data) {
/* Очищаем список */
gtk_clist_clear((GtkCList *)data);
return;
}
/* Функция прячет/отображает заголовки */
Void button_hide_show_clicked(gpointer data) {
/* 0 = сейчас видим заголовки */
static short int flag = 0;
if (flag == 0) {
/* прячем заголовки */
gtk_clist_column_titles_hide((GtkCList *)data);
flag++;
} else {
/* Отображаем заголовки */
gtk_clist_column_titles_show((GtkCList *)data);
}
return;
}
/* Данная функция будет вызвана, если пользователь выберет.
элемент */
Void selection_made(GtkWidget *clist, gint row,
gint column, GdkEventButton *event, gpointer data) {
gchar *text;
/* Получаем выбранный текст (элемент списка) */
gtk_clist_get_text(GTK_CLIST(clist), row, column, &text);
/* Просто выводим информацию на консоль */
g_print(
"Вы выбрали ряд %d. Колонка %d, текст в ячейке %s\n\n",
row, column, text);
return;
}
Int main(int argc, gchar *argv[]) {
GtkWidget *window;
GtkWidget *vbox, *hbox;
GtkWidget *scrolled_window, *clist;
GtkWidget *button_add, *button_clear, *button_hide_show;
gchar *titles[2] = { "Номер", "Дистрибутив" };
setlocale(LC_ALL, "ru_RU.KOI8-R");
// Нужно вызвать ДО gtk_init().
gtk_init(&argc, &argv);
window=gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_widget_set_usize(GTK_WIDGET(window), 300, 150);
gtk_window_set_title(GTK_WINDOW(window), "Список");
gtk_signal_connect(GTK_OBJECT(window), "destroy",
GTK_SIGNAL_FUNC(gtk_main_quit), NULL);
vbox=gtk_vbox_new(FALSE, 5);
gtk_container_set_border_width(GTK_CONTAINER(vbox), 5);
gtk_container_add(GTK_CONTAINER(window), vbox);
gtk_widget_show(vbox);
/* Создаем окно с полосками прокрутки и упаковываем в.
него список */
scrolled_window = gtk_scrolled_window_new(NULL, NULL);
gtk_scrolled_window_set_policy(
GTK_SCROLLED_WINDOW(scrolled_window), GTK_POLICY_AUTOMATIC,
GTK_РОLIСY_ALWAYS);
gtk_box_pack_start(GTK_BOX(vbox), scrolled_window,
TRUE, TRUE, 0);
gtk_widget_show(scrolled_window);
/* Создаем список с двумя колонками */
clist = gtk_clist_new_with_titles(2, titles);
/* Обработка выделения */
gtk_signal_connect(GTK_OBJECT(clist), "select_row",
GTK_SIGNAL_FUNC(selection_made), NULL);
/* Устанавливаем тень для рамки списка */
gtk_clist_set_shadow_type(GTK_CLIST(clist),
GTK_SHADOW_OUT);
/* Устанавливаем ширину для колонки. Колонки нумеруются.
с 0 */
gtk_clist_set_column_width(GTK_CLIST(clist), 0, 150);
/* Помещаем список в контейнер */
gtk_container_add(GTK_CONTAINER(scrolled_window), clist);
gtk_widget_show(clist);
/* Создаем и размещаем кнопки Добавить список,
Очистить, Спрятать/отобразить заголовок */
hbox = gtk_hbox_new(FALSE, 0);
gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0);
gtk_widget_show(hbox);
button_add = gtk_button_new_with_label("Добавить");
button_clear = gtk_button_new_with_label("Очистить");
button_hide_show = gtk_button_new_with_label("Спрятать/отобразить");
gtk_box_pack_start(GTK_BOX(hbox), button_add,
TRUE, TRUE, 0);
gtk_box_pack_start(GTK_BOX(hbox), button_clear,
TRUE, TRUE, 0);
gtk_box_pack_start(GTK_BOX(hbox), button_hide_show,
TRUE, TRUE, 0);
/* Связываем обработчики */
gtk_signal_connect_object(GTK_OBJECT(button_add),
"clicked",
GTK_SIGNAL_FUNC(button_add_clicked), (gpointer) clist);
gtk_signal_connect_object(GTK_OBJECT(button_clear),
"clicked",
GTK_SIGNAL_FUNC(button_clear_clicked),(gpointer)clist);
gtk_signal_connect_object(GTK_OBJECT(button_hide_show),
"clicked", GTK_SIGNAL_FUNC(button_hide_show_clicked),
(gpointer)clist);
gtk_widget_show(button_add);
gtk_widget_show(button_clear);
gtk_widget_show(button_hide_show);
gtk_widget_show(window);
gtk_main();
return(0);
}
Программа работает так: при нажатии кнопки Добавить создается список, состоящий из названий четырех популярных дистрибутивов Linux. Кнопка Очистить очищает список, а Спрятать/отобразить прячет или отображает заголовки списка. При щелчке на определенной ячейке списка на консоль выводится соответствующее сообщение — координаты ячейки и ее текст.
Рис 23.5.
< … стр. 639–640 … >
Void destroy(GtkWidget *widget, gpointer data);
Static void button_click(GtkWidget *widget, gpointer data);
Int main(int argc, char *argv[]) {
GtkWidget *window;
GtkWidget *button;
GtkWidget *label;
w_ctrl ctrl;
gchar *caption;
setlocale(LC_ALL, "ru_RU.KOI8-R");
caption = g_strdup_printf("Доброго времени суток!");
gtk_init(&argc, &argv);
window = gtk_window_new( GTK_WINDOW_TOPLEVEL);
gtk_signal_connect(GTK_OBJECT(window), "delete_event",
GTK_SIGNAL_FUNC(delete_event), NULL);
gtk_signal_connect(GTK_OBJECT(window), "destroy",
GTK_SIGNAL_FUNC(destroy), &ctrl);
gtk_window_set_title(GTK_WINDOW(window), caption);
gtk_container_set_border_width(GTK_CONTAINER(window), 10);
button = gtk_button_new();
label = gtk_label_new(" -== Нажмите кнопку ==- " );
ctrl.app_window = window;
ctrl.label = label;
gtk_container_add(GTK_CONTAINER(button), label);
gtk_container_add(GTK_CONTAINER(window), button);
gtk_signal_connect(GTK_OBJECT(button), "clicked",
GTK_SIGNAL_FUNC(button_click), &ctrl);
gtk_widget_show_all(window);
gtk_main();
return(0);
}
// ****************************************************
Void quit_confirm(GtkWidget *widget) {
GtkWidget *quit_form;
GtkWidget *label;
GtkWidget *yes_button, *no_button;
quit_form = gtk_dialog_new();
gtk_window_set_position(GTK_WINDOW(quit_form),
GTK_WIN_POS_CENTER);
gtk_container_set_border_width(GTK_CONTAINER(quit_form),
10);
label =
gtk_label_new("\n Вы действительно хотите выйти? \n*);
yes_button = gtk_button_new_with_label("Да");
no_button = gtk_button_new_with_label("Нет");
gtk_signal_connect_object(GTK_OBJECT(yes_button),
"clicked",
GTK_SIGNAL_FUNC(gtk_widget_destroy), (gpointer)widget);
gtk_container_add(
GTK_CONTAINER(GTK_DIALOG(quit_form)->action_area), yes_button);
gtk_signal_connect_object(GTK_OBJECT(no_button),
"clicked", GTK_SIGNAL_FUNC(gtk_widget_destroy),
(gpointer)quit_form);
gtk_container_add(
GTK_CONTAINER(GTK_DIALOG(quit_form)->action_area), no_button);
gtk_container_add(
GTK_CONTAINER(GTK_DIALOG(quit_form)->vbox), label);
gtk_window_set_modal(GTK_WINDOW(quit_form), TRUE);
gtk_widget_show_all(quit_form);
}
Gint delete_event(GtkWidget *widget, GdkEvent *event,
gpointer data) {
quit_confirm(widget);
return(TRUE);
}
Void destroy(GtkWidget *widget, gpointer data) {
printf{"GOOD-BYE!");
gtk_main_quit();
}
Static void button_click(GtkWidget *widget,
gpointer data ) {
static gint i = 0;
GtkWidget *app_window;
GtkWidget *label;
gchar msg[256];
app_window = GTK_WIDGET(((w_ctrl *)data)->app_window);
label = GTK_WIDGET(((w_ctrl*)data)->label);
i++;
sprintf(msg, "Вы нажали кнопку: %d раз(а)", i);
gtk_label_set_text(GTK_LABEL(label), msg);
}
Думаю, текст программы ясен без лишних комментариев. Нужно лишь пояснить один очень важный момент. Обратите внимание на то, что мы переопределили обработчик для события delete_event.
Gtk_signal_connect(GTK_OBJECT(window), "delete_event",
GTK_SIGNAL_FUNC(delete_event), NULL);
Gtk_signal_connect(GTK_OBJECT(window), "destroy",
GTK_SIGNAL_FUNC(destroy), &ctrl);
Если данный обработчик возвращает FALSE, то будет вызвана функция destroy(), которая уничтожит окно. Мы переписали функцию delete_event() так, чтобы она всегда возвращала TRUE, то есть функция destroy() вообще не будет вызвана. Но в таком случае наше окно вообще никогда не закроется, поэтому нужно, чтобы кто-то позаботился о закрытии окна. Это будет функция quit_confirm(), отображающая диалог завершения работы.
Gint delete_event(GtkWidget *widget, GdkEvent *event,
gpointer data) {
quit_confirm(widget);
return(TRUE);
}
Рис. 23.6. Программа только запущена.
Рис. 23.7. Пользователь нажал на кнопку 4 раза.
Рис. 23.8. Диалог завершения работы.
Теперь рассмотрим обработчики событий кнопок Да и Нет диалога:
Gtk_signal_connect_object(GTK_OBJECT(yes_button),
"clicked", GTK_SIGNAL_FUNC(gtk_widget_destroy),
(gpointer)widget);
Gtk_signal_connect_object(GTK_OBJECT(no_button), "clicked",
GTK_SIGNAL_FUNC(gtk_widget_destroy),(gpointer)quit_form);
Кнопка yes_button вызывает функцию gtk_widget_destroy() и передает ей параметр (gpointer)widget, то есть уничтожает главное окно приложения, а кнопка no_button передает функции gtk_widget_destroy() параметр (gpointer)quit_form. который указывает на окно диалога, то есть при нажатии этой кнопки будет закрыто само окно нашего диалога.
23.4.7. Меню.
Меню программы вручную писать довольно неудобно, поэтому для разработки меню воспользуемся студией разработки графического интерфейса Glade.
23.4.8. Иерархия виджитов.
GtkObject.
+GtkWidget.
| +GtkMisc.
| | +GtkLabel.
| | | +GtkAccelLabel.
| | | `GtkTipsQuery.
| | +GtkArrow.
| | +GtkImage.
| | `GtkPixmap.
| +GtkContainer.
| | +GtkBin.
| | | +GtkAlignment.
| | | +GtkFrame.
| | | | `GtkAspectFrame.
| | | +GtkButton.
| | | | +GtkToggleButton.
| | | | | `GtkCheckButton.
| | | | | `GtkRadioButton.
| | | | `GtkOptionMenu.
| | | +GtkItem.
| | | | +GtkMenuItem.
| | | | | +GtkCheckMenuItem.
| | | | | | `GtkRadioMenuItem.
| | | | | `GtkTearoffMenuItem.
| | | | +GtkListItem.
| | | | `GtkTreeItem.
| | | +GtkWindow.
| | | | +GtkColorSelectionDialog.
| | | | +GtkDialog.
| | | | | `GtkInputDialog.
| | | | +GtkDrawWindow.
| | | | +GtkFileSelection.
| | | | +GtkFontSelectionDialog.
| | | | `GtkPlug.
| | | +GtkEventBox.
| | | +GtkHandleBox.
| | | +GtkScrolledWindow.
| | | `GtkViewport.
| | +GtkBox.
| | | +GtkButtonBox.
| | | | +GtkHButtonBox.
| | | | `GtkVButtonBox.
| | | +GtkVBox.
| | | | +GtkColorSelection.
| | | | `GtkGammaCurve.
| | | `GtkHBox.
| | | +GtkCombo.
| | | `GtkStatusbar.
| | +GtkCList.
| | | `GtkCTree.
| | +GtkFixed.
| | +GtkNotebook.
| | | `GtkFontSelection.
| | +GtkPaned.
| | | +GtkHPaned.
| | | `GtkVPaned.
| | +GtkLayout.
| | +GtkList.
| | +GtkMenuShell.
| | | +GtkMenuBar.
| | | `GtkMenu.
| | +GtkPacker.
| | +GtkSocket.
| | +GtkTable.
| | +GtkToolbar.
| | `GtkTree.
| +GtkCalendar.
| +GtkDrawingArea.
| | `GtkCurve.
| +GtkEditable.
| | +GtkEntry.
| | | `GtkSpinButton.
| | `GtkText.
| +GtkRuler.
| | +GtkHRuler.
| | `GtkVRuler.
| +GtkRange.
| | +GtkScale.
| | | +GtkHScale.
| | | `GtkVScale.
| | `GtkScrollbar.
| | +GtkHScrollbar.
| | `GtkVScrollbar.
| +GtkSeparator.
| | +GtkHSeparator.
| | `GtkVSeparator.
| +GtkPreview.
| `GtkProgress.
| `GtkProgressBar.
+GtkData.
| +GtkAdjustment.
| `GtkTooltips.
`GtkItemFactory.
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
- 86.
- 87.
- 88.
- 89.
- 90.
- 91.
- 92.
- 93.
- 94.
- 95.
- 96.
- 97.
- 98.
- 99.
- 100.
- 101.
- 102.
- 103.
- 104.
- 105.
- 106.
- 107.
- 108.
- 109.
- 110.
- 111.
- 112.
- 113.
- 114.
- 115.
- 116.
- 117.
- 118.
- 119.
- 120.
- 121.
- 122.
- 123.
- 124.
- 125.
- 126.
- 127.
- 128.
- 129.
- 130.
- 131.
- 132.
- 133.
- 134.
- 135.
- 136.
- 137.
- 138.
- 139.
- 140.
- 141.
- 142.
- 143.
- 144.
- 145.
- 146.
- 147.
- 148.
- 149.
- 150.
- 151.
- 152.
- 153.
- 154.
- 155.
- 156.
- 157.
- 158.
- 159.
- 160.
- 161.
- 162.
- 163.
- 164.
- 165.
- 166.
- 167.
- 168.
- 169.
- 170.
- 171.
- 172.
- 173.
- 174.
- 175.
- 176.
- 177.
- 178.
- 179.
- 180.
- 181.
- 182.
- 183.
- 184.
- 185.
- 186.
- 187.
- 188.
- 189.
- 190.
- 191.
- 192.
- 193.
- 194.
- 195.
- 196.
- 197.
- 198.
- 199.
- 200.
- 201.
- 202.
- 203.
- 204.
- 205.
- 206.
- 207.
- 208.
- 209.
- 210.
- 211.
- 212.
- 213.
- 214.
- 215.
- 216.
- 217.
- 218.
- 219.
- 220.
- 221.
- 222.
- 223.
- 224.
- 225.
- 226.
- 227.
- 228.
- 229.
- 230.
- 231.
- 232.
- 233.
- 234.
- 235.
- 236.
- 237.
- 238.
- 239.
- 240.
- 241.
- 242.
- 243.
- 244.
- 245.
- 246.
- 247.
- 248.
- 249.
- 250.
- 251.
- 252.
- 253.
- 254.
- 255.
- 256.
- 257.
- 258.
- 259.
- 260.
- 261.
- 262.
- 263.
- 264.
- 265.
- 266.
- 267.
- 268.
- 269.
- 270.
- 271.
- 272.
- 273.
- 274.
- 275.
- 276.
- 277.
- 278.
- 279.
- 280.
- 281.
- 282.
- 283.
- 284.
- 285.
- 286.
- 287.
- 288.
- 289.
- 290.
- 291.
- 292.
- 293.
- 294.
- 295.
- 296.
- 297.
- 298.
- 299.
- 300.
- 301.
- 302.
- 303.
- 304.
- 305.
- 306.
- 307.
- 308.
- 309.
- 310.
- 311.
- 312.
- 313.
- 314.
- 315.
- 316.
- 317.
- 318.
- 319.
- 320.
- 321.
- 322.
- 323.
- 324.
- 325.
- 326.
- 327.
- 328.
- 329.
- 330.
- 331.
- 332.
- 333.
- 334.
- 335.
- 336.
- 337.
- 338.
- 339.
- 340.
- 341.
- 342.
- 343.
- 344.
- 345.
- 346.
- 347.
- 348.
- 349.
- 350.
- 351.
- 352.
- 353.
- 354.
- 355.
- 356.
- 357.
- 358.
- 359.
- 360.
- 361.
- 362.
- 363.
- 364.
- 365.
- 366.
- 367.
- 368.
- 369.
- 370.
- 371.
- 372.
- 373.
- 374.
- 375.
- 376.
- 377.
- 378.
- 379.
- 380.
- 381.
- 382.
- 383.
- 384.
- 385.
- 386.
- 387.
- 388.
- 389.
- 390.
- 391.
- 392.
- 393.
- 394.
- 395.
- 396.
- 397.
- 398.
- 399.
- 400.
- 401.
- 402.
- 403.
- 404.
- 405.
- 406.
- 407.
- 408.
- 409.
- 410.
- 411.
- 412.
- 413.
- 414.
- 415.
- 416.
- 417.
- 418.
- 419.
- 420.
- 421.
- 422.
- 423.
- 424.
- 425.
- 426.
- 427.
- 428.
- 429.
- 430.
- 431.
- 432.
- 433.
- 434.
- 435.
- 436.
- 437.
- 438.
- 439.
- 440.
- 441.
- 442.
- 443.
- 444.
- 445.
- 446.
- 447.
- 448.
- 449.
- 450.
- 451.
- 452.
- 453.
- 454.
- 455.
- 456.
- 457.
- 458.
- 459.
- 460.
- 461.
- 462.
- 463.
- 464.
- 465.
- 466.
- 467.
- 468.
- 469.
- 470.
- 471.