Spec-Zone.ru › GTK 3.24

Модель обработки ввода и событий GTK+

Модель обработки ввода и событий GTK+ — подробное описание обработки ввода и событий в GTK+

Обзор обработки ввода и событий в GTK+

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

Устройства и события

Наиболее базовые устройства ввода, с которыми каждый пользователь компьютера взаимодействовал, — это клавиатура и мышь; помимо этого, GTK+ поддерживает тачпады, сенсорные экраны и более экзотические устройства ввода, такие как графические планшеты. Внутри GTK+ каждое такое устройство ввода представлено объектом GdkDevice.

Для упрощения работы с различной функциональностью этих устройств ввода, GTK+ имеет понятие мастер- и slave-устройств. Конкретные физические устройства, имеющие множество различных характеристик (мыши могут иметь 2, 3 или 8 кнопок, клавиатуры имеют различные раскладки и могут или не могут иметь отдельную цифровую клавиатуру и т.д.), представлены как slave-устройства. Каждое slave-устройство связано с виртуальным мастер-устройством. Мастер-устройства всегда представлены парами указатель/клавиатура — вы можете представить такую пару как «место».

Виджеты GTK+ обычно работают с мастер-устройствами и, следовательно, могут использоваться с любым устройством указания или клавиатурой.

Когда пользователь взаимодействует с устройством ввода (например, перемещает мышь или нажимает клавишу на клавиатуре), GTK+ получает события от системы окон. Эти события, как правило, направляются на конкретное окно — для событий указателя, на окно под указателем (захваты усложняют эту ситуацию), для событий клавиатуры — на окно с фокусом клавиатуры.

GDK преобразует эти исходные события системы окон в GdkEvents. Типичные события ввода:

GdkEventButton
GdkEventMotion
GdkEventCrossing
GdkEventKey
GdkEventFocus
GdkEventTouch

Кроме того, GDK/GTK синтезирует другие сигналы, чтобы указать, захватывает ли захват (системный или в приложении) ввод:

GdkEventGrabBroken
«grab-notify»

При инициализации GTK+ он устанавливает обработчик событий с помощью gdk_event_handler_set(), который получает все эти события ввода (а также другие, например, связанные с управлением окнами).

Распространение событий

Для виджетов, у которых установлен GdkWindow, события принимаются от системы окон и передаются в gtk_main_do_event(). См. документацию для получения подробностей о его работе: сжатие событий enter/leave, идентификация виджета, получающего событие, размещение события в стеке для gtk_get_current_event() и распространение события на виджет.

Когда GDK-бекенд генерирует событие ввода, оно привязано к GdkDevice и GdkWindow, которые, в свою очередь, представляют поверхность системы окон в бэкенде. Если виджет захватил текущее устройство ввода или все устройства ввода, событие передаётся этому GtkWidget. В противном случае оно передаётся виджету GtkWidget, который вызвал gtk_widget_register_window() в GdkWindow, получившем событие.

Захваты реализованы для каждого устройства ввода и глобально. Событие для конкретного устройства ввода (gtk_device_grab_add()) обрабатывается в приоритете перед глобальным захватом (gtk_grab_add()). Захваты ввода действуют только в пределах GtkWindowGroup, содержащего GtkWidget, который зарегистрировал GdkWindow события. Если этот GtkWidget является дочерним элементом виджета захвата, событие передаётся дочернему элементу — это основа для распространения событий внутри модальных диалогов.

Событие передаётся виджету с помощью gtk_propagate_event(). Распространение различается в зависимости от типа события: события нажатия клавиш (GDK_KEY_PRESS, GDK_KEY_RELEASE) доставляются на верхний уровень GtkWindow; другие события распространяются вниз и вверх по иерархии виджетов в трёх фазах (см. GtkPropagationPhase).

Для событий нажатия клавиш обработчики сигналов по умолчанию для верхнего окна «key-press-event» и «key-release-event» сначала обрабатывают мнемоники и акселераторы. Затем другие нажатия клавиш передаются в gtk_window_propagate_key_event(), которое распространяет событие вверх от текущего виджета с фокусом окна (gtk_window_get_focus()) до верхнего уровня.

Для других событий на первой фазе («фаза захвата») событие передаётся каждому виджету сверху вниз (например, верхнему GtkWindow или виджету захвата) до целевого GtkWidget. Жесты, прикреплённые с помощью GTK_PHASE_CAPTURE, получают возможность реагировать на событие.

После «фазы захвата» виджет, который должен был стать целью события, выполнит прикреплённые к нему жесты с помощью GTK_PHASE_TARGET. Это известно как «фаза назначения» и происходит только в этом виджете.

Далее генерируется сигнал «event», а затем соответствующий сигнал для данного события, например «motion-notify-event». Обработка этих сигналов была основным способом обработки ввода в виджетах GTK+ до появления жестов. Если виджет реализован, генерируется сигнал «event-after». Сигналы генерируются от целевого виджета до верхнего уровня как часть «фазы всплытия».

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

События не доставляются виджету, который не чувствителен или не отображён.

В любой момент во время фазы распространения виджет может указать, что полученное событие было обработано, и поэтому распространение должно быть остановлено. В традиционных обработчиках событий это подразумевается возвращением GDK_EVENT_STOP. Если используются жесты, это может произойти, когда виджет сообщает жесту о том, чтобы он захватил последовательность событий касания (или события указателя) для себя. См. раздел «состояния жестов» ниже, чтобы узнать больше об этом.

Маски событий

Каждый экземпляр виджета имеет базовую маску событий и другую для каждого устройства ввода, которые определяют типы событий ввода, которые он получает. Каждая маска событий, установленная на виджете, добавляется к соответствующей (базовой или по-устройству) маске событий для окна GdkWindow виджета и всех дочерних окон GdkWindows.

Если виджет не имеет окна (gtk_widget_get_has_window() возвращает FALSE) и приложение хочет получать пользовательские события на нём, его необходимо поместить в GtkEventBox для получения событий, и на ящике должна быть установлена соответствующая маска событий. При реализации виджета используйте GDK_INPUT_ONLY окно GdkWindow для получения событий вместо этого.

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

Это означает, что виджеты должны добавлять к маске событий каждый тип события, который они ожидают получить, используя gtk_widget_set_events() или gtk_widget_add_events() для сохранения существующей маски. Виджеты, которые знают о плавающих устройствах, должны использовать gtk_widget_set_device_events() или gtk_widget_add_device_events(), и должны явно включить устройство, используя gtk_widget_set_device_enabled(). См. документацию GdkDeviceManager для получения дополнительной информации.

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

События касания

События касания генерируются как события типа GDK_TOUCH_BEGIN, GDK_TOUCH_UPDATE или GDK_TOUCH_END, эти события содержат «последовательность событий», которая однозначно идентифицирует физическое касание до того, как оно будет снято с устройства.

На некоторых платформах системных окон многосенсорные устройства выполняют эмуляцию указателя. Это достигается присвоением «подсказки эмуляции указателя» одной из взаимодействующих последовательностей касаний, которая будет сообщаться в каждом событии GdkEventTouch из этой последовательности. По умолчанию, если виджет не запросил события касания, установив GDK_TOUCH_MASK в своей маске событий и не переопределил “touch-event”, GTK+ преобразует эти события «эмуляции указателя» в семантически похожие события GdkEventButton и GdkEventMotion. В зависимости от наличия GDK_TOUCH_MASK в маске событий, последовательности, не эмулирующие указатель, всё ещё могут вызывать жесты или просто отфильтровываться, независимо от того, обрабатывает ли виджет их напрямую.

Если виджет устанавливает GDK_TOUCH_MASK в своей маске событий и не обрабатывает “touch-event”, будут получены только события касания, и эмуляция указателя не будет производиться.

Захват

Захват — это метод, позволяющий захватить все события ввода с устройства, который может происходить либо неявно для указателей и сенсорных устройств, либо явно. Неявный захват происходит при взаимодействии пользователя, когда происходит событие GdkEventButtonPress, все события до соответствующего GdkEventButtonRelease будут сообщаться виджету, получившему первое событие. Точно так же, при событиях касания каждое GdkEventSequence будет передавать только события виджету, который получил событие GDK_TOUCH_BEGIN.

Явный захват происходит программно (как активация, так и деактивация) и может быть как глобальным (захват GDK), так и прикладным (захват GTK). На платформах системных окон, которые его поддерживают, захват GDK будет предотвращать любое взаимодействие с любым другим приложением/окном/виджетом, кроме захватывающего, в то время как захват GTK будет эффективен только в рамках приложения (через все его окна), всё ещё позволяя взаимодействовать с другими приложениями.

Но важный аспект захватов состоит в том, что они могут произойти в любой момент где-то ещё, даже когда указатель/сенсорное устройство уже захвачено. Это делает необходимым для виджетов обработку отмены любого текущего взаимодействия. В зависимости от того, является ли это захватом GTK или GDK, виджет соответственно получит сигнал “grab-notify” или событие GdkEventGrabBroken.

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

Ввод с клавиатуры

Контроллеры событий и жесты

Контроллеры событий — это автономные объекты, которые могут выполнять определённые действия при получении событий GdkEvents. Они связаны с GtkWidget и могут узнать фазу распространения события, в которой они будут управлять событиями.

Жесты — это набор специальных контроллеров, которые подготовлены к обработке событий указателя и/или касания. Каждая реализация жеста пытается распознать определённые действия из полученных событий, сообщая об их состоянии/прогрессе, чтобы позволить виджету реагировать на них. При многосенсорных жестах каждая взаимодействующая последовательность касаний будет отслеживаться независимо.

Поскольку жесты являются «простыми» единицами, нередко бывает необходимо связать несколько жестов для выполнения действий более высокого уровня. Жесты-группы обрабатывают одни и те же последовательности событий одновременно, и эти последовательности разделяют одно состояние между всеми жестами-группами. Примеры группирования:

Жесты «перетаскивание» и «свайп» могут потребовать группирования. Первый будет сообщать события по мере перетаскивания, второй — скорости свайпа по осям X/Y только после завершения жеста.
Группирование жеста «перетаскивание» с жестом «панорамирование» позволит перетаскивать только в направлении панорамирования, поскольку оба жеста разделяют состояние.
Если нужно одновременно использовать «нажатие» и «нажатие и удержание», они потребуют группирования.

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

Жесты имеют понятие «состояния» для каждой индивидуальной последовательности касаний. Когда события от последовательности касаний впервые получаются, последовательность касаний имеет состояние «нет», что означает, что последовательность касаний обрабатывается жестом для возможного запуска действий, но распространение события не останавливается.

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

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

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

© 2005–2020 The GNOME Project
Licensed under the GNU Lesser General Public License version 2.1 or later.
https://developer.gnome.org/gtk3/3.24/chap-input-handling.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API