Версия для печати

Архив документации на OpenNet.ru / Раздел "Программирование, языки" (Многостраничная версия)
Разработка приложений с помощью Gtk+/Gnome
Перевод с английского С.В. Черникова
Оригинал документа находится на сайте Linux Land.


1999 - 2000




next up previous
Вперед: 1. Введение Назад: ggad-rus

I. Обзор



Подсекции

Linux Land
2000-09-15

next up previous contents
Вперед: I. Обзор

Havoc Pennington

Разработка приложений с помощью Gtk+/Gnome
Перевод с английского С.В. Черникова


1999 - 2000




Linux Land
2000-09-15

next up previous contents
Вперед: 1.1 Что такое Gnome? Назад: I. Обзор

1. Введение

Эта глава дает вам представление о технологиях, описанных в данной книге.



Подсекции

Linux Land
2000-09-15

next up previous contents
Вперед: 2.1 Основы Назад: 1.3 Структура книги

2. glib: переносимость и полезность

glib -- это переносимая и служебная библиотека для UNIX-подобных систем и Windows. Эта глава охватывает некоторые ее наиболее часто используемые возможности в приложениях Gtk+ и Gnome. glib проста, ее концепции хорошо знакомы, поэтому мы будем двигаться быстро. Для более подробного описания glib загляните в glib.h или свободно распространяемое справочное руководство, которое поставляется вместе с библиотекой. (Кстати, не бойтесь использовать заголовочные файлы glib, Gtk+ или Gnome -- они ясны и их легко читать, также хорошо подходят в качестве быстрой справки. По этой же причине не бойтесь заглядывать в исходные тексты, если у вас очень специфичные вопросы по реализации.)

Предполагалось, что разнообразные возможности glib имеют единый интерфейс; стиль кодирования полу-объектно-ориентированный, и идентификаторы имеют префикс g для создания подобия пространства имен.

glib имеет единственный заголовочный файл -- glib.h



Подсекции

Linux Land
2000-09-15

next up previous contents
Вперед: 3.1 Галопом по Gtk+ Назад: 2.3 Другие особенности

3. Основы Gtk+

В этой главе описывается стандартный Hello, World, чтобы дать вам обзор возможностей Gtk+, затем идет продвижение в обсуждении некоторых важных деталей, которые вам необходимы для того, чтобы начать разработку приложений Gtk+.

Если вы уже прочитали Учебник по Gtk+ на http://www.gtk.org, или книгу Разработка приложений Linux с помощью Gtk+ и Gdk (также изданную в New Riders), вы, может быть, захотите пропустить или прочитать бегло эту главу. Если вы до этого не использовали Gtk+, эта глава пронесется мимо очень быстро; читайте внимательно.



Подсекции

Linux Land
2000-09-15

next up previous contents
Вперед: 2.1.1 Определения типов Назад: 2. glib: переносимость и

2.1 Основы

glib предоставляет замены для многих стандартных и часто используемых конструкций на C. Этот раздел описывает основные определения типов в glib, макросы, процедуры распределения памяти и функции манипулирования строками.



Подсекции

Linux Land
2000-09-15

next up previous contents
Вперед: 2. glib: переносимость и Назад: 1.2.4 Слово о заголовочных

1.3 Структура книги

Эта книга разделена на несколько частей:

Эта книга подразумевает некоторое знание программирования с помощью Gtk+; Главы 2 и 3 дадут вам ускоренный обзор, если ваши знания минимальны. Но изложение будет интенсивным. Большинство специальных виджетов в Gtk+ останутся вне обзора. Эта книга предназначена для ознакомления с программированием с помощью Gnome и некоторых тем Gtk+ для опытных программистов; она является дополнением к книге по Gtk+.

Из-за недостатка времени и объема книги, будут охвачены только основные библиотеки Gnome; в частности, CORBA, печать, XML, модули и скрипты окажутся неохваченными. Большинство крупных приложений будут использовать эти возможности в дополнение к основным библиотекам.


Linux Land
2000-09-15

next up previous contents
Вперед: 2.1.2 Часто используемые макросы Назад: 2.1 Основы

2.1.1 Определения типов

Вместо использования стандартных типов C (int, long и т.д.) glib определяет свои собственные. Это сделано по многим причинам. Например, тип gint32 гарантированно имеет размер 32 бита, чего не может дать любой стандартный тип C. guint просто легче набирать, чем unsigned. Некоторые определения типов существуют просто для целостности; например, gchar всегда эквивалентен стандартному char.

В glib определены следующие примитивные типы:


Linux Land
2000-09-15

next up previous contents
Вперед: 2.1.3 Макросы для отладки Назад: 2.1.1 Определения типов

2.1.2 Часто используемые макросы

glib определяет некоторое количество знакомых по программированию на C макросов; они показаны в списке макросов 2..1. Все они должны быть самодокументирующимися. TRUE/FALSE/NULL  -- это обычные 1/0/((void *) 0); MIN()/MAX() возвращают наименьший или наибольший из своих аргументов. ABS() возвращает абсолютное значение своего аргумента. CLAMP(x, low, high) возвращает x, если x лежит в интервале [low, high]; если x меньше low, то возвращается low; если больше high, то high.

Список макросов 2..1: Знакомые по C макросы
"#include "<glib.h>
TRUE
FALSE
NULL
MAX(a, b)
MIN(a, b)
ABS(x)
CLAMP(x, low, high)

В glib существует также много уникальных макросов, такие как переносимые преобразования gpointer-в-gint и gpointer-в-guint, которые приведены в списке макросов 2..2.

Большинство структур данных glib спроектированы так, чтобы хранить gpointer. Если вы хотите хранить указатели на динамически распределяемые объекты, это правильный выбор. Однако, иногда вы захотите хранить простой список целых чисел, без динамического его распределения. Хотя стандарт C это строго и не гарантирует, возможно хранить gint или guint в переменной gpointer на большом количестве платформ, куда спортирована glib; в некоторых случаях требуется промежуточное приведение типов. Макросы в списке макросов 2..2 делают ненужным приведение типов.

Вот пример:

gint my_int;
gpointer my_pointer;

my_int = 5;
my_pointer = GINT_TO_POINTER(my_int);
printf("We are storing %d\n", GPOINTER_TO_INT(my_pointer));

Однако, будьте осторожны: эти макросы позволяют вам хранить целое число в указателе, но хранение указателя в целом числе не будет работать. Чтобы сделать это переносимо, вы должны хранить указатель в типе long. (Однако, это несомненно является плохой идеей.)

Список макросов 2..2: Макросы для хранения целых чисел в указателях
"#include "<glib.h>
GINT_TO_POINTER(p)
GPOINTER_TO_INT(p)
GUINT_TO_POINTER(p)
GPOINTER_TO_UINT(p)


Linux Land
2000-09-15

next up previous contents
Вперед: 2.1.4 Память Назад: 2.1.2 Часто используемые макросы

2.1.3 Макросы для отладки

glib имеет хороший набор макросов, которые вы можете использовать для увеличения инвариантов и предусловий в вашем коде. Они исчезнут после того, как вы определите "G_DISABLE_CHECKS" или "G_DISABLE_ASSERT", поэтому уменьшения производительности в реальном коде не будет. Рекомендуется широко использовать это. Тогда вы сможете находить ошибки намного быстрее. Вы можете даже добавлять условия и проверки всякий раз, когда находите ошибку, чтобы быть уверенными, что ошибка не появится в будущих версиях -- это дополняет регрессивный набор. Проверки особенно полезны, когда код, который вы пишете будет использоваться как черный ящик другими программистами; пользователи будут сразу же знать когда и как они неправильно использовали ваш код.

Естественно, вы должны быть уверены, что ваш код функционирует правильно не только в режиме отладки. Операторы, которые присутствуют в финальном коде никогда не должны иметь побочных эффектов.

Список макросов 2..3: Проверки предусловий
"#include "<glib.h>
g_return_if_fail(condition)
g_return_val_if_fail(condition, retval)

Список макросов 2..3 показывает проверки предусловий glib. "g_return_if_fail()" выводит предупреждение и выходит из текущей функции если условие condition ложно. "g_return_val_if_fail()" действует аналогично, но позволяет вам вернуть retval. Эти макросы невероятно полезны -- если вы их широко используете, особенно в комбинации с проверками Gtk+ периода исполнения, вы съэкономите время при нахождении плохих указателей или ошибок типов.

Использовать эти функции легко, ниже идет пример из реализации хэш-таблицы в glib:

void g_hash_table_foreach(GHashTable *hash_table, GHFunc func,
                          gpointer user_data)
{
  GHashNode *node;
  gint i;
  
  g_return_if_fail(hash_table != NULL);
  g_return_if_fail(func != NULL);
  
  for (i=0; i<hash_table->size; i++)
    for (node=hash_table->nodes[i]; node; node=node->next)
      (* func) (node->key, node->value, user_data);
}
Без проверок, передача NULL в качестве параметра в эту функцию вызовет загадочный аварийный выход. Человек, использующий библиотеку узнает, где случилась ошибка с помощью отладчика, и, возможно, посмотрит в код glib чтобы узнать, что было неправильно. С проверками, они получат красивое сообщение об ошибке, говорящее, что аргументы со значением NULL недопустимы.

Список макросов 2..4: Условия
"#include "<glib.h>
g_assert(condition)
g_assert_not_reached()

В glib также есть традиционные макросы условий, показанные в списке макросов 2..4. "g_assert()" в основном аналогичен "assert()", но реагирует на "G_DISABLE_ASSERT" и ведет себя одинаково на всех платформах. Есть также и "g_assert_not_reached()"; это условие, которое всегда ложно. Проверки условий вызывают "abort()" для выхода из программы и (если ваша среда это позволяет) записывает дамп памяти для отладочных целей.

Фатальные условия должны использоваться для проверки внутренней целостности функции или библиотеки, в то время как "g_return_if_fail()" предназначен для того, чтобы гарантировать приемлемые значения, которые передаются общим интерфейсам программного модуля. То есть, если условие не выполнится, вы, скорее всего будете искать ошибку в модуле, содержащем условие; если проверка "g_return_if_fail()" не пройдет, вы, скорее всего, будете искать ошибку в коде, который вызывает модуль.

Этот код, взятый из календарных вычислений glib показывает разницу:

GDate *g_date_new_dmy(GDateDay day, GDateMonth m, GDateYear y)
{
  GDate *d;
  g_return_val_if_fail(g_date_valid_dmy(day, m, y), NULL);
  
  d = g_new(GDate, 1);
  
  d->julian = FALSE;
  d->dmy    = TRUE;
  
  d->month = m;
  d->day   = day;
  d->year  = y;
  
  g_assert(g_date_valid(d));
  
  return d;
}

Проверка предусловия в начале гарантирует то, что пользователь передал осмысленные аргументы для числа, месяца и года; условие в конце дает гарантию того, что glib создал приемлемый объект с данными нормальными значениями.

"g_assert_not_reached()" должен быть использован для отметки невозможных ситуаций; обычное использование -- обнаружение операторов в блоке switch, которые не обрабатывают все возможные значения перечисления:

switch (val)
{
  case FOO_ONE:
    break;
  case FOO_TWO:
    break;
  default:
" /* Неправильное значение перечисления */"
    g_assert_not_reached();
    break;
}

Все отладочные макросы печатают предупреждения, используя "g_log()" из glib, что означает, что предупреждение включает имя порождающей его программы или библиотеки, и вы можете дополнительно установить замену для процедуры печати предупреждений. Например, вы можете посылать все предупреждения в диалоговое окно или лог-файл, вместо печати их на консоль.


Linux Land
2000-09-15

next up previous contents
Вперед: 2.1.5 Обработка строк Назад: 2.1.3 Макросы для отладки

2.1.4 Память

glib обертывает стандартные "malloc()" и "free()" своими "g_"-вариантами: "g_malloc()" и "g_free()", показанными в списке функций 2..1. Они привлекательны по нескольким параметрам:

В дополнение к этим небольшим удобствам, "g_malloc()" и "g_free()" могут поддерживать различные типы отладки и профилировки использования памяти. Если вы укажете параметр "-enable-mem-check" для скрипта configure библиотеки glib, скомпилированный "g_free()" будет вас предупреждать всякий раз, когда вы освобождаете один и тот же указатель дважды. Опция "-enable-mem-profile" разрешает код, которые ведет статистику использования памяти; когда вы вызываете "g_mem_profile()", она печатается на консоль. Наконец, вы можете определить "USE_DMALLOC", и тогда функции для работы с памятью из glib будут использовать MALLOC() и другие макросы для отладки, доступные в dmalloc.h на некоторых платформах.

Список функций 2..1: Распределение памяти в glib
"#include "<glib.h>
gpointer g_malloc(gulong size)
void g_free(gpointer mem)
gpointer g_realloc(gpointer mem, gulong size)
gpointer g_memdup(gconstpointer mem, guint bytesize)

Очень важно сопоставить "g_malloc()"'у "g_free()", а простому "malloc()"'у -- "free()", и (если вы используете C++) вызову new -- вызов delete. Иначе может случиться страшное, так как эти функции могут использовать различные пулы памяти (а new/delete вызывают к тому же конструкторы и деструкторы).

Конечно, есть "g_realloc()", эквивалентный "realloc()". Есть также удобный "g_malloc0()", который заполняет распределенную память нулями, и "g_memdup()", который возвращает копию размером bytesize байт, начинающуюся в mem. "g_realloc()" и "g_malloc0()" -- оба понимают size равный 0 для совместимости с "g_malloc()". Однако "g_memdup()" -- нет.

Если это неочевидно: "g_malloc0()" заполняет исходную область памяти неустановленными битами, а не значением 0, независимо от типа. Некоторые ожидают получить массив чисел с плавающей точкой, проинициализированный значением 0.0; это не будет работать.

Наконец, есть макросы распределения памяти, знающие о типах, показанные в списке макросов 2..5. Аргумент type каждого из них -- это имя типа, а аргумент count -- число блоков указанного типа, которые надо распределить. Эти макросы сэкономят вам некоторое количество нажатий клавиш и умножений, и поэтому менее склонны к ошибкам. Они автоматически преобразуют к результирующему типу, поэтому попытка присвоения рапределенной памяти неправильному типу указателя, вызовет предупреждение компилятора. (Если вы включили предупреждения, как должны делать ответственные программисты!)

Список макросов 2..5: Макросы для распределения памяти
"#include "<glib.h>
g_new(type, count)
g_new0(type, count)
g_renew(type, mem, count)


Linux Land
2000-09-15

next up previous contents
Вперед: 2.2 Структуры данных Назад: 2.1.4 Память

2.1.5 Обработка строк

glib предоставляет некоторое количество функций для обработки строк; некоторые из них встречаются только в glib, другие решают проблемы совместимости. Все они отлично взаимодействуют с процедурами распределения памяти glib.

Для тех, кто заинтересован в лучшем типе для строки, чем gchar *, есть тип GString. Он не рассматривается в этой книге, но документация по нему доступна на http://www.gtk.org/.

Список функций 2..2: Функции для совместимости
"#include "<glib.h>
gint g_snprintf(gchar *buf,
                gulong n,
                const gchar *format,
                ...)
gint g_strcasecmp(const gchar *s1, const gchar *s2)
gint g_strncasecmp(const gchar *s1, const gchar *s2, guint n)

Список функций 2..2 показывает некоторые замены, которые glib предоставляет для широко реализованных, но непортируемых расширений к ANSI C.

Одна из раздражающих вещей в C -- это то, что он предоставляет возможности для сбоев и дыр в безопасности, обычно через пагубный "sprintf()", в то время как относительно безопасный и широко реализованный "snprintf()" является расширением стандарта. "g_snprintf()" оборачивает стандартный "snprintf()" на платформах его имеющих, и предоставляет его реализацию на платформах его не имеющих. Итак, вы можете сказать прощай "sprintf()"'у навсегда. Даже лучше: классический "snprintf()" не гарантирует, что он завершит NULL'ом заполняемый им буфер, "g_snprintf()" -- гарантирует.

"strcasecmp()" и "g_strncasecmp" производят регистронезависимое сравнение двух строк, опционально с заданием максимальной длины. "strcasecmp()" доступна на многих платформах, но не универсальна, поэтому рекомендуется использовать вместо нее функции из glib.

Функции в списке функций 2..3 изменяют саму строку: первые две преобразуют строку к нижнему или верхнему регистру соответственно, тогда как "g_strreverse()" переставляет символы в строке задом-наперед. "g_strchug()" и "g_strchomp()" удаляют лидирующие и завершающие пробелы. Эти последние две функции возвращают строку в дополнение к модификации ее самой; в некоторых случаях может быть удобно воспользоваться возвращаемым значением. Есть макрос "g_strstrip()", который объединяет обе функции для удаления и лидирующих, и завершающих пробелов; он используется так же, как и отдельные функции.

Список функций 2..3: Изменения в самих строках
"#include "<glib.h>
void g_strdown(gchar *string)
void g_strdup(gchar *string)
void g_strreverse(gchar *string)
gchar *g_strchug(gchar *string)
gchar *g_strchomp(gchar *string)

Список функций 2..4 показывает чуть больше полу-стандартных функций, которые оборачивает glib. "g_strtod()", как и "strtod()", преобразовывает строку nptr к числу с плавающей точкой с двойной точностью, единственным исключением -- она также попытается преобразовать строку в локали "C", если не смогла преобразовать строку в локали по умолчанию. *endptr устанавливается на первый непреобразованный символ, то есть на любой текст после числа. Если преобразование не выполнилось, *endptr указывает на nptr. endptr может принимать значение NULL с дальнейшим игнорированием.

"g_strerror()" и "g_strsignal()" аналогичны своим не-"g_" эквивалентым, но портируемы. (Они возвращают строковое представление errno или номера сигнала.)

Список функций 2..4: Преобразование строк
"#include "<glib.h>
gdouble g_strtod(const gchar *nptr, gchar **endptr)
gchar *g_strerror(gint errnum)
gchar *g_strsignal(gint signum)

Список функций 2..5 показывает богатый набор функций для выделения памяти для строк. Как ни странно, "g_strdup()" и "g_strndup()" делают распределенную копию str или первых n ее символов. Для согласованности с функциями glib выделения памяти, они возвращают NULL, если им передать указатель на NULL. Варианты "printf()" возвращают форматированную строку. "g_strescape()" обрамляет любые символы 3#3 в аргументе вставкой еще одного 3#3 перед ними, возвращая обрамленную строку. "g_strnfill()" возвращает строку размером length, заполненную "fill_char".

"g_strdup_printf()" заслуживает особого упоминания: это более простой способ обработать нижеследующий часто встречающийся кусок кода:

gchar* str = g_malloc(256);
g_snprintf(str, 256, "%d printf-style %s", 1, "format");
Вместо этого вы можете сказать вот так, и избежать необходимости точного определения размера буфера:
gchar* str = g_strdup_printf("%d printf-style %s", 1, "format");
Список функций 2..5: Выделение памяти для строк
"#include "<glib.h>
gchar *g_strdup(const gchar *str)
gchar *g_strndup(const gchar *format, guint n)
gchar *g_strdup_printf(const gchar *format, ...)
gchar *g_strdup_vprintf(const gchar *format, va_list args)
gchar *g_strescape(gchar *string)
gchar *g_strnfill(guint length, gchar *fill_char)

glib предоставляет некоторые удобные функции для объединения строк, показанные в списке функций 2..6. "g_strconcat()" возвращает свежевыделенную строку, созданную объединением всех строк в списке аргументов. Последний аргумент должен быть NULL, чтобы "g_strconcat()" знала, когда остановиться. "g_strjoin()" действует аналогично, только вставляет separator между объединяемыми строками. Если separator равен NULL, он не будет использоваться.

Список функций 2..6: Объединение строк
"#include "<glib.h>
gchar *g_strconcat(const gchar *string1, ...)
gchar *g_strjoin(const gchar *separator, ...)

Наконец, список функций 2..7 объединяет в себе несколько процедур, которые манипулируют с массивами строк, завершенными NULL. "g_strsplit()" разбивает строку string в каждом из разделителей, описанных в delimiter, возвращая свежевыделенный массив. "g_strjoinv()" объединяет каждую строку в массиве, возвращая свежевыделенную строку. Если задан separator, то он вставляется между строками. "g_strfreev()" освобождает каждую строку в массиве, а затем и сам массив.

Список функций 2..7: Объединение строк
"#include "<glib.h>
gchar **g_strsplit(const gchar *string,
                   const gchar *delimiter,
                   gint max_tokens)
gchar *g_strjoinv(const gchar *separator, gchar **str_array)
void g_strfreev(gchar **str_array)


Linux Land
2000-09-15

next up previous contents
Вперед: 2.2.1 Списки Назад: 2.1.5 Обработка строк

2.2 Структуры данных

В glib реализовано много общих структур данных, поэтому вам не надо изобретать колесо каждый раз, когда вы хотите связный список. Этот раздел описывает реализацию связных списков, сортированных двоичных деревьев, N-связных деревьев и хэш-таблиц в glib.



Подсекции

Linux Land
2000-09-15

next up previous contents
Вперед: 2.2.2 Деревья Назад: 2.2 Структуры данных

2.2.1 Списки

glib предоставляет универсальные одно- и двусвязные списки, соответственно GSList и GList. Они реализованы как списки, содержащие gpointer; вы можете использовать их, чтобы хранить целые числа с помощью макросов "GINT_TO_POINTER" и "GPOINTER_TO_INT". GSList и GList имеют идентичный API за исключением того, что есть функция "g_list_previous()", но нет "g_slist_previous()". В этом разделе будет обсуждаться GSList, но все это применимо и к двусвязным спискам.

В реализации glib пустой список -- это просто указатель равный NULL. Всегда можно передавать NULL функциям, так как это допустимый список, имеющий нулевую длину. Код, создающий список и добавляющий к нему один элемент, должен выглядеть примерно так:

GSList *list = NULL;
gchar *element = g_strdup("a string");
list = g_slist_append(list, element);

Списки в glib реализованы под заметным влиянием Lisp'а; пустой список равен специальному значению "nil" по этой причине. "g_slist_prepend()" работает почти как cons -- это постоянная по времени операция, которая добавляет новую ячейку в начало списка.

Обратите внимание, вы должны заменить список, переданный в функции, изменяющие список, на их возвращаемое значение на случай, если голова списка изменится. glib сам справится с проблемами памяти, освобождая или выделяя связи списка по необходимости.

Например, следующий код удалит элемент, добавленный в примере выше, и, таким образом, очистит список:

list = g_slist_remove(list, element);

list не равен NULL. Вы также, естественно, сами должны освободить element. Для очистки всего списка воспользуйтесь "g_slist_free()", который удалит все связи за раз. "g_slist_free()" не имеет возвращаемого значения, потому что оно всегда будет NULL, и вы можете просто присвоить это значение своему списку, если захотите. Очевидно, функция "g_slist_free()" освобождает только ячейки списка; она ничего не знает про то, что делать с содержимым списка.

Для доступа к элементу списка, ссылайтесь на структуру GSList напрямую:

gchar *my_data = list->data;

Для хождения по списку, можно писать примерно следующий код:

GSList *tmp = list;
while (tmp != NULL)
{
  printf("List data: %p\n", tmp->data);
  tmp = g_slist_next(tmp);
}

Список функций 2..8 показывает базовые функции для изменения содержимого GSList. Вы должны присваивать своей переменной-списку возвращаемое значение всех этих функций на случай, если голова списка изменится. Учтите, что glib не хранит указатель на хвост списка, поэтому добавление в начало -- это постоянная по времени операция, тогда как время выполнения добавления в конец, вставки и удаления пропорционально длине списка.

В частности, это означает, что создание списка с использованием "g_slist_append()" -- ужасная идея; используйте "g_slist_prepend()" и затем вызывайте "g_slist_reverse()", если вам важно расположение элементов. Если вы собираетесь часто добавлять элементы к концу списка, вы можете хранить указатель на последний элемент. Следующий код может быть использован для организации эффективного добавления в конец списка:

void efficient_append(GSList **list, GSList **list_end,
                      gpointer data)
{
  g_return_if_fail(list != NULL);
  g_return_if_fail(list_end != NULL);

  if (*list == NULL)
  {
    g_assert(*list_end == NULL);
    
    *list = g_slist_append(*list, data);
    *list_end = *list;
  }
  else
    *list_end = g_slist_append(*list_end, data)->next;
}

Чтобы воспользоваться этой функцией, храните список и его хвост где-нибудь, и передавайте их адреса в "efficient_append()":

GSList *list = NULL;
GSList *list_end = NULL;

efficient_append(&list, &list_end, g_strdup("Foo"));
efficient_append(&list, &list_end, g_strdup("Bar"));
efficient_append(&list, &list_end, g_strdup("Baz"));

Конечно, вы должны осторожно пользоваться списковыми функциями, которые могут изменить хвост списка без изменения "list_end".

Список функций 2..8: Изменения содержимого связного списка
"#include "<glib.h>
GSList *g_slist_append(GSList *list, gpointer data)
GSList *g_slist_prepend(GSList *list, gpointer data)
GSList *g_slist_insert(GSList *list, gpointer data, gint position)
GSList *g_slist_remove(GSList *list, gpointer data)

Для доступа к элементам списка в списке функций 2..9 приведены соответствующие функции. Ни одна из них не меняет структуру списка. "g_slist_foreach()" применяет GFunc к каждому элементу списка. GFunc определена следующим образом:

typedef void (*GFunc) (gpointer data, gpointer user_data);
При использовании из "g_slist_foreach()", ваша GFunc будет вызвана для каждого "list->data" в списке list, с передачей ваших данных "user_data". "g_slist_foreach()" сравнима с функцией "map" из Scheme.

Например, вы имеете список строк, и, возможно, хотите создать параллельный список из трансформированных некоторым образом строк из первого списка. Вот код, использующий "efficient_append()" из предыдущего примера:

typedef struct _AppendContext AppendContext;
struct _AppendContext
{
  GSList *list;
  GSList *list_end;
  const gchar *append;
};

static void append_foreach(gpointer data, gpointer user_data)
{
  AppendContext *ac = (AppendContext *) user_data;
  gchar *oldstring = (gchar *) data;
  
  efficient_append(&ac_list, &ac->list_end,
                   g_strconcat(oldstring, ac->append, NULL));
}

GSList *copy_with_append(GSList *list_of_strings, const gchar *append)
{
  AppendContext ac;
  
  ac.list = NULL;
  ac.list_end = NULL;
  ac.append = append;
  
  g_slist_foreach(list_of_strings, append_foreach, &ac);
  
  return ac.list;
}

glib и Gtk+ широко используют идиому указатель на функцию и данные пользователя. Если у вас есть опыт функционального программирования, это наподобие использования 4#4-выражений для создания клаузы. (Клауза сочетает функцию со средой -- набором пар имя-значение. В этом случае средой являются данные пользователя, которые вы передаете в "append_foreach()", а клаузой -- комбинация из указателя на функцию и данных пользователя.)

Список функций 2..9: Доступ к данным в связном списке
"#include "<glib.h>
GSList *g_slist_find(GSList *list, gpointer data)
GSList *g_slist_nth(GSList *list, guint n)
gpointer g_slist_nth_data(GSList *list, guint n)
GSList *g_slist_last(GSList *list)
gint g_slist_index(GSList *list, gpointer data)
void g_slist_foreach(GSList *list, GFunc func, gpointer user_data)

Существует также несколько удобных процедур для манипулирования списком, приведенные в списке функций 2..10. За исключением "g_slist_copy()", все эти функции действуют на списки на месте. Это означает, что вы должны присвоить возвращенное значение и забыть о переданном указателе, как вы делаете когда добавляете или удаляете элементы в/из списка. "g_slist_copy()" возвращает уже выделенный в памяти список, поэтому вы можете продолжать пользоваться обоими списками, и в конце концов должны удалить оба списка.

Список функций 2..10: Манипулирование связным списком
"#include "<glib.h>
guint g_slist_length(GSList *list)
GSList *g_slist_concat(GSList *list1, GSList *list2)
GSList *g_slist_reverse(GSList *list)
GSList *g_slist_copy(GSList *list)

И, наконец, существуют некоторые возможности для сортировки списков, показанные в списке функций 2..11. Чтобы воспользоваться ими, вы должны написать GCompareFunc, которая похожа на функцию сравнения в стандартном qsort(). С использованием типов glib она становится такой:

typedef gint (*GCompareFunc) (gconstpointer a, gconstpointer b);

Если "a < b", функция должна возвратить отрицательное значение; если "a > b" -- положительное; если "a == b" -- должна возвратить 0.

Как только у вас появилась функция сравнения, вы можете вставлять элемент в уже отсортированный список, или сортировать список целиком. Списки сортируются в порядке возрастания. Вы даже можете переработать вашу GCompareFunc, чтобы находить элементы списка, используя "g_slist_find_custom()". (Предупреждение: GCompareFunc используется несогласованно в glib; иногда glib ожидает предикат равенства вместо функции в "qsort()"-стиле. Однако, использование согласованно внутри спискового API.)

Будьте осторожны с сортированными списками; неправильное их использование может стать очень неэффективным. Например, "g_slist_insert_sorted()" -- это O(n)-операция, но если вы ее используете в цикле для вставки большого количества элементов, цикл замедляется экспоненциально. Лучше добавить все ваши элементы в начало списка, а затем вызвать "g_slist_sort()".

Список функций 2..11: Сортированные списки
"#include "<glib.h>
GSList *g_slist_insert_sorted(GSList *list, gpointer data,
                              GCompareFunc func)
GSList *g_slist_sort(GSList *list, GCompareFunc func)
GSList *g_slist_find_custom(GSList *list, gpointer data,
                            GCompareFunc func)


Linux Land
2000-09-15

next up previous contents
Вперед: GTree Назад: 2.2.1 Списки

2.2.2 Деревья

В glib существуют два разных типа деревьев; GTree -- это основное сбалансированное двоичное дерево, подходящее для хранения пар ключ-значение, отсортированных по ключу; GNode хранит произвольные структурированные в дерево данные, такие как дерево синтаксического разбора или таксономию.



Подсекции

Linux Land
2000-09-15

next up previous contents
Вперед: GNode Назад: 2.2.2 Деревья

GTree

Для создания или удаления GTree, используйте пару конструктор-деструктор, приведенную в списке функций 2..12. GCompareFunc аналогична функции сравнения "qsort()", описанной для GSList; в данном случае она используется для сравнения ключей в дереве.

Список функций 2..12: Создание и удаление сбалансированных двоичных деревьев
"#include "<glib.h>
GTree *g_tree_new(GCompareFunc key_compare_func)
void g_tree_destroy(GTree *tree)

Функции для манипулирования содержимым дерева показаны в списке функций 2..13. Все очень прямолинейно; "g_tree_insert()" перезаписывает любое существующее значение, поэтому будьте осторожны, если существующее значение является указателем на область выделенной памяти. Если "g_tree_lookup()" не может найти ключ, она возвращает NULL, иначе она возвращает связанное с ключом значение. И ключи, и значения имеют тип gpointer, но макросы "GPOINTER_TO_INT()" и "GPOINTER_TO_UINT()" позволяют вам использовать целые числа.

Список функций 2..13: Манипулирование содержимым GTree
"#include "<glib.h>
void g_tree_insert(GTree *tree, gpointer key,
                   gpointer value)
void g_tree_remove(GTree *tree, gpointer key)
gpointer g_tree_lookup(GTree *tree, gpointer key)

Существуют две функции, показанные в списке функций 2..14, которые дают вам информацию, насколько большим является дерево.

Список функций 2..14: Определение размера GTree
"#include "<glib.h>
gint g_tree_nnodes(GTree *tree)
gint g_tree_height(GTree *tree)

Используя "g_tree_traverse()" (список функций 2..15), вы можете проходить все дерево. Для ее использования вы должны обеспечить GTraverseFunc, которой передается каждая пара ключ-значение и аргумент data, который вы передаете в "g_tree_traverse()". Прохождение дерева продолжается до тех пор, пока GTraverseFunc возвращает FALSE; если она возвращает TRUE, прохождение прекращается. Вы можете использовать это для поиска значения в дереве. Вот определение GTraverseFunc:

typedef gint (*GTraverseFunc) (gpointer key, gpointer value,
                               gpointer data);

GTraverseType - это перечисление; возможны 4 значения. Вот их смысл применительно к GTree.

Список функций 2..15: Прохождение GTree
"#include "<glib.h>
void g_tree_traverse(GTree *tree, GTraverseFunc traverse_func,
                     GTraverseType traverse_type, gpointer data)


Linux Land
2000-09-15

next up previous contents
Вперед: 2.2.3 Хэш-таблицы Назад: GTree

GNode

GNode - это N-направленное дерево, реализованное как двусвязный список с родителем и списком детей. Поэтому, большинство операций со списками имеют аналоги в API GNode. Вы также можете проходить дерево различными способами. Вот объявление узла:

typedef struct _GNode GNode;

struct _GNode
{
  gpointer data;
  GNode   *next;
  GNode   *prev;
  GNode   *parent;
  GNode   *children;
};
Существуют макросы для доступа к членам GNode, показанные в списке макросов 2..6. Как и с GList, член data предполагается использовать напрямую. Эти макросы возвращают члены next, prev и children соответственно; они также проверяют, что аргумент равен NULL перед удалением ссылки на него, и возвращают NULL, если равен.
Список макросов 2..6: Доступ к членам GNode
"#include "<glib.h>
g_node_prev_sibling(node)
g_node_next_sibling(node)
g_node_first_child(node)

Для создания узла предоставляется обычная функция "_new()" (список функций 2..16). "g_node_new()" создает узел без детей и родителей, содержащий data. Обычно, "g_node_new()" используется только для создания корневого узла; есть удобные макросы, которые автоматически создают новые узлы при необходимости.

Список функций 2..16: Создание GNode
"#include "<glib.h>
GNode *g_node_new(gpointer data)

Для построения дерева используются операции, приведенные в списке функций 2..17. Каждая операция возвращает только что добавленный узел, для удобства при написании циклов или прохождении дерева. В отличии от GList, возвращаемое значение можно безопасно проигнорировать.

Список функций 2..17: Построение дерева GNode
"#include "<glib.h>
GNode *g_node_insert(GNode *parent, gint position,
                     GNode *node)
GNode *g_node_insert_before(GNode *parent, GNode *sibling,
                            GNode *node)
GNode *g_node_prepend(GNode *parent, GNode *node)

Удобные макросы, показанные в списке макросов 2..7 реализованы в терминах фундаментальных операций. "g_node_append()" аналогичен "g_node_prepend()"; остальные берут аргумент data, автоматически выделяя для него узел, и вызывают соответствующую базовую операцию.

Список макросов 2..7: Построение GNode
"#include "<glib.h>
g_node_append(parent, node)
g_node_insert_data(parent, position, data)
g_node_insert_data_before(parent, sibling, data)
g_node_prepend_data(parent, data)
g_node_append_data(parent, data)

Для удаления узла из дерева, существуют две функции, показанные в списке функций 2..18. "g_node_destroy()" удаляет узел из дерева, освобождая его и всех его детей. "g_node_unlink()" удаляет узел и превращает его в корневой узел, то есть преобразует поддерево в независимое дерево.

Список функций 2..18: Удаление GNode
"#include "<glib.h>
void g_node_destroy(GNode *root)
void g_node_unlink(GNode *node)

Есть два макроса для определения вершины и низа дерева GNode, показанные в списке макросов 2..8. Корневой узел определен как узел без родителя и одноуровневых элементов. Листовой узел не имеет детей.

Список макросов 2..8: Предикаты для GNode
"#include "<glib.h>
G_NODE_IS_ROOT(node)
G_NODE_IS_LEAF(node)

Вы можете попросить у glib нужную информацию о GNode, включая число узлов, которое оно содержит, его корневой узел, его глубину и узел, содержащий определенный указатель на данные. Эти функции приведены в списке функций 2..19.

GTraverseType был введен ранее, по отношению к GTree; вот возможные значения для GNode:

Функции для обхода дерева GNode имеют аргумент GTraverseFlags. Это битовое поле, используемое для изменения природы обхода. В настоящее время существуют только три флага -- вы можете обходить только листовые узлы, только нелистовые узлы, или все узлы:

Список функций 2..19: Свойства GNode
"#include "<glib.h>
guint g_node_n_nodes(GNode *root, GTraverseFlags flags)
GNode *g_node_get_root(GNode *node)
gboolean g_node_is_ancestor(GNode *node, GNode *descendant)
guint g_node_depth(GNode *node)
GNode *g_node_find(GNode *root, GTraverseType order,
                   GTraverseFlags flags, gpointer data)

Остальные функции GNode прямолинейны; большинство из них просто являются операциями над списком детей узла. Они приведены в списке функций 2..20. Есть также два определения функций только для GNode:

typedef gboolean (*GNodeTraverseFunc) (GNode *node, gpointer data);
typedef void (*GNodeForeachFunc) (GNode *node, gpointer data);
Они вызываются с указателем на узел, который обходится, и пользовательские данные. GNodeTraverseFunc может возвратить TRUE для остановки обхода; поэтому вы можете использовать GNodeTraverseFunc в комбинации с "g_node_traverse()" для поиска значения в дереве.
Список функций 2..20: Доступ к GNode
"#include "<glib.h>
void g_node_traverse(GNode *root, GTraverseType order,
                     GTraverseFlags flags, gint max_depth,
                     GNodeTraverseFunc func, gpointer data)
guint g_node_max_height(GNode *root)
void g_node_children_foreach(GNode *node, GTraverseFlags flags,
                             GNodeForeachFunc func, gpointer data)
void g_node_reverse_children(GNode *node)
guint g_node_n_children(GNode *node)
GNode *g_node_nth_child(GNode *node, guint n)
GNode *g_node_last_child(GNode *node)
GNode *g_node_find_child(GNode *node, GTraverseFlags flags,
                         gpointer data)
gint g_node_child_position(GNode *node, GNode *child)
gint g_node_child_index(GNode *node, gpointer data)
GNode *g_node_first_sibling(GNode *node)
GNode *g_node_last_sibling(GNode *node)


Linux Land
2000-09-15

next up previous contents
Вперед: 2.3 Другие особенности Назад: GNode

2.2.3 Хэш-таблицы

GHashTable -- это простая реализация хэш-таблицы, обеспечивающая ассоциативный массив с постоянным временем поиска. Для использования хэш-таблицы, вы должны предоставить GHashFunc, которая должна возвращать положительное целое число при передаче хэш-ключа:
typedef guint (*GHashFunc) (gconstpointer key);
Каждый из возвращенных guint (размер таблицы) соответствует ячейке или блоку в хэше; GHashTable обрабатывает конфликты путем хранения связного списка пар ключ-значение в каждой ячейке. Поэтому, значения guint, возвращаемые вашей GhashFunc должны быть честно равномерно распределены по всему набору возможных значений guint, иначе хэш-таблица дегенерирует в связный список. Ваша GHashFunc должна также быть быстрой, так как она используется при каждом поиске.

В дополнение к GHashFunc требуется GCompareFunc для проверки ключей на равенство. Немного неприятно то, что GHashTable не использует GCompareFunc таким же образом, что и GSList и GTree, хотя название функции такое же. Здесь от GCompareFunc ожидается, что она будет оператором равенства, возвращая TRUE, если ее аргументы равны. Она не должна быть функцией сравнения в стиле "qsort()". Функция сравнения ключей используется для нахождения правильной пары ключ-значение когда из-за конфликтов в хэше, в ячейке оказывается более одной пары.

Для создания и удаления GHashTable, используйте конструктор и деструктор, приведенные в списке функций 2..21. Запомните, что glib не знает, как удалять данные, содержащиеся в вашей хэш-таблице; она удаляет только саму таблицу.

Список функций 2..21: GHashTable
"#include "<glib.h>
GHashTable *g_hash_table_new(GHashFunc hash_func,
                             GCompareFunc key_compare_func)
void g_hash_table_destroy(GHashTable *hash_table)

Готовые к использованию хэш-функции и функции сравнения предоставлены для наиболее часто встречающихся ключей: целых чисел, указателей и строк. Все они приведены в списке функций 2..22. Функции для целых чисел принимают указатель на gint, а не сам gint. Если вы передадите NULL в качестве аргумента хэш-функции в функцию "g_hash_table_new()", то по умолчанию будет использоваться "g_direct_hash()". Если вы передадите NULL в качестве аргумента функции сравнения ключей, тогда будет использоваться простое сравнение указателей (эквивалентное "g_direct_equal()", но без вызова функции).

Список функций 2..22: Готовые хэши/сравнения
"#include "<glib.h>
guint g_int_hash(gconstpointer v)
gint g_int_equal(gconstpointer v1, gconstpointer v2)
guint g_direct_hash(gconstpointer v)
gint g_direct_equal(gconstpointer v1, gconstpointer v2)
guint g_str_hash(gconstpointer v)
gint g_str_equal(gconstpointer v1, gconstpointer v2)

Манипулировать хэшем просто. Процедуры сведены в списке функций 2..23. Вставки не копируют ключ или значение; они вводятся в таблицу точно как вы их передали, перезаписывая любую уже существующую пару ключ-значение с тем же ключом (такой же определяется вашей хэш-функцией и функцией сравнения, запомните это). Если это проблема, вы должны делать поиск и удаление перед вставкой. Будьте особенно осторожны, если вы динамически выделяете ключи или значения.

Простая функция "g_hash_table_lookup()" возвращает значение, которое она находит, связанное с ключом key, или NULL, если нет значения. Иногда это не так. Например, NULL может быть самим по себе допустимым значением. Если вы используете строки как ключи, особенно динамически выделяемые строки, знание того, что ключ сожержится в таблице может быть недостаточно; вы, наверное, захотите получить точный "gchar *", который используется в хэш-таблице для представления ключа "foo". Вторая функция поиска предоставляется для подобных случаев. "g_hash_table_lookup_extended()" возвращает TRUE, если поиск был успешным; если она возвращает TRUE, она помещает найденные ключ и значение в переданные указатели.

Список функций 2..23: Манипуляции с GHashTable
"#include "<glib.h>
void g_hash_table_insert(GHashTable *hash_table,
                         gpointer key, gpointer value)
void g_hash_table_remove(GHashTable *hash_table,
                         gconstpointer key)
gpointer g_hash_table_lookup(GHashTable *hash_table,
                             gconstpointer key)
gboolean g_hash_table_lookup_extended(GhashTable *hash_table,
                                      gconstpointer lookup_key,
                                      gpointer *orig_key,
                                      gpointer *value)

GHashTable хранит внутренний массив, размер которого -- простое число. Она также хранит счетчик числа пар ключ-значение, хранимых в таблице. Если среднее число пар на доступные выбросы ячеек ниже 5#5 (или около того), массив делается меньше; если оно превышает 3, то массив делается больше для уменьшения конфликтов. Изменение размеров происходит автоматически когда вы вставляете или удаляете пары из таблицы. Это гарантирует то, что память, используемая хэш-таблицей, расходуется оптимально. К сожалению, если вы проводите большое количество вставок или удалений, перестраивать хэш-таблицу становится неэффективно. Для решения этой проблемы, таблица может быть заморожена, что означает, что изменение размеров временно не производится. Когда вы закончили добавлять или удалять элементы, вы просто размораживаете таблицу, что влечет за собой одиночное вычисление оптимального размера. (Однако, будьте осторожны; замороженная таблица может закончится из-за слишком большого количества конфликтов, если вы добавляете огромное количество данных. Это может быть хорошо, если вы размораживаете перед тем как производить поиск.) Функции в списке функций 2..24.

Список функций 2..24: Замораживание и размораживание GHashTable
"#include "<glib.h>
void g_hash_table_freeze(GHashTable *hash_table)
void g_hash_table_thaw(GHashTable *hash_table)


Linux Land
2000-09-15

next up previous contents
Вперед: 3. Основы Gtk+ Назад: 2.2.3 Хэш-таблицы

2.3 Другие особенности

В этой книге недостаточно места, чтобы осветить все особенности glib. Стоит посмотреть в glib, как только вы ловите себя на мысли: Обязательно должна быть функция, которая... -- glib.h и документация по glib на http://www.gtk.org -- превосходные ресурсы.

Вот краткий список еще не упомянутых особенностей:

Если вам нужна некая широко используемая процедура, которой еще нет в glib, напишите ее в стиле glib и пожертвуйте ее в эту библиотеку! Вы получите бесплатно содействие при проектировании, отладке и обслуживании, и другие программисты получат выгоду от написанного вами средства. В то время как вы это читаете, возможно, что возможность, которую вы хотели, уже есть в последней версии glib.


Linux Land
2000-09-15

next up previous contents
Вперед: 1.2 Основы разработки с Назад: 1. Введение

1.1 Что такое Gnome?

Gnome -- это проект по разработке свободного программного обеспечения (или программного обеспечения в открытых текстах), начатый в 1997 году Miguel de Icaza из Мексиканского Автономного Национального Университета и небольшой командой программистов по всему миру. Вдохновленный успехом аналогичного проекта -- K Desktop Environment (KDE), растущей популярностью операционной системы GNU/Linux, и мощью графического пакета разработчика Gtk+, Gnome рос быстро -- за год сотни программистов были привлечены к проекту и были написаны тысячи строк кода. Gnome стал мощной основой для разработки приложений с графическим интерфейсом, и работает на любой современной разновидности UNIX.

Gnome на самом деле сокращение от GNU Network Object Model Environment (Среда сетевых объектных моделей GNU). Первоначально проект задумывался для создания основы для приложений-объектов аналогично технологиям Microsoft OLE и COM. Однако, область применения проекта быстро расширялась; стало ясно, что требовался прочный фундамент перед тем как сетевые объекты стали реальностью. Последние версии Gnome для разработчиков содержат архитектуру встраивания объектов под названием Bonobo, и Gnome 1.0 включает в себя быстрый и легковесный CORBA 2.2 ORB, называемый ORBit.

Gnome -- это часть проекта GNU, общей целью которого является разработка свободной операционной системы (названной GNU) и приложений для нее. GNU расшифровывается как GNU's Not UNIX (GNU -- это не UNIX), шутливый способ сказать, что операционная система GNU совместима с UNIX. Вы можете больше узнать о GNU на http://www.gnu.org.

Gnome имеет две важных ипостаси. С точки зрения пользователя это интегрированная среда рабочего стола и набор приложений. С точки зрения программиста это среда для разработки приложений (созданная из большого количества библиотек). Приложения, написанные с использование библиотек Gnome превосходно работают даже если пользователь не работает со средой рабочего стола, но они хорошо интегрируются в десктоп Gnome, если он доступен.

Среда рабочего стола включает в себя менеджер файлов, панель для переключения между задачами, запуска программ и встраиваемых апплетов, центр управления для конфигурирования, и несколько более мелких полезных мелочей. Эти программы прячут традиционную для UNIX оболочку за легко используемым графическим интерфейсом.

Среда разработки Gnome делает возможным написание согласованных, легко используемых, хорошо взаимодействующих приложений. Дизайнеры X Window System сделали обдуманное решение не навязывать какую-либо стратегию пользовательского интерфейса. Gnome добавляет слой этой стратегии, создавая законченный интерфейс. Законченные приложения Gnome хорошо работают с десктопом Gnome, но могут использоваться и отдельно от него -- пользователям необходимо лишь установить разделяемые библиотеки Gnome. Возможно также написание приложений Gnome, которые не полагаются на X Window System; вы можете, например, написать неграфический CORBA-сервис.

Эта книга о Gnome с точки зрения разработчика; она описывает как писать приложения Gnome, используя библиотеки и инструменты Gnome.


Linux Land
2000-09-15

next up previous contents
Вперед: 1.2.1 Библиотеки, не относящиеся Назад: 1.1 Что такое Gnome?

1.2 Основы разработки с помощью Gnome

Среда разработки приложений Gnome основывается на наборе библиотек; все они написаны на ANSI C с учетом использования на UNIX-подобных системах. Библиотеки, которые используют графику, полагаются в этом на X Window System. Существуют обертки, которые экспортируют Gnome API практически в любой язык программирования, включая Ada, Scheme, Python, Perl, Tom, Eiffel, Dylan и Objective C. Существуют также как минимум три различных оболочки для C++.

Эта книга описывает только C-интерфейс к библиотекам; однако, она должны быть полезна пользователям любого языка, так как отображение с C на ваш любимый язык программирования как правило прямолинеен. Книга описывает библиотеки Gnome версии 1.0 (включая совместимые исправленные релизы, такие как 1.0.9 -- все версии 1.0.x совместимы).



Подсекции

Linux Land
2000-09-15

next up previous contents
Вперед: glib Назад: 1.2 Основы разработки с

1.2.1 Библиотеки, не относящиеся к Gnome

Используя все преимущества свободного программного обеспечения, разработка Gnome не начиналась с нуля. Он использует несколько библиотек, которые поддерживаются отдельно от проекта Gnome. Они являются частью среды разработки Gnome, и вы можете считать, что они всегда присутствуют вместе с Gnome.



Подсекции

Linux Land
2000-09-15

next up previous contents
Вперед: libgnome Назад: Imlib

1.2.2 Библиотеки Gnome

Библиотеки, описанные в этом разделе являются частью пакета gnome-libs и были разработаны специально для проекта Gnome.



Подсекции

Linux Land
2000-09-15

next up previous contents
Вперед: Gtk+ Назад: 1.2.1 Библиотеки, не относящиеся

glib

glib является основой инфраструктуры Gnome. Это вспомогательная C-библиотека, предоставляющая процедуры для создания и манипулирования общими структурами данных. Она также предназначена для улучшения переносимости; например, во многих системах не хватает функции snprintf(), а glib содержит реализацию "g_snprintf()", которая гарантированно существует на всех платформах и намного безопаснее, чем snprintf() (она всегда завершает выходной буфер символом NULL).

Gnome 1.0 использует glib версии 1.2 и работает с любой версией glib из серии 1.2 (1.2.1, 1.2.2, и т.д.). Все версии glib начиная с 1.2 -- совместимые между собой исправленные релизы.


Linux Land
2000-09-15

next up previous contents
Вперед: ORBit Назад: glib

Gtk+

Gtk+, или Gimp Tool Kit -- библиотека, используемая для построения графического интерфейса в приложениях Gnome. Gtk+ была изначально написана для Gimp'а (GNU Image Manipulation Program, программа для обработки изображений -- http://www.gimp.org), но стала библиотекой общего применения. Gtk+ основывается на glib.

Gtk+ включает в себя Gdk, Gimp Drawing Kit, который является упрощением и абстрагированием от низкоуровневых библиотек X Window System. Так как Gtk+ использует Gdk вместо прямых вызовов X, порт Gdk позволяет Gtk+ работать в оконных средах, отличных от X, после относительно небольшого количества правок. Gtk+ и Gimp уже перенесены на платформу Win32 таким образом.

Gtk+ предоставляет следующие возможности для приложений Gnome:

Gnome добавляет некоторое количество дополнительных виджетов к базовой коллекции Gtk+.

Gnome 1.0 основывается на Gtk+ версии 1.2. Все версии Gtk+, начиная с 1.2, являются совместимыми исправленными релизами; например, 1.2.1.


Linux Land
2000-09-15

next up previous contents
Вперед: Imlib Назад: Gtk+

ORBit

ORBit -- это CORBA 2.2 ORB, написанный на C. Он был спроектирован так, чтобы быть компактным и быстрым по сравнению с другими ORB, и поддерживать отображение на C. ORBit реализован как набор библиотек.

CORBA, или Общая архитектура брокера запроса объектов -- это спецификация для брокеров запроса объектов, или ORB. ORB очень похож на динамического компоновщика, только он работает с объектами, а не с подпрограммами. Во время выполнения, программа может запросить сервисы конкретного объекта; ORB находит объект и создает связь между ним и программой. Например, почтовый клиент может запросить объект адресная книга и использовать его для поиска имени человека. В отличие от динамического связывания, CORBA хорошо работает и по сети, и даже позволяет взаимодействовать друг с другом разным языкам программирования и разным операционным системам. Если вы знакомы с DCOM в среде Windows, CORBA -- примерно то же самое.


Linux Land
2000-09-15

next up previous contents
Вперед: 1.2.2 Библиотеки Gnome Назад: ORBit

Imlib

Imlib (Image Library) предоставляет процедуры для загрузки, сохранения, отображения и масштабирования изображений в разнообразных популярных форматах (включая GIF, JPEG, PNG и TIFF). Она поставляется в двух вариантах: версия только для Xlib и версия, основывающаяся на Gdk. Gnome использует Gdk-версию.


Linux Land
2000-09-15

next up previous contents
Вперед: libgnomeui Назад: 1.2.2 Библиотеки Gnome

libgnome

libgnome - это совокупность не связанных с графическим интерфейсом процедур для использования в приложениях Gnome. Она, например, включает код для разбора конфигурационных файлов. Она также включает интерфейсы для некоторых внешних возможностей, таких как интернализация (через пакет GNU gettext), разбор аргументов (через пакет popt), и звука (через Enlightenment Sound Daemon, esound). Пакет gnome-libs заботится о взаимодействии с внешними библиотеками, поэтому программисту не надо думать самому об их реализации или доступности.


Linux Land
2000-09-15

next up previous contents
Вперед: libgnorba Назад: libgnome

libgnomeui

В libgnomeui собран код Gnome, связанный с графическим интерфейсом. Он состоит, в основном, из виджетов, улучшающих и расширяющих Gtk+. Вообще, виджеты Gnome навязывают стратегию пользовательского интерфейса, которая делает доступным более удобное API (программисту меньше приходится указывать что-либо явно). Конечно, это также приводит к более согласованным интерфейсам.

Вот наиболее яркие особенности libgnomeui:


Linux Land
2000-09-15

next up previous contents
Вперед: libzvt Назад: libgnomeui

libgnorba

libgnorba предоставляет возможности, связанные с CORBA, включая механизмы защиты и активации объектов. (Активация объекта -- это процесс получения ссылки на объект, который реализует данный интерфейс; он может включать запуск программы-сервиса, загрузку разделяемой библиотеки, или запрашивание у уже запущенной программы экземпляра нового объекта.)


Linux Land
2000-09-15

next up previous contents
Вперед: libart    lgpl Назад: libgnorba

libzvt

Эта небольшая библиотека содержит виджет терминала (ZvtTerm), который вы можете использовать в своих программах.


Linux Land
2000-09-15

next up previous contents
Вперед: 1.2.3 Другие библиотеки Назад: libzvt

libart    lgpl

Эта библиотека содержит процедуры рендеринга графики, написанные Raph Levien. Процедуры включенные сюда выпущены под GNU LGPL (общая публичная лицензия GNU для библиотек) и используются в виджете GnomeCanvas; Raph Levien также продает расширенную собственную версию. "libart_lgpl" обеспечивает антиалиасинг, microtile обновление областей и другую магию. В сущности, это библиотека растеризации векторной графики, функционально аналогичная языку PostScript.


Linux Land
2000-09-15

next up previous contents
Вперед: gnome-print Назад: libart    lgpl

1.2.3 Другие библиотеки

Эти библиотеки обычно используются в приложениях Gnome, но не являются частью gnome-libs.



Подсекции

Linux Land
2000-09-15

next up previous contents
Вперед: gnome-xml Назад: 1.2.3 Другие библиотеки

gnome-print

gnome-print -- это пока нечто экспериментальное, но многообещающее. Она использует "libart_lgpl" и хорошо работает с GnomeCanvas. Она предоставляет виртуальные устройства вывода (так называемые контексты печати), таким образом один и тот же код может выводит в виджет предварительного просмотра, в PostScript, и, в конце концов, в другие форматы принтеров. gnome-print также включает в себя элементы графического интерфейса, связанные с печатью, такие как диалог настройки печати и интерфейс к виртуальным шрифтам (который справляется с такой проблемой как то, что X-шрифты нельзя напечатать).


Linux Land
2000-09-15

next up previous contents
Вперед: Guile Назад: gnome-print

gnome-xml

gnome-xml -- это не проверяющая правильность формата XML-машина, написанная Daniel Veillard из World Wide Web Consortium. Она может производить синтаксический разбор в древовидную структуру, и выводить дерево в виде XML. Она полезна для любого приложения, которое нуждается в загрузке и сохранении структурированных данных; многие приложения Gnome используют XML как формат своих файлов. Эта библиотека не зависит от других, даже от glib, поэтому она привязана к Gnome только по имени. Однако, вы можете ожидать, что многие пользователи Gnome ее установили, поэтому ваше приложение, использующее gnome-xml, не причинит им неудобства.


Linux Land
2000-09-15

next up previous contents
Вперед: Bonobo Назад: gnome-xml

Guile

Guile -- это реализация языка программирования Scheme в библиотеке таким образом, что любое приложение может иметь встроенный интерпретатор Scheme. Это официальный язык расширения проекта GNU, и используется в нескольких приложениях Gnome. Добавление языка расширения в ваше приложение может звучать сложно, но Guile делает это почти тривиальным процессом. (Некоторые приложения Gnome также поддерживают Perl и Python; обычно добавить поддержку для нескольких языков проще, если вы реализовали первый. Но Guile занимает особое место в сердцах разработчиков Gnome.)


Linux Land
2000-09-15

next up previous contents
Вперед: 1.2.4 Слово о заголовочных Назад: Guile

Bonobo

В сжатые сроки Gnome-хакеры завершали Bonobo. Bonobo -- это архитектура составных документов, выполненная в традициях OLE от Microsoft; она позволяет вам, например, встраивать диаграммы в таблицы. Она будет использоваться в Gnome повсюду; любое приложение будет способно показать MIMEданные, такие как текст, HTML или изображения, запрашивая в библиотеках Gnome соответствующий компонент Bonobo. Взгляните на технологию Bonobo в следующем релизе Gnome.


Linux Land
2000-09-15

next up previous contents
Вперед: 1.3 Структура книги Назад: Bonobo

1.2.4 Слово о заголовочных файлах

Повсюду в этой книге, точный заголовочный файл, который описывает каждую функцию дается рядом с прототипом функции. Это сделано для облегчения вам процесса изучения исходных текстов. Однако, возможно, вы не захотите вручную включать сотни заголовочных файлов Gtk+ и Gnome. Вы можете включить заголовочные файлы Gtk+ все сразу включив лишь gtk/gtk.h. Он также включает gdk/gdk.h. Вы можете включить все заголовочные файлы Gnome включив gnome.h, который уже включает gtk/gtk.h. Большинство приложений Gnome просто включает gnome.h.


Linux Land
2000-09-15

next up previous contents
Вперед: 3.1.1 Полный Hello, World Назад: 3. Основы Gtk+

3.1 Галопом по Gtk+

Объектно-ориентированный стиль кодирования Gtk+, чистый дизайн и тщательно соблюдаемые соглашения по именованию API, делают программы простыми для написания и простыми для понимания. Чтобы это проиллюстрировать, вот полный Hello, World на Gtk+; вероятно, вы сможете догадаться, что делает 80% кода, даже без опыта программирования на Gtk+.



Подсекции

Linux Land
2000-09-15

next up previous contents
Вперед: 3.2.1 Распределение размеров Назад: Еще о сигналах и

3.2 Контейнеры и расположение виджетов

Существуют два типа контейнерных виджетов в Gtk+. Все они являются подклассами абстрактного GtkContainer. Первый тип контейнерных виджетов является наследником GtkBin, а другой является абстрактным базовым классом. Наследники GtkBin могут содержать в себе только один дочерний виджет; эти контейнеры добавляют некоторую функциональность ребенку. Например, GtkButton является GtkBin'ом, который превращает ребенка в нажимаемую кнопку. GtkFrame -- это GtkBin, который рисует рельефную рамку вокруг ребенка. GtkWindow позволяет ребенку появляться в окне верхнего уровня.

Второй тип контейнерных виджетов часто имеет GtkContainer прямым родителем. Эти контейнеры могут содержать больше одного ребенка, и их предназначение -- управлять раскладкой. Управлять раскладкой -- это значит, что эти контейнеры присваивают размеры и положение виджетам, которых они содержат. Например, GtkVBox размещает своих детей в вертикальную стопку. GtkFixed позволяет вам расположить детей в произвольных координатах. GtkPacker дает вам управление раскладкой в стиле Tk.

Эта глава о втором типе контейнеров. Для того, чтобы произвести требуемую вам раскладку без фиксирования любых размерв, вы должны понимать, как их (контейнеры) использовать. Цель заключается в том, чтобы не делать предположений о размерах окна и экрана, вида виджетов, шрифтов и т.п. Ваше приложение автоматически адаптируется при изменении этих факторов.



Подсекции

Linux Land
2000-09-15

next up previous contents
Вперед: 3.3.1 Жизненный цикл виджета Назад: 3.2.5 Регулируемая вручную раскладка

3.3 Концепции виджетов

В этом разделе обсуждаются концепции, которые примеными ко всем виджетам, такие как управление памятью и конкретные особые состояния, в которых могут находиться виджеты. Это концептуальный раздел; однако эти концепции очень важны для практических тем, описанных далее в этой книге.



Подсекции

Linux Land
2000-09-15

next up previous contents
Вперед: Предписание Назад: 3.2 Контейнеры и расположение

3.2.1 Распределение размеров

Для понимания контейнеров раскладки, вы сначала должны понять, как виджеты Gtk+ договариваются о своих размерах. На самом деле это довольно просто; есть только две концепции: предписание и выделение. Они отвечают за две фазы раскладки.



Подсекции

Linux Land
2000-09-15

next up previous contents
Вперед: 3.2 Контейнеры и расположение Назад: Вход в главный цикл

Еще о сигналах и обработчиках

Если испущен какой-либо из сигналов, к которому подключилась программа, вызывается соответствующий обработчик. Наш обработчик "delete_event" завершает цикл событий "gtk_main()" путем вызова "gtk_main_quit()"; это заставляет "gtk_main()" вернуть управление, что завершает программу. Обработчик "clicked" заменяет текст метки тем же, но перевернутым текстом. Заметьте, что метка была передана в обработчик как параметр "user_data" в "gtk_signal_connect()".

Обычно, ошибочно думают что все сигналы используют один и тот же тип обработчика. Каждый сигнал требует обработчик с определенным типом и набором аргументов и определенным поведением. Сигнал "clicked" имеет очень распространенный тип обработчика; его обработчик принимает указатель на виджет, который испустил сигнал и любой "user_data"-параметр, который предоставляет программист. Обработчик должен вернуть void, иначе может произойти порча памяти.

С другой стороны, "delete_event" является особым случаем. Он принимает три аргумента; первый и последний аналогичны "clicked", тогда как второй является указателем на событие, которое породило сигнал (события -- это сообщения от X приложению, оповещающие о передвижениях мыши, нажатиях на клавиши и т.п.). Обработчик "delete_event" возвращает волшебное значение: если вернули FALSE, значит Gtk+ удалит окно; а если TRUE -- то ничего не сделает. Возвращайте TRUE, если вам надо сделать что-то отличное от удаления окна; например, если вы захотите предупредить пользователя о несохраненном документе.

Заголовочные файлы виджетов -- наилучшее быстрое справочное руководство по типу обработчиков. Структура класса для виджета будет иметь место для обработчика сигнала по умолчанию; ваш обработчик должен быть смоделирован на основе обработчика по умолчанию. Например, в "gtk/gtkbutton.h" структура класса GtkButton выглядит примерно так:

struct _GtkButtonClass
{
  GtkBinClass parent_class;
  void (* pressed)  (GtkButton *button);
  void (* released) (GtkButton *button);
  void (* clicked)  (GtkButton *button);
  void (* enter)    (GtkButton *button);
  void (* leave)    (GtkButton *button);
};

Глава 9 объясняет, для чего нужна структура класса; а сейчас, просто обратите внимание на указатели на функции, и отметьте, что они соответствуют сигналам. Для перехода от этого:

void (* clicked)  (GtkButton *button);
к этому:
static void button_click_cb(GtkWidget *w, gpointer data);
просто добавьте gpointer data к описанию функции в структуре класса. В Hello, World я также сменил тип с GtkButton* на GtkWidget*; это делается часто, так как может быть более удобно иметь GtkWidget*. Аргумент всегда будет кнопкой (GtkButton), испускающей сигнал.

Другой пример может быть полезен; вот "delete_event" из "gtk/gtkwidget.h":

gint (* delete_event)  (GtkWidget *widget, GdkEventAny *event);
а вот обработчик из Hello, World:
static gint delete_event_cb(GtkWidget *w, GdkEventAny *e,
                            gpointer data);

Ну вот и все с этим. Вы можете писать простые приложения Gtk+, используя только информацию, представленную в этом разделе. Gtk+ и Gnome -- мощные средства разработки приложений, потому что вы можете думать о реальной функциональности, вместо борьбы за появление окна на экране.


Linux Land
2000-09-15

next up previous contents
Вперед: Распределение Назад: 3.2.1 Распределение размеров

Предписание

Предписание виджета состоит из ширины и высоты -- размеров, которых хочет быть виджет. Они представлены в структуре GtkRequisition:

typedef struct _GtkRequisition    GtkRequisition;

struct _GtkRequisition
{
  gint16 width;
  gint16 height;
};

Различные виджеты различными способами выбирают размер, который надо затребовать. Например, GtkLabel требует достаточно места для показа всего текста метки. Большинство контейнеров основыают свои требования размера на требованиях своих детей. Например, если вы поместите несколько кнопок в блок, то блок будет запрашивать такой размер, чтобы уместить все кнопки.

Первая фаза раскладки начинается с виджета верхнего уровня, такого как GtkWindow. Так как это контейнер, то GtkWindow спрашивает свой дочерний виджет о требованиях к размеру; этот ребенок может спросить своих детей; и так далее, рекурсивно. Когда все дочерние виджеты были опрошены, GtkWindow наконец получит обратно GtkRequisition от своего ребенка. В зависимости от того, как он был сконфигурирован, GtkWindow может быть, а может и не быть способным к расширению, чтобы адаптироваться к требованию размера.


Linux Land
2000-09-15

next up previous contents
Вперед: 3.2.2 GtkBox Назад: Предписание

Распределение

Вторая фаза раскладки начинается с этого места. GtkWindow принимает решение о том, сколько места действительно доступно для ребенка, и передает свое решение ребенку. Это называется распределением ребенка, представленное следующей структурой:

typedef struct _GtkAllocation    GtkAllocation;

struct _GtkAllocation
{
  gint16 x;
  gint16 y;
  guint16 width;
  guint16 height;
}

Элементы width и height идентичны GtkRequisition; они представляют размер виджета. GtkAllocation также включает координаты ребенка относительно его родителя. Структуры GtkAllocation присваиваются детям их родительским контейнером.

От виджетов требуется, чтобы они обращали внимание на GtkAllocation, передаваемый им. GtkRequisition всего лиш запрос; виджеты должны быть способными справляться с любым размером.

В приведенном описании процесса раскладки легко увидеть роль, которую играют контейнеры. Их работа состоит в том, чтобы собрать требование размера каждого ребенка в одно требование, передаваемое выше по дереву виджетов; затем разделить полученное распределение между своими детьми. Как это точно происходит -- зависит от конкретного контейнера.


Linux Land
2000-09-15

next up previous contents
Вперед: Детали раскладки GtkBox Назад: Распределение

3.2.2 GtkBox

GtkBox управляет строкой (GtkHBox) или столбцом (GtkVBox) виджетов. В GtkHBox всем виджетам присвоена одинаковая высота; задача блока заключается в том, чтобы распределить доступную ширину между ними. GtkHBox опционально использует некоторую доступную ширину для вставки промежутков (называемых заполнителями) между виджетами. GtkVBox аналогичен GtkHBox, но в другом направлении (то есть он распределяет доступную высоту, а не ширину). GtkBox -- абстрактный базовый класс; GtkHBox и GtkVBox могут быть почти польностью использованы через его интерфейс. Блоки -- самые используемые контейнеры.

Для создания GtkBox вы должны воспользоваться одним из конструкторов, приведенных в списке функций 3..1 и 3..2. Функции-конструкторы блока принимают два параметра. Если homogeneous равен TRUE, это значит, что все дети блока будут распределены с равным размером. spacing определяет размер пробела между каждым из детей. Существуют функции для изменение промежутка и равномерности после создания блока.

Список функций 3..1: Конструктор GtkHBox
"#include "<gtk/gtkhbox.h>
GtkWidget *gtk_hbox_new(gboolean homogeneous, gint spacing)
Список функций 3..2: Конструктор GtkVBox
"#include "<gtk/gtkvbox.h>
GtkWidget *gtk_vbox_new(gboolean homogeneous, gint spacing)

Есть две базовые функции для добавления ребенка в GtkBox; они показаны в списке функций 3..3.

Список функций 3..3: Упаковка GtkBox
"#include "<gtk/gtkbox.h>
void gtk_box_pack_start(GtkBox *box, GtkWidget *child,
                        gboolean expand, gboolean fill, gint padding)
void gtk_box_pack_end(GtkBox *box, GtkWidget *child,
                      gboolean expand, gboolean fill, gint padding)

Блок может содержать два набора виджетов. Первый набор упакован в начале (вверху или слева) блока; второй упакован в конце (внизу или справа). Если вы упакуете три виджета в начало блока, первый упакованный вами виджет окажется самым верхним или левым; второй последует за первым; а третий окажется самым ближайшим к центру блока. Если вы затем упакуете три виджета в конец того же блока, первый из них появится самым нижним или правым; второй последует за ним, а третий появится ближе всех к центру. После упаковки всех шести виджетов, порядок сверху/слева вниз/направо будет таким: 1, 2, 3, 3, 2, 1. Рисунок 3..2 показывает его для GtkVBox.

Рисунок 3..2: Кнопки, упакованные в GtkVBox



Подсекции

Linux Land
2000-09-15

next up previous contents
Вперед: Примеры упаковки неравномерных блоков Назад: 3.2.2 GtkBox

Детали раскладки GtkBox

На упаковку действует три параметра, одинаковые для упаковки в конец и в начало; смысл этих параметров немного сложен, потому что они взаимодействуют с установкой блока homogeneous и друг с другом.

Вот как GtkBox вычисляет свои требования размеров в интересующем нас направлении (ширина для GtkHBox, высота для GtkVBox):

  1. Считается, что общий запрашиваемый размер ребенка равен запросу ребенка, плюс двойной размер значения padding, используемое для упаковки ребенка. padding ребенка -- это величина пустого места с любой из его сторон. Размер ребенка = (Размер, запрошенный ребенком) + 2 6#6 (Интервал ребенка).
  2. Если блок равномерный, то базовый запрос размера блока равен размеру (запрос + интервал) наибольшего ребенка, умноженный на число детей. В равномерном блоке все дети такие же большие, как и наибольший ребенок.
  3. Если блок неравномерный, то базовый запрос размера блока есть сумма размеров (запрос + интервал) каждого ребенка.
  4. Применимая для блока установка spacing определяет, сколько пустого места оставить между детьми; поэтому это значение умножается на число детей минус один, и добавляется к базовому запросу размера. Учтите, что пробел не принадлежит ребенку; это пустое место между детьми, и на него не влияют параметры expand и fill. С другой стороны, набивка (padding) -- это место вокруг каждого ребенка, и на него влияют параметры упаковки ребенка.
  5. Все контейнеры имеют установку ширина границы; двойной размер ширины границы добавляется к запросу, представляя границу вокруг каждой из сторон. Поэтому, общий размер, запрашиваемый GtkBox равен: (Сумма размеров детей) + Пробел 6#6 (Число детей - 1) + 2 6#6 (Ширина границы).

После вычисления запроса размера и донесения его до родительского контейнера, GtkBox получит свой выделенный размер и распределит его между своими детьми следующим образом:

  1. Достаточное количество места для границы и пробелов между детьми вычитается из выделенного размера; остаток -- это доступное место для самих детей. Это место разделяется на два части: количество действительно запрошенного детьми места (запросы детей и набивка), и излишки. Излишки = (Выделенный размер) - (Сумма размеров детей).
  2. Если блок неравномерный, то излишки разделяются между всеми детьми, у которых параметр expand установлен в TRUE. Эти дети могут быть расширены, чтобы заполнить доступное место. Если ни один из детей не может расширяться, излишки места добавляются в центр блока, между виджетами, упакованными в начале и в конце.
  3. Если блок равномерный, излишки распределяются в соответствии с нуждами; те дети, которые затребовали больше места, получают меньше излишков, таким образом все заканчивается тем, что у каждого оказывается одинаковое количество места. Параметр expand игнорируется для равномерных блоков -- излишки распределяются между всеми детьми, а не только между расширяемыми.
  4. Когда ребенок получает некоторое количество лишнего места, есть две возможности. Вокруг ребенка можно добавить больше набивки, или дочерний виджет сам может быть расширен. Параметр fill определяет, что именно произойдет. Если он равен TRUE, дочерний виджет расширяется для заполнения места, таким образом все дополнительное место входит в выделенный ребенку размер; если fill равен FALSE, то увеличивается набивка ребенка для заполнения места, и для ребенка выделяется столько места, сколько он запросил. Заметьте, что fill не имеет никакого эффекта, если expand установлен в FALSE и блок неравномерный, потому что ребенок никогда не получит никакого дополнительного места.

Ух ты! Кто хочет все это обдумать? К счастью, имеется несколько общих образцов использования, поэтому вам не надо решать многовариантное уравнение, чтобы узнать, как использовать виджет. Авторы Учебника по Gtk+ красиво свели пять случаев, которые случаются на практике; в этом мы последуем за ними.


Linux Land
2000-09-15

next up previous contents
Вперед: Примеры упаковки равномерного блока Назад: Детали раскладки GtkBox

Примеры упаковки неравномерных блоков

Есть три интересных способа упаковать неравномерный блок. Первый: вы можете упаковать все виджеты в конец блока с нейтральным размером. Это означает, что параметр expand устанавливается в FALSE:

gtk_box_pack_start(GTK_BOX(box), child, FALSE, FALSE, 0);

Результат показан на рисунке 3..3. Параметр expand -- единственный, который влияет в этом случае; ни один из детей не получает дополнительного места, поэтому они не будут способны заполнять его, даже если бы fill был TRUE

Рисунок 3..3: Неравномерный, с expand = FALSE

Второй: вы можете протянуть виджеты через весь блок, позволяя им сохранять их первоначальный размер, как на рисунке 3..4; это значит, что надо установить параметр expand в TRUE:

gtk_box_pack_start(GTK_BOX(box), child, TRUE, FALSE, 0);
Рисунок 3..4: Неравномерный, с expand = TRUE и fill = FALSE

Наконец, если вы заполняете блок виджетами (позволяя б7#7льшим детям иметь больше места) установкой также и параметра fill в TRUE:

gtk_box_pack_start(GTK_BOX(box), child, TRUE, TRUE, 0);
Эта конфигурация показана на рисунке 3..5.
Рисунок 3..5: Неравномерный, с expand = TRUE и fill = TRUE


Linux Land
2000-09-15

next up previous contents
Вперед: Выводы по упаковке блоков Назад: Примеры упаковки неравномерных блоков

Примеры упаковки равномерного блока

Есть только два интересных способа упаковать равномерный блок. Вспомните, что параметр expand неуместен в равномерных блоках; поэтому два случая соответствуют установке параметра fill.

Если fill установлен в FALSE, вы получите рисунок 3..6. Заметьте, что блок логически разделен на три равные части, но только самый большой ребенок занимает все свое место. Другие дополнены пустым местом, чтобы заполнять свою треть места. Если fill установлен в TRUE, вы получите рисунок 3..7; все виджеты одинакового размера.

Рисунок 3..6: Равномерный, с fill = FALSE
Рисунок 3..7: Равномерный, с fill = TRUE


Linux Land
2000-09-15

next up previous contents
Вперед: 3.2.3 GtkTable Назад: Примеры упаковки равномерного блока

Выводы по упаковке блоков

Рисунок 3..8 показывает все пять технологий упаковки блока вместе. (Они упакованы в равномерный GtkVBox с fill = TRUE и расстоянием между детьми в два пиксела.) Это даст вам чувство их относительных эффектов. Запомните, что вы также можете поиграть параметрами padding и spacing для увеличения или уменьшения количества пустого места между виджетами. Однако, вы можете легко создать ужасную раскладку, используя несогласованные пробелы -- хорошей идеей будет попробовать сохранить виджеты выстроенными в линию и плотно расставленными.

Рисунок 3..8: Равномерный, с fill = TRUE

Последнее замечание: параметры expand и fill уместны только когда выделенный размер блока больше, чем затребованный. Таким образом, эти параметры определяют, как излишки места будут распределены. Обычно, лишнее место появляется когда пользователь изменяет размеры окна, чтобы сделать его больше размера по умолчанию. Поэтому, вы всегда должны пытаться изменить размеры окна, чтобы быть уверенными, что ваши блоки упакованы правильно.


Linux Land
2000-09-15

next up previous contents
Вперед: Пример на GtkTable Назад: Выводы по упаковке блоков

3.2.3 GtkTable

Второй, наиболее часто используемый контейнер раскладки -- это GtkTable. GtkTable делит область на ячейки; вы можете присвоить каждый дочерний виджет прямоугольнику, сделанному из одной и более ячеек. Вы можете думать о GtkTable как о разлинованном листке бумаги (с большей гибкостью -- линии сетки не обязаны быть равноудаленными, хотя могут и быть).

У GtkTable имеется обычный конструктор, и несколько функций для присоединения детей к ней; они показаны в списке функций 3..4. При создании таблицы, вы можете указать число ячеек, которые вы планируете использовать; это нужно просто для эффективности. Таблица будет автоматически расти, если вы будете помещать детей в ячейки, которые лежат за пределами текущей области. Как и блоки, таблицы могут быть равномерными и нет.

Список функций 3..4: GtkTable
"#include "<gtk/gtktable.h>
GtkWidget *gtk_table_new(guint rows, guint columns,
                         gboolean homoheneous)
GtkWidget *gtk_table_attach(GtkTable *table, GtkWidget *child,
                            guint left_side, guint right_side,
                            guint top_side, guint bottom_side,
                            GtkAttachOptions xoptions,
                            GtkAttachOptions yoptions,
                            guint xpadding, guint ypadding)

Первые два аргумента в "gtk_table_attach()" -- это таблица и ребенок, которого нужно поместить в таблицу. Следующие четыре указывают, какие линии сетки будут формировать обрамляющий блок для ребенка. Линии сетки нумеруются с верхнего левого (северо-западного) угла таблицы, начиная с 0; таким образом таблица размером 2 на 3 будет включать вертикальные линии 0, 1, 2 и горизонтальные линии 0, 1, 2, 3. Последние два аргумента -- это величина набивки слева и справа от ребенка (xpadding) и сверху и снизу (ypadding). Это аналогично набивке блоков.

Аргументы GtkAttachOptions требуют некоторого объяснения. Вот изложение их возможных значений. Значения являются битовыми масками, таким образом больше одного значения может быть указано через операцию | (OR).

Можно установить пробелы между строками и колонками в дополнение к набивке вокруг отдельных детей; термины пробелы и набивка означают одно и то же применительно к таблицам и блокам. Смотрите "gtk/gtktable.h" для получения полного списка функций GtkTable.



Подсекции

Linux Land
2000-09-15

next up previous contents
Вперед: Использование gtk table attach Назад: 3.2.3 GtkTable

Пример на GtkTable

Следующий код создает таблицу с четырьмя ячейками и тремя детьми; один ребенок покрывает две ячейки. Дети упакованы с использованием различных параметров:

GtkWidget *window;
GtkWidget *button;
GtkWidget *container;

window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
container = gtk_table_new(2, 2, FALSE);

gtk_container_add(GTK_CONTAINER(window), container);
gtk_window_set_title(GTK_WINDOW(window), "Table Attaching");
gtk_container_set_border_width(GTK_CONTAINER(container), 10);

/* This would be a bad idea in real code; but it lets us
 * experiment with window resizing. */
gtk_window_set_policy(GTK_WINDOW(window), TRUE, TRUE, TRUE);

gtk_signal_connect(GTK_OBJECT(window), "delete_event",
                   GTK_SIGNAL_FUNC(delete_event_cb), NULL);

button = gtk_button_new_with_label("1. Dosn't shrink\nor expand");
gtk_table_attach(GTK_TABLE(container), button, 0, 1, 0, 1,
                 GTK_FILL, GTK_FILL, 0, 0);

button = gtk_button_new_with_label("2. Expands and shrinks\nvertically");
gtk_table_attach(GTK_TABLE(container), button, 0, 1, 1, 2,
                 GTK_FILL, GTK_FILL | GTK_EXPAND | GTK_SHRINK,
                 0, 0);

button = gtk_button_new_with_label("3. Expands and shrinks\n"
                                   "in both directions");
gtk_table_attach(GTK_TABLE(container), button, 1, 2, 0, 2,
                 GTK_FILL | GTK_EXPAND | GTK_SHRINK,
                 GTK_FILL | GTK_EXPAND | GTK_SHRINK, 0, 0);

Поучительно посмотреть на результирующую таблицу после изменения размеров окна. Сначала, краткое изложение того, как присоединяются дети:

  1. Первый ребенок всегда получает свой затребованный размер; он не расширяется и не урезается.
  2. Второй ребенок может расширяться и урезаться только в направлении оси Y.
  3. Третий ребенок может расширяться и обрезаться в любом направлении.

Натуральный размер окна показан на рисунке 3..9; заметьте, что некоторым ячейкам выделено больше места, чем потребовали виджеты внутри них, потому что ячейки таблицы должны оставаться выровненными. (Вспомните, что кнопка с меткой будет требовать достаточно места только для показа метки целиком.) Флаг "GTK_FILL" заставляет GtkTable выделять дополнительное место для самих виджетов, вместо заполнения пустым местом промежутков вокруг них.

Рисунок 3..9: GtkTable перед изменением размеров

Теперь представьте, что пользователь расширяет окно по вертикали; заметьте, что дополнительное пространство добавилось к виджетам с включенным "GTK_EXPAND" в направлении Y -- а именно виджетам два и три -- в то время как виджет в левом верхнем углу остается неизменным. Рисунок 3..10 показывает это состояние дел.

Рисунок 3..10: GtkTable после вертикального расширения

Далее, представьте, что пользователь расширил окно горизонтально; только дочерний виджет номер три может расширяться горизонтально. Рисунок 3..11 это показывает.

Рисунок 3..11: GtkTable после горизонтального расширения

Рисунок 3..12 показывает результат после уменьшения вертикальных размеров таблицы, таким образом недостаточно места по вертикали, чтобы выделить виджетам то, что они запросили. Ребенок номер два получился укороченным, тогда как ребенок номер один получает все вертикальное место, которое ему требуется.

Рисунок 3..12: GtkTable после вертикального сжатия

И, наконец, рисунок 3..13 показывает результат того, что пользователь уменьшил горизонтальные размеры таблицы. Ребенок номер три в этой ситуации получает короткий конец палки.

Рисунок 3..13: GtkTable после горизонтального сжатия

Неплохо было бы попытаться менять размеры вашего окна вышеприведенным образом всякий раз, когда вы проектируете раскладку, чтобы быть уверенным, что все проходит нормально. Определение нормальности варьируется в зависимости от конкретных виджетов, которые вы помещаете в раскладку.


Linux Land
2000-09-15

next up previous contents
Вперед: 3.2.4 Другие виджеты раскладки Назад: Пример на GtkTable

Использование gtk table attach defaults()

Так как функция "gtk_table_attach()" довольно громоздкая, есть ее упрощенная версия, называемая "gtk_table_attach_defaults()", показанная в списке функций 3..5. Эта версия присоединяет ребенка с опциями "GTK_EXPAND" и "GTK_FILL", и без заполнения промежутков.

Заманчиво использовать "gtk_table_attach_defaults()" все время, чтобы уменьшить количество печатаемых знаков, но на самом деле вы не должны этого делать; между прочим, нужно честно сказать, что она редко используется. Эта функция используема только тогда, когда значения по умолчанию являются именно теми установками, которые вы хотите. В основном, вы должны тщательно настраивать параметры присоединения к вашей таблице, чтобы получить по-настоящему красивое поведение, когда ваше окно меняет размеры. Всегда пытайтесь менять размеры вашего окна, чтобы быть уверенными, что вы хорошо спроектировали раскладку.

Список функций 3..5: Присоединение со значениями по умолчанию
"#include "<gtk/gtktable.h>
GtkWidget *gtk_table_attach_defaults(GtkTable *table,
             GtkWidget *child, guint left_side, guint right_side,
             guint top_side, guint bottom_side)


Linux Land
2000-09-15

next up previous contents
Вперед: 3.2.5 Регулируемая вручную раскладка Назад: Использование gtk table attach

3.2.4 Другие виджеты раскладки

Блоки и таблицы -- наиболее часто используемые виджеты раскладки. Однако, существуют еще другие, для особых ситуаций.


Linux Land
2000-09-15

next up previous contents
Вперед: 3.3 Концепции виджетов Назад: 3.2.4 Другие виджеты раскладки

3.2.5 Регулируемая вручную раскладка

Существует возможность вручную обходить управление геометрией Gtk+. Это плохая идея в 95% случаев, потому что геометрия Gtk+ -- это в основном геометрия, предпочитаемая пользователем, определяемая темой, и изменением размеров окон верхнего уровня. Если вы вдруг захотите сделать эти вещи вручную, вероятно это потому, что вы используете не тот контейнер раскладки, или действительно должны будете написать свой собственный виджет-контейнер.

Вы можете заставить виджет принять нужные размеры и положение функциями, приведенными в списке функций 3..6. Однако, они редко могут понадобиться. В частности, "gtk_widget_set_usize()" не должна использоваться, чтобы установить размер по умолчанию виджета верхнего уровня. Обычно вы хотите установить размер окна, потому что сохранили состояние приложение и восстанавливаете его, или потому что пользователь указал геометрию окна в командной строке. К несчастью, если вы воспользуетесь "gtk_widget_set_usize()", пользователь не сможет уменьшить размеры окна, и вы получите по почте послание, полное ненависти. Вместо того, чтобы принудительно выставить размер, вы захотите указать начальный размер функцией "gtk_window_set_default_size()", показанной в списке функций 3..7. "gtk_widget_set_usize()" почти не используется и для виджетов верхнего уровня; в большинстве случаев вы получите лучшие результаты, использовав правильный виджет раскладки.

"gtk_widget_set_uposition()" можно использовать только для окон верхнего уровня; она почти не влияет на другие виджеты, и почти наверное приведет к плохим результатам. Ее основное назначение -- обслуживать аргумент командной строки -geometry.

Все три этих функции могут принять -1 в качестве аргументов x, y, width или height. Функции игнорируют любой из аргументов равный -1; это позволяет вам установить только один или два аргумента, оставляя остальным аргументам их значения по умолчанию.

Список функций 3..6: Принудительное выделение
"#include "<gtk/gtkwidget.h>
void gtk_widget_set_uposition(GtkWidget *widget,
                              gint x, gint y)
void gtk_widget_set_usize(GtkWidget *widget,
                          gint width, gint height)
Список функций 3..7: Размер окна по умолчанию
"#include "<gtk/gtkwindow.h>
void gtk_window_set_default_size(GtkWindow *window,
                                 gint width, gint height)


Linux Land
2000-09-15

next up previous contents
Вперед: Сборка Hello, World Назад: 3.1 Галопом по Gtk+

3.1.1 Полный Hello, World

Рисунок 3..1: Hello, World

#include <gtk/gtk.h>

static gint delete_event_cb(GtkWidget *w, GtkEventAny *e,
                            gpointer data);
static void button_click_cb(GtkWidget *w, gpointer data);

int main(int argc, char *argv[])
{
  GtkWidget *window;
  GtkWidget *button;
  GtkWidget *label;
  
  gtk_init(&argc, &argv);
  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  button = gtk_button_new();
  label = gtk_label_new("Hello, World!");
  
  gtk_container_add(GTK_CONTAINER(button), label);
  gtk_container_add(GTK_CONTAINER(window), button);
  
  gtk_window_set_title(GTK_WINDOW), "Hello");
  gtk_container_set_border_width(GTK_CONTAINER(button), 10);
  
  gtk_signal_connect(GTK_OBJECT(window), "delete_event",
                     GTK_SIGNAL_FUNC(delete_event_cb), NULL);
  gtk_signal_connect(GTK_OBJECT(button), "clicked",
                     GTK_SIGNAL_FUNC(button_click_cb), label);

  gtk_widget_show_all(window);
  
  gtk_main();
  return 0;
}

static gint delete_event_cb(GtkWidget *window, GdkEventAny *e,
                            gpointer data)
{
  gtk_main_quit();
  return FALSE;
}

static void button_click_cb(GtkWidget *w, gpointer data)
{
  GtkWidget *label;
  gchar *text;
  gchar *tmp;
  
  label = GTK_WIDGET(data);
  gtk_label_get(GTK_LABEL(label), &text);
  tmp = g_strdup(text);
  g_str_reverse(tmp);
  gtl_label_set_text(GTK_LABEL(label), tmp);
  g_free(tmp);
}



Подсекции

Linux Land
2000-09-15

next up previous contents
Вперед: Инициализация Назад: Сборка Hello, World

3.1.2 Как это работает

Эта простая программа содержит все важные элементы приложения Gtk+. Она не содержит никаких возможностей Gnome; но так как Gnome строится на Gtk+, то будут применимы аналогичные концепции.



Подсекции

Linux Land
2000-09-15

next up previous contents
Вперед: 3.1.2 Как это работает Назад: 3.1.1 Полный Hello, World

Сборка Hello, World

С Gtk+ идет shell-скрипт gtk-config; этот скрипт создается при сборке Gtk+. Его задача состоит в том, чтобы сообщить флаги компилятора, которые необходимы при компиляции программ Gtk+. Приведенная ниже shell-сессия демонстрирует его возможности:
$ gtk-config --version
1.2.0
$ gtk-config --prefix
/home/hp/local
$ gtk-config --exec-prefix
/home/hp/local
$ gtk-config --libs
-L/home/hp/local/lib -L/usr/X11R6/lib -lgtk -lgdk -rdynamic
-lgmodule -lglib -ld
$ gtk-config --libs gthread
-L/home/hp/local/lib -L/usr/X11R6/lib -lgtk -lgdk -rdynamic
-lgmodule -lgthread -lglib
$ gtk-config --cflags
-I/usr/X11R6/include -I/home/hp/local/lib/glib/include
-I/home/hp/local/include
$

Если вы используете вариант Bourne shell, такой как bash, вы можете использовать обратные апострофы ("`") для выполнения gtk-config и подстановки его вывода. Простой Makefile для компиляции Hello, World может выглядеть следующим образом:

CC=gcc

all: hello.c
        $(CC) `gtk-config --libs` `gtk-config --cflags` -o hello hello.c

clean:
        /bin/rm -f *.o *~

Конечно, этот Makefile слишком прост для настоящих приложений; глава 4 описывает как собирать реальные приложения с использованием automake и autoconf.

gtk-config позволяет вам найти Gtk+ в системе пользователя, вместо того, чтобы жестко зашивать путь в ваш Makefile. Он также удобен, если вы имеете две версии Gtk+ в вашей системе; если вы устанавливаете их в различные каталоги; вы можете выбрать конкретную версию, поместив правильный gtk-config в путь поиска вашего шелла.


Linux Land
2000-09-15

next up previous contents
Вперед: Виджеты Назад: 3.1.2 Как это работает

Инициализация

Сначала, Gtk+ должна быть проинициализирована:
  gtk_init(&argc, &argv);

Этот вызов производит соединение с X-сервером и разбирает некоторые аргументы по умолчанию, которые понимают все программы на Gtk+. Разобранные аргументы удаляются из argv, а argc соответственно уменьшается. "gtk_init()" также регистрирует функцию подчистки используя "atexit()". На практике, это важно только при использовани "fork()"; процесс-ребенок должен выйти с помощью "_exit()" вместо "exit()" во избежание завершения Gtk+ в родителе.


Linux Land
2000-09-15

next up previous contents
Вперед: Сигналы Назад: Инициализация

Виджеты

Далее, любая программа будет иметь некоторые элементы пользовательского интерфейса. В традиции X они называются виджетами. Все виджеты -- это подклассы базового класса GtkWidget, поэтому вы можете использовать GtkWidget* для ссылки на них. (Так как C не имеет родной поддержки наследования объектов, Gtk+ имеет свой собственный механизм -- глава 9 его описывает.)
  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  button = gtk_button_new();
  label = gtk_label_new("Hello, World!");
  
  gtk_container_add(GTK_CONTAINER(button), label);
  gtk_container_add(GTK_CONTAINER(window), button);
  
  gtk_window_set_title(GTK_WINDOW(window), "Hello");
  gtk_container_set_border_width(GTK_CONTAINER(button), 10);

Каждый виджет имеет функцию с именем "gtk_widgetname_new()", аналогичную конструктору в C++ или Java. Эта функция выделяет новый объект, инициализирует его и возвращает указатель на него. Все процедуры "_new()" возвращают GtkWidget* даже если они выделяют его подкласс; это сделано для удобства.

Как только у вас есть GtkWidget*, представляющий объект, вы можете манипулировать объектом, используя его методы. Все функции виджетов Gtk+ начинаются с имени типа, над которым они оперируют, и принимают указатель на этот тип в качестве первого аргумента. В вышеприведенном коде, "gtk_container_add()" принимает GtkContainer* в качестве первого аргумента. Макрос "GTK_CONTAINER()" транслирует тип GtkWidget*, а также выполняет проверку типов периода исполнения. Трансляция требуется из-за того, что C не понимает отношение наследования.

Как вы могли представить, GtkButton и GtkWindow -- оба подклассы GtkContainer. GtkContainer может содержать в себе любой другой виджет. Код создает окно верхнего уровня, помещает внутрь него кнопку, и помещает метку (строку текста) внутрь кнопки. Затем он устанавливает заголовок окна, и добавляет небольшой косметический бордюр вокруг кнопки.


Linux Land
2000-09-15

next up previous contents
Вперед: Вход в главный цикл Назад: Виджеты

Сигналы

Далее, вы захотите узнать как реагировать, когда пользователь манипулирует виджетами. В этом простом приложении, есть две интересные вещи, которые могут произойти: пользователь может нажать на кнопку, или закрыть окно, используя рамку, предоставляемую оконным менеджером. Виджеты (на самом деле, все наследники GtkObject) испускают сигналы, когда происходит что-то интересное, и на что программа захочет ответить. Для ответа на сигнал, вы должны подключить обработчик к нему, то есть зарегистрировать функцию, которая будет вызываться тогда, когда сигнал испущен. Вот снова этот код:
  gtk_signal_connect(GTK_OBJECT(window), "delete_event",
                     GTK_SIGNAL_FUNC(delete_event_cb), NULL);
  gtk_signal_connect(GTK_OBJECT(button), "clicked",
                     GTK_SIGNAL_FUNC(button_click_cb), label);

"gtk_signal_connect()" определяет, за каким GtkObject следить, к какому сигналу подключиться, обработчик, который подключить, и, наконец, аргумент "user_data" -- это произвольный gpointer, который будет передан в обоработчик. Макрос "GTK_SIGNAL_FUNC()" транслирует обработчик в стандартную функцию; так как обработчики имеют разнообразие типов, альтернативой будут десятки вариантов "gtk_signal_connect()".

Gtk+ выполняет обильное количество проверок безопасности во время выполнения; макрос "GTK_OBJECT()" включает проверку типа в дополнение к транслирванию C, а "gtk_signal_connect()" проверит, действительно ли объект может испускать сигнал, который вы указали.


Linux Land
2000-09-15

next up previous contents
Вперед: Еще о сигналах и Назад: Сигналы

Вход в главный цикл

После того как все установлено, остаются выполнить два шага: вы должны показать окно на экране и ждать ввода пользователя.
  gtk_widget_show_all(window);
  
  gtk_main();
  return 0;

"gtk_widget_show_all()" рекурсивно вызывает "gtk_widget_show()" для контейнера и всех его детей. Следующий код будет иметь тот же эффект в данном случае:

  gtk_widget_show(label);
  gtk_widget_show(button);
  gtk_widget_show(window);

Необходимо показать каждый виджет, который вы хотите видеть на экране. Противоположная операция называется "gtk_widget_hide()"; виджеты начинают свою жизнь спрятанными, и могут быть перезапрятаны/перепоказаны любое количество раз. Показывание все дочерних виджетов перед показом самого внешнего виджета является хорошей практикой; иначе пользователь сначала будет видеть появляющийся контейнер, а затем появляющихся детей. Виджеты на самом деле не показываются на экране до тех пор, пока их родитель не виден. Исключением из правила является GtkWindow, так как он не имеет родителя.

Как только ваши виджеты показались, вы захотите подождать, пока пользователь не сделает с ними что-нибудь. "gtk_main()" входит в основной цикл Gtk+; основной цикл управляется событиями. То есть, пользовательские действия побуждают события, которые, в общем, заставляют сигналы испускаться, и затем вызываться обработчики. "gtk_main()" циклится бесконечно, дожидаясь и отвечая на ввод пользователя. Главный цикл более детально описан в разделе 3.4. События и их отношение к главному циклу описаны в разделе 10.5.


Linux Land
2000-09-15

next up previous contents
Вперед: 3.3.2 Осознание, отображение и Назад: 3.3 Концепции виджетов

3.3.1 Жизненный цикл виджета

Управление ресурсами и памятью виджета почти автоматическое. Однако, существует несколько исключений (???), которые надо всегда помнить, когда вы делаете более сложные вещи.

Список функций 3..8: Удаление виджета
"#include "<gtk/gtkwidget.h>
void gtk_widget_destroy(GtkWidget *widget)

Виджет может быть удален в любое время вызовом функции "gtk_widget_destroy()" (показанной в списке функций 3..8); удаление виджета освобождает всю связанную с ним память и другие ресурсы. Если виджет находится внутри контейнера, он автоматически удаляется из контейнера перед своим разрушением. "gtk_widget_destroy()" является лишь другим именем для "gtk_object_destroy()"; Наследники GtkObject имеют виртуальные деструкторы, поэтому "gtk_object_destroy()" всегда все сделает правильно.

Внутри реализации для виджетов ведется счетчик ссылок (на самом деле, для всех GtkObject). Объекты начинают свою жизнь со счетчиком ссылок, равным 1, даже если на них еще ни разу не ссылались. В этой фазе об объекте можно сказать, что он плавающий, и ему присваивается это состояние. Можно удалить начальную ссылку на объект; это называется затопление плавающего объекта, и разрушить объект, если плавающая ссылка была равна единице.

Контейнер сначала ссылается на любой добавляемый виджет, а затем его затопляет. Затопляя виджет, контейнер становится его владельцем в целях управления его ресурсами. Поэтому, счетчик ссылок виджета остается равным 1, но объект уже не помечен как плавающий. Когда виджет удаляется из контейнера (или контейнер разрушается), счетчик ссылок уменьшается до 0. Когда счетчик ссылок объекта достигает 0, он разрушается.

На практике, это значит, что вам нужно разрушать только виджеты верхнего уровня; любые виджеты внутри контейнера будут разрушены вместе с контейнером.

Однако, тут заключена опасность. Иногда вы захотите удалить виджет из контейнера; может быть некоторый элемент вашего интерфейса является необязательным или появляется только при определенных обстоятельствах. Когда вы удаляете виджет (используя "gtk_container_remove()"), ссылка на него будет удалена, его счетчик ссылок упадет до 0, и он будет разрушен. Чтобы избежать этого, вы должны добавить ссылку на виджет перед его удалением. Список функций 3..9 приводит функции для манипулирования счетчиком ссылок.

Список функций 3..9: Учет ссылок
"#include "<gtk/gtkobject.h>
void gtk_object_ref(GtkObject *object)
void gtk_object_unref(GtkObject *object)
void gtk_object_sink(GtkObject *object)

"gtk_object_ref()" и "gtk_object_unref()" имеют варианты для виджетов ("gtk_widget_ref()", и т.д.), но версии для объектов и виджетов являются полными синонимами. Версии для виджетов остались от ранних версий Gtk+.

Итак, чтобы безопасно удалить виджет из контейнера, вы должны сделать следующее:

gtk_widget_ref(widget);
gtk_container_remove(container, widget);

Теперь виджет имеет одну ссылку, удерживаемую этим кодом. В некоторый момент вам нужно будет освободить ссылку, удаляя виджет. (Это будет иметь смысл сделать после повторного добавления виджета в какой-либо другой контейнер, например.)

Стоит напомнить, что удаление виджетов из контейнеров является особым случаем; в общем, быстрее будет просто спрятать виджет с помощью "gtk_widget_hide()", а затем вызвав "gtk_widget_show()", чтобы показать его потом.

"gtk_object_sink()" используется почти исключительно при реализации кода виджетов, когда вы, скорее всего, будете первичным владельцем объекта. Если объект не плавает, то "gtk_object_sink()" не произведет никакого эффекта. Чтобы заявить права на владение виджетом, проделайте следующее:

gtk_object_ref(widget);
gtk_object_sink(GTK_OBJECT(widget));

Этот код добавит ссылку на виджет; если виджет был плавающим, он также вычтет единицу из счетчика ссылок. Если виджет не был плавающим, "gtk_widget_sink()" не будет иметь эффекта.

Важно понять детали, потому что в некоторых случаях они могут играть свою роль. Но в основном, вы можете следовать нескольким простым правилам:


Linux Land
2000-09-15

next up previous contents
Вперед: 3.3.3 Другие концепции виджетов Назад: 3.3.1 Жизненный цикл виджета

3.3.2 Осознание, отображение и показывание

Полное понимание Gtk+ требует некоторого минимального понимания X Window System. Эта книга предполагает, что вы имеете понимание на уровне пользователя -- вы знаете, что такое X-сервер, что X может работать по сети, что делает менеджер окон и т.п. Однако, нужны некоторые дополнительные детали для написания программ.

Одна особенно важная деталь: X Window System обслуживает дерево окон. Окно в этом смысле относится к окну X, а не к GtkWindow (это концепция только Gtk+, виджет, который соответствует окну верхнего уровня приложения). Окно в X -- это не видимая пользователю концепция окна, представленная GtkWindow; это в большей степени абстракция, используемая X-сервером для разделения экрана на части. Фон, показываемый вашим X-сервером -- это корневое окно, у него нет родителя. Окна приложений -- это типичные дети корневого окна; большинство оконных менеджеров создают ребенка корневого окна, чтобы держать там рамку заголовка и другие элементы обрамления, и помещают окно приложения внутрь. Оконные менеджеры имеют полный контроль над окнами приложений -- они могут их перемещать, менять их родителя, или минимизировать их, при желании.

В свою очередь, окна приложений могут содержать подокна, которые управляются приложением. Заметьте, что Gtk+ использует библиотеку Gdk, вместо того чтобы пользоваться X напрямую; в Gdk есть тонкая обертка для X окна, называемая GdkWindow. Не путайте GdkWindow и GtkWindow.

X-окно, или GdkWindow дает X-серверу подсказки о структуре отображаемой графики. Так как X может прозрачно работать по сети, это помогает уменьшить сетевой трафик. X-сервер знает, как показать окно на экране; спрятать его, переместить его (сохраняя детей в зависимой от позиции родителя); перехватить такие события как движения мышью для каждого окна; и так далее. GdkWindow -- это также фундаментальный виджет для рисования графики: вы не можете рисовать на экран в целом, вы должны рисовать в GdkWindow.

Большинство виджетов Gtk+ имеют соответствующее GdkWindow. Есть исключения, такие как GtkLabel; на них ссылаются как на безоконные виджеты, и они сравнительно легковесны. Виджеты без связанного с ними GdkWindow рисуют себя в родительское GdkWindow. Некоторые операции, такие как перехват событий, требуют GdkWindow; поэтому они невозможны для безоконных виджетов.

Виджеты проходят через некоторое количество состояний, связанных с их GdkWindow:

В типичном своем коде, вам нужно только вызвать "gtk_widget_show()"; это повлечет осознание и отображение виджета сразу же после осознания и отображение его родителя. Важно понять, что "gtk_widget_show()" не имеет моментального эффекта, она просто планирует показать виджет. Это значит, что вы не должны беспокоиться о показе виджетов в каком-то конкретном порядке; это также значит, что вы не можете сразу же получить доступ к GdkWindow виджета. Иногда требуется получить доступ к GdkWindow; в таких случаях вы захотите вручную вызвать "gtk_widget_realize()" для его создания. "gtk_widget_realize()" также осознает и родителей виджета, если допустимо. Вам нечасто потребуется "gtk_widget_realize()"; если вам все же это понадобится, вероятно, вы подходите к проблеме неправильно.

Разрушение виджета автоматически вызывает последовательность событий в обратном порядке, рекурсивно отменяя осознание дочерних виджетов, а затем и самого виджета.

Список функций 3..10 объединяет функции, обсуждаемые в этом разделе.

Список функций 3..10: Отображение/осознание виджетов
"#include "<gtk/gtkwidget.h>
void gtk_widget_realize(GtkWidget *widget)
void gtk_widget_unrealize(GtkWidget *widget)
void gtk_widget_map(GtkWidget *widget)
void gtk_widget_unmap(GtkWidget *widget)
void gtk_widget_show(GtkWidget *widget)
void gtk_widget_hide(GtkWidget *widget)

Список макросов 3..1 объединяет макросы для запроса состояний, обсуждаемых в этом разделе

Список макросов 3..1: Предикаты виджетов
"#include "<gtk/gtkwidget.h>
GTK_WIDGET_NO_WINDOW(widget)
GTK_WIDGET_REALIZED(widget)
GTK_WDIGET_MAPPED(widget)
GTK_WIDGET_VISIBLE(widget)


Linux Land
2000-09-15

next up previous contents
Вперед: Чувствительность Назад: 3.3.2 Осознание, отображение и

3.3.3 Другие концепции виджетов

Этот раздел описывает некоторые другие концепции, связанные с базовым классом GtkWidget, включая чувствительность, фокус и состояния виджетов.



Подсекции

Linux Land
2000-09-15

next up previous contents
Вперед: Фокус Назад: 3.3.3 Другие концепции виджетов

Чувствительность

Виджеты могут быть чувствительными и нечувствительными; нечувствительные виджеты не отвечают на ввод. (На других платформах их называют привидениями или неактивными.)

"gtk_widget_set_sensitive()" (список функций 3..11) изменяет чувствительность виджета.

Список функций 3..11: Изменение чувствительности
"#include "<gtk/gtkwidget.h>
void gtk_widget_set_sensitive(GtkWidget *widget,
                              gboolean setting)

По умолчанию чувствительность установлена в TRUE. Виджет действительно чувствителен, если все его родители чувствительны; то есть, вы можете сделать весь контейнер, заполненный виджетами (не)чувствительным, установив чувствительность контейнера. Настоящая чувствительность виджета, включая состояние родителя, может быть проверена макросом "GTK_WIDGET_IS_SENSITIVE()". Чувствительность самого виджета, которая имеет значение лишь если родитель виджета чувствителен, может быть запрошена с использованием "GTK_WIDGET_SENSITIVE()". Они приведены в списке макросов 3..2.

Список макросов 3..2: Чувствительность
"#include "<gtk/gtkwidget.h>
GTK_WIDGET_IS_SENSITIVE(widget)
GTK_WIDGET_SENSITIVE(widget)


Linux Land
2000-09-15

next up previous contents
Вперед: Захваты Назад: Чувствительность

Фокус

Внутри каждого окна верхнего уровня только один виджет может иметь клавиатурный фокус. Любые нажатия клавиш, получаемые окном верхнего уровня передаются сфокусированному виджету. Это важно, потому что набор чего-то на клавиатуре должен иметь только один эффект -- изменение только одного поля ввода текста, например.

Большинство виджетов дадут какую-то визуальную индикацию того, что они имеют фокус. Используя тему Gtk+ по умолчанию, сфокусированный виджет обычно окружен тонкой черной рамкой. Пользователь может передвинуть фокус между виджетами, используя стрелки или табуляцию. Фокус также может быть переведен на виджет путем нажатия на него мышью.

Концепция фокуса важна для навигации клавиатурой. Например, нажатие на Enter или пробел активизирует многие виджеты, если они имеют фокус; например, вы можете передвигаться между кнопками с помощью табуляции, а затем нажимать одну из них пробелом.


Linux Land
2000-09-15

next up previous contents
Вперед: Умолчание Назад: Фокус

Захваты

Виджеты могут захватить мышь и клавиатуру у других виджетов. Это в основном значит, что виджет становится модальным; ввод идет только этому виджету, и фокус не может уйти с него. Частой причиной захвата ввода является создание модального диалога; если у окна есть захват, взаимодействие с другими окнами блокируется. Заметьте, что есть другой захват, уровня Gdk; захват Gdk мыши и клавиатуры происходит на уровне X-сервера, таким образом другие приложения не могут получать события от мыши или клавиатуры. Захват на уровне виджета -- это концепция Gtk+; он только захватывает события, идущие от других виджетов в том же приложении.


Linux Land
2000-09-15

next up previous contents
Вперед: Состояния виджета Назад: Захваты

Умолчание

Каждое окно может иметь максимум один виджет по умолчанию. Например, диалоги имеют кнопку по умолчанию, которая активизируется, когда пользователь нажимает Enter.


Linux Land
2000-09-15

next up previous contents
Вперед: 3.4 Главный цикл Назад: Умолчание

Состояния виджета

Виджеты имеют состояния, которые определяют их внешний вид:

Точное значение и визуальное представление конкретного состояния зависит от конкретного виджета и текущей темы. Вы можете получить доступ к состоянию виджет с помощью "GTK_WIDGET_STATE()" (список макросов 3..3). Этот макрос возвращает одну из констанат: "GTK_STATE_NORMAL", "GTK_STATE_ACTIVE", "GTK_STATE_PRELIGHT" или "GTK_STATE_INSENSITIVE".

Список макросов 3..3: Чувствительность
"#include "<gtk/gtkwidget.h>
GTK_WIDGET_STATE(widget)


Linux Land
2000-09-15

next up previous contents
Вперед: 3.4.1 Основы главного цикла Назад: Состояния виджета

3.4 Главный цикл

Основная роль главного цикла Gtk+ заключается в том, чтобы слушать события, поступающие из дескриптора файла, подключенного к X-серверу, и пересылать их виджетам. Раздел 10.5.3 более детально описывает обработку событий в главном цикле. Этот раздел объясняет главный цикл в общих терминах, и описывает, как добавить функциональность к главному циклу: обработчики, которые вызываются когда главный цикл бездействует, через определенный интервал, когда дескриптор файла готов к чтению или записи, и когда главный цикл завершается.



Подсекции

Linux Land
2000-09-15

next up previous contents
Вперед: 3.4.2 Функции выхода Назад: 3.4 Главный цикл

3.4.1 Основы главного цикла

Главный цикл в основном реализован в glib, которая имеет общую абстракцию для главного цикла. Gtk+ подключает главный цикл glib к соединению с X-сервером, и предоставляет удобный интерфейс (цикл glib довольно низкоуровневый по сравнению с циклом Gtk+). Основной интерфейс главного цикла Gtk+ показан в списке функций 3..12.

"gtk_main()" выполняет главный цикл. "gtk_main()" не вернет управление до тех пор, пока не будет вызвана функция "gtk_main_quit()". "gtk_main()" может быть вызвана рекурсивно; каждый вызов "gtk_main_quit()" завершает один экземпляр "gtk_main()". "gtk_main_level()" возвращает уровень рекурсии; то есть она вернет 0, если не работает ни одна "gtk_main()", 1, если работает одна "gtk_main()", и т.д.

Все экземпляры "gtk_main()" функционально идентичны; все они следят за одним подключением к X-серверу и работают с одной очередью событий. Экземпляры "gtk_main()" используются для блокирования, приостанавливая выполнение функции до наступления каких-то событий. Все программы Gtk+ используют этот прием для предотвращения выхода из "main()" во время выполнения приложения. Функция "gnome_dialog_run()" (см. раздел 7.2) использует рекурсивный главный цикл, поэтому она не вернет управление до тех пор, пока пользователь не нажмет кнопку диалога.

Иногда вы захотите обработать несколько событий без обработки потока управления в "gtk_main()". Вы можете произвести одиночную итерацию главного цикла, вызвав "gtk_main_iteration()". Она должна обработать одиночное событие, например; это зависит от того, какие задачи ожидают в очерели. Вы можете проверить, не ожидают ли какие-либо события обработки, вызвав "gtk_events_pending()". Вместе, эти две функции позволяют вам временно вернуть управление Gtk+, и таким образом графический интерфейс может наверстать упущенное. Например, во время длительного вычисления, вы захотите показать индикатор прогресса; вы должны позволить главному циклу Gtk+ периодически выполняться, тогда Gtk+ сможет перерисовать индикатор. Используйте следующий код:

while (gtk_events_pending())
  gtk_main_iteration();
Список функций 3..12: Главный цикл
"#include "<gtk/gtkmain.h>
void gtk_main()
void gtk_main_quit()
void gtk_main_iteration()
gint gtk_events_pending()
guint gtk_main_level()


Linux Land
2000-09-15

next up previous contents
Вперед: 3.4.3 Функции таймаута Назад: 3.4.1 Основы главного цикла

3.4.2 Функции выхода

Функция выхода -- это обработчик, который будет вызываться при вызове "gtk_main_quit()". Другими словами, обработчик выполняется сразу перед тем как "gtk_main()" вернет управление. Обработчик должен иметь тип GtkFunction, определенный следующим образом:

typedef gint (*GtkFunction) (gpointer data);

Функции выхода добавляются с помощью "gtk_quit_add()" (список функций 3..13. При добавлении функции выхода, вы должны указать уровень главного цеикла, возвращаемый "gtk_main_level()". Второй и третий аргументы указывают обработчик и его данные.

Значение, возвращаемое обработчиком указывает, нужно ли вызвать обработчик еще раз. До тех пор, пока обработчик возвращает TRUE, он будет повторно вызываться. Как только он возвратит FALSE, обработчик отключается. Когда все функции выхода возвратили FALSE, "gtk_main()" может возвратить управление.

"gtk_quit_add()" возвращает идентификатор, который может использоваться для удаления функции выхода с помощью "gtk_quit_remove()". Вы можете удалить функцию выхода путем передачи данных обработчика функции "gtk_quit_remove_by_data()".

Список функций 3..13: Функции выхода
"#include "<gtk/gtkmain.h>
guint gtk_quit_add(guint main_level, GtkFunction function,
                   gpointer data)
void gtk_quit_remove(guint quit_handler_id)
void gtk_quit_remove_by_data(gpointer data)


Linux Land
2000-09-15

next up previous contents
Вперед: 3.4.4 Функции простоя Назад: 3.4.2 Функции выхода

3.4.3 Функции таймаута

Функции таймаута подключаются и отключаются точно также, как и функции выхода; ожидаемый обработчик такой же. "gtk_timeout_add()" ожидает аргумент interval; обработчик вызывается каждые interval миллисекунд. Если обработчик когда-либо возвратит FALSE, он удаляется из списка функций таймаута, так же, как если бы вы вызвали "gtk_timeout_remove()". Небезопасно вызывать "gtk_timeout_remove()" изнутри функции таймаута; это меняет список таймаутов в то время, как Gtk+ по нему проходит, вызывая падения. Вместо этого, верните FALSE для удаления функции.

Список функций 3..14: Функции таймаута
"#include "<gtk/gtkmain.h>
guint gtk_timeout_add(guint32 interval,
                      GtkFunction function, gpointer data)
void gtk_timeout_remove(guint timeout_handler_id)


Linux Land
2000-09-15

next up previous contents
Вперед: 3.4.5 Функции ввода Назад: 3.4.3 Функции таймаута

3.4.4 Функции простоя

Функция простоя непрерывно вызывается когда главному циклу Gtk+ больше нечем заняться. Функции простоя выполняются только когда очередь событий пуста, и главный цикл будет работать вхолостую, ожидая когда что-то произойдет. До тех пор, пока они возвращают TRUE, они вызываются снова и снова; когда они вернут FALSE, они удаляются, как будто была вызвана функция "gtk_idle_remove()".

API функций простоя, показанный в списке функци 3..15, идентичен API функций таймаута и выхода. Опять же, "gtk_idle_remove()" не должен вызываться изнутри функции простоя, потому что это может повредить список функций простоя Gtk+. Верните FALSE, если хотите удалить функцию простоя.

Функции простоя наиболее часто используются для постановки в очередь одноразового кода, который выполняется после того, как все события были обработаны. Относительно дорогие операции, такие как согласование размеров в Gtk+ и перерисовки GnomeCanvas происходят в функциях простоя, возвращающих FALSE. Это позволяет быть уверенным в том, что дорогие операции выполняются только один раз, даже если несколько последовательных событий затребовали перевычисления.

Главный цикл Gtk+ содержит простой планировщик; функции простоя на самом деле имеют приоритеты, им присвоенные, также как и процессы в UNIX. Вы можете присвоить приоритет, не равный приоритету по умолчанию вашим функциям простоя, но это сложная тема вне компетенции этой книги.

Список функций 3..15: Функции простоя
"#include "<gtk/gtkmain.h>
guint gtk_idle_add(GtkFunction function, gpointer data)
void gtk_idle_remove(guint idle_handler_id)
void gtk_idle_remove_by_data


Linux Land
2000-09-15

next up previous contents
Назад: 3.4.4 Функции простоя

3.4.5 Функции ввода

Функции ввода обрабатываются на уровне Gdk. Они вызываются когда данный дескриптор файла готов для чтения или записи. Они особенно удобны для сетевых приложений.

Для добавления функции ввода, вы указываете дескриптор файла, за которым надо следить, состояние, которого вы хотите дождаться (готовность для чтения или записи), и пару обработчик/данные. Список функций 3..16 показывает это API. Функции могут быть удалены по тэгу, возвращаемому "gdk_input_add()". В отличие от функций выхода, таймаута и простоя, в данном случае можно безопасно вызывать "gdk_input_remove()" изнутри функции ввода; Gtk+ не будет в середине пробега по списку функций ввода.

Для указания условия (условий), которых необходимо дождаться, используйте флаги GdkInputCondition: "GDK_INPUT_READ", "GDK_INPUT_WRITE" и "GDK_INPUT_EXCEPTION". Вы можете объединить несколько флагов вместе операцией OR. Эти флаги соответствуют трем наборам дескриптора файла, передаваемых в системный вызов select(); детали смотрите в хорошей книге по программированию в UNIX. Если встретилось любое из указанных условий, вызывается функция ввода.

Обработчик должен выглядеть примерно так:

typedef void (*GdkInputFunction) (gpointer data, gint source_fd,
                                  GdkInputCondition condition);
Он получает данные, дескриптор файла, за которым следит, и условия, которые привели к вызову (вероятны подмножества условий, за которыми вы следите).
Список функций 3..16: Функции ввода
"#include "<gdk/gdk.h>
gint gdk_input_add(gint source_fd, GdkInputCondition condition,
                   GdkInputFunction function, gpointer data)
void gdk_input_remove(gint tag)

Linux Land
2000-09-15