Spec-Zone.ru › GTK 3.22

Модель обработки ввода и событий 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(). Подробности о его работе см. в его документации: сжатие событий входа/выхода, идентификация виджета, получающего событие, помещение события в стек для 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.22/chap-input-handling.html

Spec-Zone.ru

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