Новости · Пользователю · Переводчику · Разработчику · Ресурсы

Создание виджетов с использованием Cairo и GTK+ 2.8, часть 2

Исходная статья по адресу: http://www.gnomejournal.org/article/36/writing-a-widget-using-cairo-and-gtk28-part-2
16 февраля 2006, Дэвид Мадэли (Davyd Madeley)
9 марта 2006, Безденежных Сергей (sib-mail@mtu-net.ru)


В предыдущей статье мы создали виджет часов и назвали его EggClockFace, для изображения циферблата использовались функции библиотеки Cairo. Мы рассмотрели основы реализации GObject и рисования с помощью библиотеки векторной графики Cairo. Теперь пришло время завести наши часы.

Шаг 3. Заводим часы

Сделать стрелки часов движущимися также просто, как запустить таймер, который будет вызывает callback-функцию. Тем не менее, нам может потребоваться устанавливать время отличное от системных часов, так что время наших часов придется хранить внутри виджета.
Не хотелось бы, чтобы люди меняли время непосредственно на часах, так как тогда мы не сможем узнать, что оно изменилось. Вместо этого, для изменения времени, мы предоставим людям открытый API наших часов. В терминах объектно-ориентированного программирования мы сделаем переменную, содержащую время, закрытой (private).
Добавим структуру закрытых данных о времени часов в начало нашего C-фала виджета. Мы добавляем ее в С-файл вместо заголовочного файла, так как она частная для класса виджета часов (код которого содержится внутри этого С-файла), таким образом мы не засоряем пространство имен и избегаем использования этой переменной извне нашего класса.


Добавим это в начало файла clock.c:

Добре, в добавок создадим макрос, дабы упростить доступ:

В заключении, зарегистрируем эту закрытую структуру класса в конце функции egg_clock_face_class_init():

Благодаря этой функции, мы можем получить доступ к нашим частным переменным из любой функции в С-файле, которой передан объект EggClockFace. Получить доступ к нашей закрытой структуре можно примерно таким образом:

Теперь обратиться к полю структуры можно обычным способом, например:

Совместив упомянутые выше приемы, добавим в нашу программу функцию egg_clock_face_update(), которая будет обновлять время наших часов:

Обратите внимание на то, что эта функция возвращает логическое значение (true). Функции, предающиеся как события таймера, возвращают логическое значение. Если возвращаемое значение равно true – событие будет обработано еще раз, если равно false – не будет. Есть еще одна функция, которую мы до сих пор не определили: egg_clock_face_redraw_canvas(). Эта функция будет перерисовывать для нас холст. Изучив страницы руководства по GtkDrawingArea (наш родительский класс), мы договоримся использовать функцию gdk_window_invalidate_rect() для сброса холста, которая вызывает его перерисовку. Опять же из руководства, мы можем узнать, что эта функция – оболочка вокруг функции gdk_window_invalidate_region() и поэтому, чтобы наше событие произошло немедленно, мы должны вызвать функцию gdk_window_process_all_updates(). Наша функция перерисовки, будет выглядеть так:

Вырисовывание стрелок потребует от нас небольших знаний геометрии и немного подумать головой. Для начала о часовой стрелке: она поворачивается на 30 градусов в час и на пол градуса в минуту.

 (8 Кб)

Так что, для вырисовывания стрелки, понадобиться что-то вроде этого:

Минутная и секундная стрелка поворачивается на 6 градусов в минуту/секунду. Минутную стрелку легко реализовать следующим способом:

В конце, нам нужно сделать эти стрелки движущимися, для этого в egg_clock_face_init() мы добавим функцию таймера:

Последним мы правили файл clock-ex4.c (если нужно загрузите файлы clock.h и main.c ), все это можно скомпилировать командой:

Сделав это, вы увидите вот такие часы:

 (158 Кб)

Дополнительно: Картинка с тикающими часами

Анимационная картинка тикающих часов сделана в формате GIF с помощью программы, называемой byzanz. Я просто записал 60 секунд работы часов. Чтобы byzanz-record могла найти координаты окна, мне пришлось добавить следующий код в файл main.c после функции gtk_widget_show_all():

Эти строчки печатают параметры, которые можно вставить в другую командную строку:

Шаг 4: Создание сигналов

К настоящему моменту мы реализовали GObject, добавили к нему закрытые поля и использовали Cairo для вырисовывания циферблата часов. Однако виджеты GTK+, которые мы привыкли использовать, обычно предлагают нам свой открытый API и испускают сигналы, дабы предупредить нас, когда происходит определенное, связанное с ними, событие. Поэтому мы тоже попробуем создать свой сигнал, который будет сообщать о том, что кто-то вращает минутную стрелку.
Для начала, надо решить при каком событии будет послан наш сигнал, и добавить это в clock.h. Мы создадим сигнал “time-changed”, который вместе с объектом будет передавать функциям установленное на часах время, в часах и минутах. Если бы мы подключили этот сигнал, наша callback-функция выглядела примерно так:

В файле clock.h нужно добавить следующее в структуру _EggClockFaceClass:

В файле clock.c, нам нужно сделать несколько вещей. Во-первых, нам нужно перечислить значения всех наших сигналов, для чего сделаем определение:

После этого нам нужно определить массив беззнаковых целых чисел, чтобы хранить значения идентификаторов этих сигналов:

В заключение нам необходимо зарегистрировать наш сигнал в egg_clock_face_class_init():

Подробную информацию о функции g_signal_new() можно найти в документации по API GTK+. Обратите внимание на параметр clock_marshal_VOID_INT_INT – это наша функция упорядочивания (marshaller), она используется GTK для преобразования значений параметров испускаемых сигналов для использования в callback-функциях языка C. В GTK+ есть готовый набор наиболее часто используемых функций упорядочивания, например, если у обработчика сигнала нет параметров, кроме объекта испустившего сигнал и пользовательских данных, можно использовать готовый обработчик g_cclosure_marshal_VOID__VOID. Все готовые функции упорядочивания начинаются с префикса g_cclosure_marshal и имеют вид «префикс_ВОЗВРАЩАЕМЫЙТИП_ПАРАМЕТР_ПАРАМЕТР_итд». К сожалению, среди стандартных функций отсутствует такая, где были бы параметры “VOID_INT_INT”, по этой причине нам придется создать эту функцию самим.
К счастью, это достаточно просто сделать, нужно лишь определить её в файле clock-marshallers.list. В этом файле нужно определить список всех функций упорядочивания, которые следует сгенерировать, например:

Самый простой способ сгенерировать файлы с кодом и заголовочные файлы наших функций упорядочивания, это сделать небольшой Makefile, например такой:

Как видно, цель “clock” зависит от файлов “clock.c”, “clock.h” и “main.c”, которые у нас есть, но кроме этого цель зависит от файлов “clock-marshallers.c” и “clock-marshallers.h”, которых у нас нет, так что нужно ввести дополнительные цели, чтобы создать эти файлы из нашего списка функций упорядочивания. Не забудьте включить файл “clock-marshallers.h” в “clock.c”, иначе вы не сможете использовать полученные функции.
Такой способ автоматического создания кода, помогает облегчить труд программиста, и избавляет его от необходимости писать одинаковый код несколько раз. Эта возможность особенно полезна, если в программе реализуется множество сигналов. Конечно, нет ничего плохого, в том, чтобы самому написать парочку функций упорядочивания, или использовать заранее сгенерированный код, но зачем? Поступая так, вы усложните добавление и редактирование сигналов в будущем.
Дальше мы реализуем обработчик сигнала button-press-event, причем так, чтобы можно было определить, что кто-то нажал мышкой на стрелку часов. Мы можем заменить обработчики сигналов button-press-event, button-release-event и motion-notify-handler, так же как заменили обработчик сигнала expose-event. Для этого в функцию egg_clock_face_class_init() добавим:

После прочтения документации по нашему родительскому классу Gtk Drawing Area?, вы узнаете, что события связанные с нажатием кнопок и движением мыши над виджетом, отбираются по определенной маске. Так что для обработки сигналов нам дополнительно придется установить маску. Поскольку нужно чтобы она присутствовала в каждом EggClockFace, добавим следующий код в egg_clock_face_init():

Линия стрелок часов чрезвычайно мала, так что не приходиться-с ожидать от пользователя, что он попадет на неё мышкой. В этом случае, будет правильно дать ему запас активного пространства в 5 пикселей от линии стрелки. Чтобы сделать это, снова вспомним геометрию.
Мы знаем, что (sin ф, cos ф) – это точка на единичной окружности, т.е с радиусом равным 1. Таким образом вектор от начала координат до (sin ф, cos ф) – единичный вектор, назовем его l. Вектор p будет вектором от начала координат до точки, где пользователь нажал мышкой.
Эти векторы можно представить так:

Внутренний голос подсказывает нам, что на l существует точка u, в которой l перпендикулярен вектору (ul – p) (который является кратчайшим расстоянием между линией стрелки и точкой нажатия мыши).
Спроектировать p на l можно с помощью скалярного произведения u = p l. Математически, скалярное произведение выглядит так:

u = lx * px + ly * py;
 (7 Кб)

Ничего страшного, если u получиться отрицательным числом, это значит, что пользователь нажал мышкой на другой стороне от линии стрелки, поэтому следует проигнорировать знак. Теперь можно найти расстояние до точки. Если расстояние (в степени 2) получилось меньше 5 пикселей (в степени 2), можно установить свойство dragging в true (мы возвели значения в квадрат, чтобы потом не выполнять медленную функцию sqrt()).

Теперь нам нужно реализовать обработку сигналов motion_notify_event и button_release_event. В обоих обработчиках много одинакового кода, поэтому вынесем его в отдельную функцию emit_time_changed_signal(). Геометрия этой функции простая противоположность, той, которую мы использовали при вырисовывании стрелок.

Собственно сигнал об изменении времени посылается всем слушателям функцией g_signal_emit(). Вы, наверное, уже заметили переменную minute_offset, мы используем её для того, чтобы узнать на сколько смещена минутная стрелка относительно текущего времени. Это смещение будет добавлено к любому запросу текущего времени. Реализация такого же смещения для часовой стрелки оставляется на долю читателя.
После всего этого, наши два обработчика будут иметь простой вид:

Конечно, чтобы узнать, когда появляется созданный нами сигнал, в файле “main.c " нужно подключить его обработчик:

В итоге, совместив все файлы, у нас получатся clock.c, clock.h, main.c, clock-marshallers.list и Makefile.


Вот и все! Теперь вы знаете, как реализовать GObject, рисовать внутри этого GObject, добавлять поля, сигналы, и анимировать объект. Это, пожалуй, все, что вам понадобиться при создании вашего собственного виджета GtkWidget.


 
Много файлов (3). [Показать файлы/форму]
Комментариев нет. [Показать комментарии/форму]