Исходная статья по адресу:
http://www.gnomejournal.org/article/34/writing-a-widget-using-cairo-and-gtk28
2 декабря 2005, Дэвид Мадэли (Davyd Madeley)
4 января 2006, Безденежных Сергей (sib-mail@mtu-net.ru)
Начиная с версии 2.8, GTK+ отрисовывает все свои элементы интерфейса с помощью мощной библиотеки векторной графики Cairo. В этой статье рассказывается как можно самому создать виджет GTK+, используя библиотеки Cairo для его отрисовки.
Cairo это мощный двухмерный графический инструментарий, поддерживающий большое число современных графических функций, включая alpha-blending и сглаживание. Он поддерживает множество графических форматов, позволяя разработчикам использовать один и тот же код для отображения графики на экране, печати на принтере и ускорения обработки графики с помощью OpenGL.
Начиная с версии 2.8, грaфический инструментарий GTK+ интегрирован с библиотекой
Cairo 1.0, предоставляя разработчикам доступ к этому гибкому графическому API.
Первая часть этой статьи посвящена рассмотрению кода, необходимого при создании виджетов GTK+, использующих Cairo. Во второй части рассказано, как использовать Cairo для того, чтобы выполнять собственно рисование. Если вы хотите просто рисовать внутри существующих виджетов или внутри GtkDrawingArea, вы можете пропустить первую часть и перейти сразу ко второй. Будущие статьи будут посвящены рассмотрению способов реализации сигналов виджета и некоторым другим возможностям Cairo API.
Можно просто начать рисовать, с помощью Cairo внутри площадки для рисования GtkDrawingArea, однако если нужно рисовать много раз одно и тоже, вы наверное захотите создать собственный виджет, который можно будет использовать многократно. Большая часть этой главы будет полезна при создании любых других виджетов, не обязательно использующих для отрисовки графическую библиотеку Cairo.
Первый шаг к написанию своего собственного виджета – это создание нового потомка универсального класса GObject. Всестороннее рассмотрение создания потомка GObject может стать сложной задачей, поэтому мы остановимся на рассмотрении ключевых моментов, необходимых для написания нашего виджета.
Как известно GTK+ – объектно-ориентированная среда, это означает что существует две структуры данных, на которые придется потратить время: класс и его экземпляр. Экземпляр это наиболее привычная нам стрктура данных типа GtkWidget, к которой мы будем постоянно обращаться внутри программы.
Классы наследуют свойства других классов. Так как мы пишем виджет, который будет отрисовываться с помощью Cairo, наиболее удобно будет создать класс основанный на GtkDrawingArea, который наследует свойства GtkWidget, GtkObject и в конце GObject. Тем более, что в классе GtkDrawingArea уже реализовано множество функций, которые нужны для нашего виджета, это поможет нам избавится от написания большого количества ненужного кода.
Класс создается только один раз, тогда как экземпляр класса создается каждый раз, когда мы создаем новую копию виджета. Если все это сбивает вас с толку или вы не знакомы с объектно-ориентированным программированием в С, не беспокойтесь, мы будем постепенно углубляться с тем что бы вы поняли смысл.
В файле clock.c с помощью макроса зададим имя нашего нового класса:
Мы назвали наш класс EggClockFace, все наши функции будем начинать с префикса egg_clock_face, при этом мы унаследовали класс GtkDrawingArea.
Теперь нам нужно определить структуры для нашего нового класса и экземпляров этого класса. В нашем примере эти структуры достаточно просты. Если бы мы хотели использовать общедоступные переменные и структуры, то объявили бы их здесь. Локальные структуры определяются в другом месте (рассмотрим это позже). Так что, в clock.h пишем:
Есть еще несколько шаблонных строк, которые нужно включить в заголовочный файл нового класса, они позволят использовать те удобные макросы, к которым мы привыкли при использовании виджетов GTK+. Без дальнейших объяснений, разместим это в начале файла clock.h:
Во время инициализации класса, вызывается функция egg_clock_face_class_init(), которая позволяет задать функции и свойства класса в нашем новом классе. Так как мы выполняем рисование, нам нужно переписать родительский обработчик показа виджета. Так что пишем в файле clock.c:
Позже, в функции class_init, с помощью функции g_type_class_add_private() мы сможем зарегистрировать локальные структуры класса.
Когда кто-нибудь создает новый экземпляр EggClockFace, вызывается функция egg_clock_face_init(). Нам не нужно беспокоиться о создании экземпляра GtkWidget или о выделении памяти, все это будет сделано за нас. Не надолго оставим пустой эту функцию:
Обратите внимание на то, что рассмотренные до сих пор функции – статические. Нужно запомнить, что все эти функции внутренние по отношению к объекту и не описываются в публичном API.
Нам нужен какой-нибудь способ создания объекта, поэтому добавим функцию egg_clock_face_new().
Как вы видите эта функция объявляется просто для удобства.
В конце концов, чтобы закончить, определим наш обработчик рисования.
Теперь нужно написать главную функцию приложения main(). Вот что получилось у меня в main.c:
Итак, если вы все сделали правильно, у вас должны получится следующие файлы
clock-ex1.c, clock.h, and main.c.
Вы можете скомпилировать все это с помощью команды:
Запустив программу, вы увидите окно, изображенное на рисунке 1.

Рисунок 1 – Пустое GTK окно
Здорово, правда? Да ... может быть не совсем. Теперь нам нужно что-нибудь здесь нарисовать.
Если какой-нибудь элемент GTK+ требует перерисовки, возникает событие перерисовки expose-event. Если вы пропустили шаг 1, скажу, что сейчас мы будем писать код обработчика для этого события. Когда возникает событие перерисовки, GTK+ передает для обработки некоторую информацию, включая площадь виджета, которую нужно перерисовать. Вся эта информация передается в структуре GdkEventExpose.
Чтобы начать рисовать с Cairo нам нужно получить контекст Cairo (обычно называемый cairo_t). Мы можем получить cairo_t для GdkWindow (куда собственно мы будем рисовать). Нужно обратить внимание, что GdkWindow и GtkWindow – разные вещи, все виды виджетов содержат в себе GdkWindow для рисования. Нестрашно, если вы сразу не запомните что куда, со временем вы поймете суть.
Получить доступ к GdkWindow большинства виджетов (напр. GtkDrawingArea), можно обратившись к свойству window структуры виджета, таким способом widget – > window. Итак чтобы получить cairo_t в нашем приложении, нужно немного дописать код:
Теперь виджет будет полностью перерисован, после каждого события перерисовки. Чтобы сделать перерисовку немного быстрее, определим область перерисовки.
Теперь попробуем что-нибудь нарисовать внутри области перерисовки. С Cairo процесс рисования представляет собой определение кривых, с последующим их вычерчиванием. Это можно сравнить с тем, как будто вы набрасываете рисунок простым карандашом, а затем обрисовываете его кистью. Вы можете выбирать различные кисти с различной толщиной линии и цветом, однако каждое такое обрисовывание требует своей кисти. Вы можете также делать другие вещи, например закрасить область цветом, внутри очерченной кривой и только потом обвести ее по контуру.
Вначале попробуем нарисовать циферблат часов, для это потребуется немного вспомнить геометрию. Размер нашего холста (canvas), где мы будем рисовать, храниться в структуре виджета под названием allocation. Поэтому, найти центр нашего холста (x,y) достаточно просто:
Так как нам надо нарисовать круг, попробуем нарисовать его максимального радиуса, исходя из размеров нашего холста:
Чтобы нарисовать циферблат нужно определить дугу с центром в (x,y) и поворотом межу 0 и 2Пи радиан:
Заполним получившейся круг белым цветом, а потом обведем его черным цветом:
Скомпилировав и запустив, увидим изображенную на рисунке 2 форму.

Рисунок 2 – Заготовка для циферблата
Теперь добавим отметку для каждого часа на циферблате. Снова вспомним немного геометрии, нам нужно разделить 2Пи на 12 частей, получается Пи/6 радиан на каждую отметку.
Теперь нужно нарисовать линии изнутри круга к его границам, такие линии можно определить, с помощью функций cairo_move_to() и cairo_line_to().
Очень удобно, когда отметки на циферблате для 12, 3, 6, 9 часов выделяются из обычных отметок, попробуем сделать также.
Может быть вы заметили, что в этом примере мы использовали две новых функции Cairo_save() и cairo_restore(). Эти функции позволяют нам управлять стеком состояний Cairo. Чтобы сохранить старые значения ширины используемой кисти, просто поместим их в стек. Теперь мы можем изменять значение толщины и вырисовывать пути, когда потребуется можно снова восстановить старые значения из стека. Это простой способ не отслеживать значения кистей при выполнении операций рисования.
Теперь файл с инструкциями рисования будет иметь вид
clock-ex3.c. Если вы скомпилируете все это с помощью команды:
увидите пустой циферблат часов, изображенный на рисунке 3.

Рисунок 3 – Форма циферблата
Итак, мы рассмотрели некоторые способы рисования на холсте и убедились, что при возникновении события перерисовки рисунок обновляется. В следующий раз мы рассмотрим, как модифицировать наш виджет, чтобы изобразить на нем стрелку и реализуем сигналы, обрабатывающие поступающую информацию.
Если вы хотите узнать больше о Cairo и Cairo API, посетите сайт по адресу:
http://www.cairographics.org/