3.2.5.2. Использование action-процедур.

    Описанный в предыдущем пункте механизм использования callback-процедур достаточно прост и удобен, но имеет и существенный недостаток. В частности, указанные процедуры не могут вызываться в ответ на действие, не определенное для данного класса объектов. Например, в OSF/Motif класс XmDrawingArea (область для рисования) поддерживает список callback- процедур, вызываемый, когда widget получает фокус ввода. Однако, этого явно недостаточно, чтобы реализовать более сложное взаимодействие с пользователем.

    Наряду с callback-процедурами, Xt Intrinsics поддерживает еще один механизм, позволяющий пользователю связывать код программы с widget. Указанный механизм состоит в использовании процедур - "ответов на действия" (action-процедур).

    Как уже отмечалось ранее, action-процедура - это функция, которая вызывается в ответ на определенные события, порождаемые сервером. Они, в свою очередь, могут быть связаны с теми или иными действиями пользователя - нажатием на кнопку мыши, нажатие клавиш клавиатуры и т. д.

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

  1. Сначала создается специальная "таблица действий" (action table), связывающая символические имена "действий" (action) с указателями на соответствующие action-процедуры.
  2. Регистрация созданной "таблицы". Для этого можно использовать процедуру XtAppAddActions( ) или XtAddActions( ).
  3. Создание "таблицы соответствия" (мы также будем употреблять термин "таблица трансляции", английский эквивалент - translation table). Каждый ее элемент есть пара "последовательность событий / имя (или имена) "действия". "Таблица" размещается в файле ресурсов программы или непосредственно в ее коде. В последнем случае перед использованием "таблица" должна быть переведена процедурой XtParseTranslationTable( ) во внутренне представление Xt и зарегистрирована для объекта с помощью XtAugmentTranslations( ) или XtOverrideTranslations( ). Подробно формат "таблицы соответствия" описан ниже.

    С учетом представленных шагов вызов action-процедуры, когда в widget происходит какое-то событие (события) , производится следующим образом:

    Рассмотрим более подробно, что представляют из себя "таблица действий" и "таблица трансляции".

    Первая из них - это массив структур типа XtActionRec, который определяется следующим образом:

typedef struct {
   String	     string;
   XtActionProc	     proc;
} XtActionRec,      *XtActionRecList;

    Здесь первое поле есть символическое имя "действия". Указатель на соответствующую процедуру задается полем proc. Например, "таблица действий", содержащая процедуру KeyboardPrint( ), вызываемую, когда происходит "действие" с именем "KeyboardPrint", может быть определена следующим образом:

static void KeyboardPrint( );
static XtActionRec paAct[ ] = {
	{ "KeyboardPrint", KeyboardPrint },
};

    Сделаем ряд замечаний.

    Если используется множество управляющих элементов OSF/Motif, то следует помнить, что каждый класс widget указанного множества предусматривает набор стандартных action- процедур, которые могут использоваться всеми экземплярами объектов данного класса. Функции же, регистрируемые с помощью XtAppAddActions( ) (XtAddActions( )), могут быть использованы произвольным widget любого класса данной программы.

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

    Имена "действия" и реализующей его процедуры в принципе могут не иметь ничего общего. Но для удобства принято, чтобы они совпадали.

    Каждая action-процедура должна иметь следующий прототип:

void ActionProc (Widget prW, XEvent *prEvent,
	String *psParams, Cardinal *nNumParams);

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

    Как уже указывалось ранее, после регистрации action-процедуры, например, с помощью XtAppAddActions( ), последнюю могут использовать произвольные widget данной программы. У каждого объекта есть ресурс XtNtranslations, значение которого - "таблица трансляции", представленная во внутреннем формате Xt.

    В ресурсном файле или в исходном коде программы эта "таблица" представляется как строка, содержащая пары "последовательность событий / имя (или имена) действия", разделенные символом '\n'. В начале строки может стоять директива "#replace", "#augment" или "#override". Первая "говорит", что "таблица" должна заменить уже имеющуюся у widget "таблицу". Вторая и третья означают, что новая "таблица" должна быть объединена с "таблицей", уже имеющейся у widget. При этом, если указана директива "#augment", то, если в "таблицах" имеются записи, соответствующие одному и тому же "действию", то приоритет имеет старая запись. Если указана директива "#override", наоборот - новая.

    Каждая пара "последовательность событий / имя (или имена) действия" (в дальнейшем мы будем использовать также термин "трансляция") имеет следующий вид:

[модификатор, . . , модификатор]<событие>, . .<событие>[ (число 
повторений события)] [детализация] : имя action([аргументы]) 
[имя action([ аргументы)  . . . . ] 

Здесь элементы, заключенные в индексные скобки[ ] , являются необязательными.

    В поле "модификатор" могут использоваться стандартные модификаторы, поддерживаемые Xt Intrinsics, например: Ctrl, Shift, Lock и др. Список используемых в "таблице трансляции" модификаторов приводится ниже.

МодификаторОписание
CtrlКлавиша Control
ShiftКлавиша Shift
LockКлавиша Caps Lock
MetaКлавиша Meta
HyperКлавиша Hyper
SuperКлавиша Super
AltКлавиша Alt
Mod1, . . . , Mod5Дополнительные клавиши-модификаторы
Button1, . . . , Button5Кнопки мыши

    В поле <событие> должно стоять имя события X Window. Эти имена приведены в приложении 1Ссылка.

    Если модификаторов в "трансляции" нет, то указанные в записи "действия" и соответствующие им процедуры будут вызываться в ответ на определенные в угловых скобках события или последовательности событий независимо от того, в каком состоянии находятся клавиши-модификаторы и кнопки мыши, например:

<KeyPress> : KeyboardPrint( )

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

    Для конкретизации различных событий предусмотрено специальное поле "детализация". Так для событий от клавиатуры в этом поле указывается символ клавиши, определенный в файле "keysymdef.h" (префикс "XK_" опускается). Например, следующая запись

<KeyPress>a : KeyboardPrint( )

используется для определения события, соответствующего нажатию клавиши "a". Для событий ButtonPress, ButtonRelease, MotionNotify "детализация" есть номер нажатой кнопки.

    Для событий EnterNotify, LeaveNotify, FocusIn, FocusOut в поле "детализация" может быть использован любой из следующих символов:

Normalдля событий с обычным режимом получения и передачи фокуса ввода;
Grab для событий, когда устройство "захвачено";
Ungrabдля событий, возникающих при отмене "захвата".

Для событий ClientMassage "детализация" - тип сообщения (число).

    Для того, чтобы облегчить задание "таблицы трансляций", Xt предусматривает специальную аббревиатуру для некоторых событий.

АббревиатураСмысл
CtrlСобытие KeyPress, когда нажата клавиша "Control".
MetaСобытие KeyPress, когда нажата клавиша "Meta".
ShiftСобытие KeyPress, когда нажата клавиша "Shift".
Btn1Down, . . ., Btn5DownСобытие ButtonPress для 1, . . ., 5-й кнопки мыши.
Btn1Up, . . ., Btn5UpСобытие ButtonRelease для 1, . . ., 5-й кнопки мыши.
BtnMotionСобытие MotionNotify.
Btn1Motion, . . ., Btn5MotionСобытие MotionNotify когда нажата 1, . . ., 5-й кнопка мыши.

Например, запись:

<Btn2Down> : StartWindowing( )

означает, что в ответ на нажатие второй кнопки мыши, вызывается "действие" StartWindowing( ).

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

СимволОписание
None Ни одна клавиша-модификатор не должна быть нажата.
! Событие должно сопровождаться только указанными модификаторами и никакими другими.
: Позволяет различать события от нажатия клавиш в зависимости от регистра.
~ Модификатор, непосредственно следующий за данным символом, не должен быть в нажатом состоянии.

Например, запись

!Alt<Key>a : KeyboardPrint( )

означает, что соответствующая процедура будет вызвана только при нажатии комбинации <Alt+a>. Применение символа "None" эквивалентно использованию в "трансляции" символа "!" без модификаторов, например, следующие записи эквивалентны:

None<Key>a : KeyboardPrint( )

и

!<Key>a : KeyboardPrint( )

Это означает, что "действие" будет реализовано, когда клавиша "a" нажата, а все модификаторы находятся в не нажатом состоянии.

    Использование в "трансляции" символа ":" позволяет различать события, соответствующие нажатию клавиш на разных регистрах: верхнем или нижнем (т.е. при нажатой или освобожденной клавише Shift или Caps Lock). Дело в том, что, например, следующие записи эквиваленты по выполняемому действию:

<Key>b : KeyboardPrint( )

и

<Key>B : KeyboardPrint( )

т.е. соответствуют вызову процедуры при нажатии клавиши "b" или комбинации <Shift+b>. Использование символа ":" позволяет различить эти две ситуации, в частности, следующие записи будут уже не идентичны:

: <Key>b : KeyboardPrint( )

и

: <Key>B : KeyboardPrint( )

первая определяет вызов соответствующей процедуры при нажатии клавиши "b"; вторая же "трансляция" соответствует вызову функции при нажатии комбинации < Shift+b>.

    Символ "~" используется для отрицания модификаторам т.е. следующий за символом модификатор не должен быть нажат. Например запись:

Button2<Key> : KeyboardPrint( )

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

~Button2<Key> : KeyboardPrint( )

"говорит", что соответствующая функция будет вызвана, когда нажимается произвольная клавиша, и, если при этом не нажата вторая кнопка мыши. Заметим, что использование символа "~" относится только к модификатору, непосредственно следующему за указанным символом, и не распространяется на остальные модификаторы. Например, "трансляция"

~Ctrl Alt<Key>a : KeyboardPrint( )

указывает, что соответствующая функция будет вызвана при нажатии комбинации <Alt+a>, если при этом не нажата клавиша Control.

    Модификаторы, расположенные в начале "трансляции", относятся ко всем событиям, указанным в ней. Например, следующие записи эквивалентны:

Shift<Btn2Down>, <Btn2Up> : KeyboardPrint( )

и

Shift<Btn2Down>, Shift<Btn2Up> : KeyboardPrint( )

    Для определения в "трансляциях" событий от кнопок мыши используются модификаторы Button1, Button2, Button3, Button4 и Button5, например, следующая запись

Button2<Key>a : KeyboardPrint( )

означает, что соответствующая процедура будет вызвана, когда нажимается вторая кнопка мыши и клавиша "a".

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

<Btn1Down>, <Btn1Up> : KeyboardPrint( )

    Для задания нескольких нажатий и отпусканий используется поле "число повторений события". Так, например, "трансляция"

<Btn2Up> : KeyboardPrint( )		Вероятна опечатка

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

<Btn2Down>, <Btn2Up>, <Btn2Down>, <Btn2Up> :
KeyboardPrint( )

    Для задания большего числа повторений можно использовать символ "+", например, следующая запись

<Btn3Up>(2+) : KeyboardPrint( )

означает, что соответствующая функция будет вызвана при 2-х и более нажатиях на третью кнопку мыши (максимальное число, которое может быть указано в поле "повторений" - 9 ). По умолчанию временной интервал, во время которого считаются нажатия кнопки мыши, составляет 200 миллисекунд. Если между очередным нажатием кнопки и следующим проходит меньше 200 миллисекунд, то система рассматривает это как одно двойное нажатие (double click) соответствующей кнопки мыши. Для изменения указанного интервала используется ресурс объекта XtNmultiClickTime. Он может быть установлен функцией XtSetMultiClickTime( ).

    Поле "аргументы" в "трансляции" используется для задания третьего и четвертого аргументов action-процедуры. Например, если "трансляция" была определена следующим образом:

<Key>a : KeyboardPrint(1, JUSTAS, ALEX)

то при вызове соответствующей функции аргумент psParams будет указывать на список, содержащий строки "1", "JUSTAS" и "ALEX", а аргумент nNumParams будет равен 3.

    Ниже приведены примеры задания "таблицы соответствия" в файле ресурсов

justas*mywidget.translations : #augment \
  	<Btn2Down>              : Arm( )\n\
        <Btn2Down>, <Btn2Up>    : Activate( ) Disarm( )\n\
        <Btn2Down>(2+)          : MultiArm( )\n\
        <Btn2Up>(2+)            : MultiActivate( )\n\
        <LeaveWindow>           : Leave( )

и коде программы:

static  char *psTable : "#augment \
        <Btn2Down>              : Arm( )\n\
        <Btn2Down>, <Btn2Up>    : Activate( ) Disarm( )\n\
        <Btn2Down>(2+)          : MultiArm( )\n\
        <Btn2Up>(2+)            : MultiActivate( )\n\
        <LeaveWindow>           : Leave( )";

    Как мы уже упоминали ранее, если "таблица" задана в коде программы, то перед использованием она должна быть переведена во внутренний формат Xt процедурой XtParseTranslationTable( ). Регистрируется "таблица" с помощью функций

XtAugmentTranslations (Widget prWidget,
	           XtTranslations pTranslationsTable);

или

XtOverrideTranslations (Widget prWidget,
	         XtTranslations pTranslationsTable);

обе они регистрируют "таблицу трансляции" для объекта, заданного widget, объединяя "таблицу" pTranslationsTable, с "таблицей", уже имеющейся у widget. При этом вторая процедура замещает существующие "трансляции" на новые, а первая оставляет их без изменения.

    Следующий пример показывает, как добавить action-процедуру к объекту.

. . . . . . . .
Widget prWidget;
static    XtActionRec actions [ ] = ("PressMe", KeyboardPrint);
static    char  UserTranslations [ ] =
	"Alt <Key>a : PressMe"; 
. . . . . . . .
XtAugmentTranslations (prWidget,
         XtParseTranslationTable (UserTranslations));
. . . . . . . .

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

<Key>             : InputSymbol( ) \n\
<Key>Return       : EndInput( )

то процедура, соответствующая "действию" InputSymbol, будет вызвана при нажатии клавиши <Return>, а функция для EndInput никогда не будет вызвана.