Иногда бывает удобно пользоваться "горячими клавишами", настройка которых не зависит от того, окно какого приложения в данный момент активно. О том, как добиться такого эффекта и пойдет здесь речь. Правда, описанные приемы могут оказаться полезными и в других случаях.
Для начала, немного об идеологии Фотона.
Пробел случая :).
Вообще-то, на эту тему есть хорошие статьи, например
[1] и [2]. Но на всякий случай, а отчасти для придания солидности своему труду, добавлю немного теории.
Фирма QSSL придумала красивую аллегорию для описания своей графической оболочки.
Итак, дано:
прозрачный куб (ну, параллелепипед).
Передняя грань, обращенная к зрителю - экран вашего монитора, противоположенная теряется в его недрах.
Внутренний объем параллелепипеда называется пространством событий (event space).
В нем плавают прямоугольники - регионы (regions). Ближе всех к пользователю расположен регион графического драйвера (graphics driver region). За ним, регионы устройств ввода, далее все прочие регионы. За всеми регионами, в противоположенном конце пространства событий расположен корневой (root) регион.
Регионы могут излучать фотоны. Делают они это перпендикулярно своей плоскости. Кроме того, они обладают по отношению к фотонам двумя свойствами - чувствительность (sensitiv) и непрозрачность (opaque).
Если подвигать мышкой, регион драйвера мыши начнет излучать фотоны типа Ph_EV_PTR_MOTION_NOBUTTON (типа, мышь двигается :)). Перемещаем курсор в нужную позицию и нажимаем на левую кнопку. И тут начинается:
- Регион драйвера мыши испускает фотон типа
Ph_EV_BUT_PRESS.
- Этот фотон летит в пространстве, пронзая прозрачные для него регионы, пока не наткнется на непрозрачный. Надо помнить, что независимо от свой прозрачности, регион может быть чувствительным для этого типа фотонов (этим мы позже и воспользуемся).
- Допустим, он наткнулся на регион кнопки (виджет
PtButton). Этот регион испускает фотон типа Ph_EV_DRAW .
- Фотон врезается в регион графического драйвера. В ответ драйвер перерисовывает изображение кнопки, делая ее нажатой.
В действительности все происходит несколько сложнее, но об этом лучше написано в [2].
При использовании клавиатуры все происходит очень похоже. Излучается фотон типа Ph_EV_KEY. Если он встречает на своем пути регион, чувствительный к этому типу фотонов, например виджета PtText, то он вызывает ответную реакцию.
Этих представлений вполне достаточно, чтобы решить нашу задачу.
Надо создать регион, перекрывающий весь экран и чувствительный для фотонов, излучаемых при нажатии на клавиши. Он должен располагаться поверх всех пользовательских регионов, чтобы его никто не затенял и быть прозрачным для всех типов фотонов, чтобы не затенять другие регионы.
Физика элементарных частиц.
Строение фотона или события (event) описывается структурой PhEvent_t (см. файл PhT.h):
typedef struct Ph_event {
unsigned long type;
unsigned short subtype;
unsigned short processing_flags;
PhEventRegion_t emitter;
PhEventRegion_t collector;
unsigned short input_group;
unsigned short flags;
unsigned long timestamp;
PhPoint_t translation;
unsigned short num_rects;
unsigned short data_len;
} PhEvent_t;
Член структуры type определяет тип фотона и может принимать одно из следующих значений:
| | Ph_EV_KEY | 0x00000001 |
| | Ph_EV_BUT_PRESS | 0x00000002 |
| | Ph_EV_BUT_RELEASE | 0x00000004 |
| | Ph_EV_PTR_MOTION_NOBUTTON | 0x00000008 |
| | Ph_EV_PTR_MOTION_BUTTON | 0x00000010 |
| | Ph_EV_BOUNDARY | 0x00000020 |
| | Ph_EV_EXPOSE | 0x00000040 |
| | Ph_EV_DRAW | 0x00000080 |
| | Ph_EV_INPUT_OTHER | 0x00000100 |
| | Ph_EV_DRAG | 0x00000200 |
| | Ph_EV_COVERED | 0x00000400 |
| | Ph_EV_BLIT | 0x00000800 |
| | Ph_EV_SYSTEM | 0x00001000 |
| | Ph_EV_WM | 0x00002000 |
| | Ph_EV_BUT_REPEAT | 0x00004000 |
| | Ph_EV_RAW | 0x00008000 |
| | Ph_EV_TIMER | 0x00010000 |
| | Ph_EV_LB_SYSTEM | 0x00020000 |
| | Ph_EV_SERVICE | 0x00040000 |
| | Ph_EV_INFO | 0x00080000 |
| | Ph_EV_AUDIO | 0x00100000 |
| | Ph_EV_DNDROP | 0x00200000 |
Член структуры data_len содержит размер данных, связанных с событием.
А для того чтобы получить указатель на эти данные, существует функция PhGetData():
void *PhGetData( PhEvent_t const *event );
С каждым типом события связаны свои данные. Например для типа Ph_EV_PTR_MOTION_NOBUTTON (перемещение мыши, не нажимая кнопки), это структура PhPointerEvent_t , описанная в PhT.h:
typedef struct Ph_ev_ptr_data {
PhPoint_t pos;
unsigned short buttons;
unsigned short button_state;
unsigned char click_count;
unsigned char flags;
short z;
unsigned long key_mods;
unsigned long zero;
} PhPointerEvent_t;
При нажатии клавиш излучаются фотоны типа Ph_EV_KEY, а данные выглядят как структура PhKeyEvent_t (опять PhT.h):
typedef struct Ph_ev_key_data {
unsigned long key_mods;
unsigned long key_flags;
unsigned long key_cap;
unsigned long key_sym;
unsigned short key_scan;
unsigned short key_zero;
PhPoint_t pos;
unsigned short button_state;
} PhKeyEvent_t;
Член структуры key_mods - набор битовых флагов, описывающий состояние управляющих клавиш.
Например, Pk_KM_Alt указывает на то, что была нажата клавиша <Alt>.
key_mods содержит информацию о том, какие действия производили с
клавишей - нажимали, отпускали или просто держали нажатой.
Код нажатой клавиши находится в key_cap.
Все константы описаны в файле PkKeyDef.h.
А все подробности можно найти там, где ищут в последнюю очередь :) [3].
К снаряду...
Простой тест.
Напишем простой и бестолковый тест - запуск helpviewer при нажатии комбинации клавиш <Alt>+<F1>.
Создаем регион.
Регион должен перекрывать весь экран. Для этого выясним разрешение экрана. При этом не надо забывать, что на экране монитора видна только одна девятая часть всего пространства, а координата 0,0 соответствует левому верхнему углу первой части.
Текущие настройки экрана можно получить вызвав функцию PgGetVideoMode():
int PgGetVideoMode( PgDisplaySettings_t *settings );
Структура PgDisplaySettings_t описана в Pg.h:
typedef struct {
unsigned mode;
int xres;
int yres;
int refresh;
unsigned flags;
unsigned long reserved[22];
} PgDisplaySettings_t;
Нас интересуют значения xres и yres.
Регион должен быть прозрачен для всех событий и чувствителен для событий типа Ph_EV_KEY.
Регион создается функцией PhRegionOpen():
PhRid_t PhRegionOpen( unsigned fields,
PhRegion_t const *info,
PhRect_t const *rect,
void const *data );
Второй аргумент - структура PhRegion_tPhRegion_t const *info, описывающая свойства региона (все тот же PtT.h).
Первый аргумент unsigned fields - набор битовых флагов, указывающий, какие члены структуры PhRegion_t надо принимать во внимание.
Третий аргумент PhRect_t const *rect, прямоугольник, ассоциируемый с регионом, служит для описания его геометрии.
Четвертый аргумент - указатель на данные связанные с регионом. Он нас не интересует.
Вот текст функции, создающий регион и возвращающий его идентификатор:
PhRid_t create_region()
{
PhRegion_t region;
PhRect_t rect;
PhRid_t rid;
PgDisplaySettings_t settings;
// выясняем размеры экрана
PgGetVideoMode(&settings);
// регион чувствителен к событиям типа Ph_EV_KEY
region.events_sense = Ph_EV_KEY;
// устанавливаем "родителя" нашего региона
region.parent = Ph_ROOT_RID;
// левый верхний угол региона по отношению к родительскому региону
region.origin.x = region.origin.y = 0;
// геометрия региона
rect.ul.x = 0; // левый верхний угол региона - в левом верхнем углу экрана
rect.ul.y = 0;
rect.lr.x = settings.xres * 3; // регион перекрывает все пространство
rect.lr.y = settings.yres * 3;
// открытие региона
rid = PhRegionOpen( Ph_REGION_PARENT |
Ph_REGION_EV_SENSE |
Ph_REGION_ORIGIN |
Ph_REGION_RECT,
®ion, &rect, NULL );
return( rid );
}
Прием событий.
Для приема событий предназначена функция PhEventNext():
Первый аргумент - указатель на буфер под принимаемые события, второй - размер этого буфера. Указывать размер буфера надо так, чтобы туда поместились PhEvent_t и PhKeyEvent_t с учетом выравнивания.
В справке на эту функцию приведен пример, где размер указывается просто с запасом:
PhEventNext( event, sizeof( PhEvent_t ) + 1000 ) );
Этим же рецептом воспользуемся и при выделении памяти под сообщение:
event = (PhEvent_t *)malloc( sizeof( PhEvent_t ) + 1000 );
Ниже приведен вариант функции main():
#include <stdio.h>
#include <Ph.h>
int main()
{
PhEvent_t *event = 0;
PhKeyEvent_t *key_struct = 0;
int ret = 0;
// инициализация библиотеки Photon
if(-1 == PtInit(NULL))
{
fprintf( stderr, "Couldn't initialize the widget library.\n" );
exit( EXIT_FAILURE );
}
// создаем регион
if(-1 == create_region())
{
fprintf( stderr, "Couldn't create region.\n" );
exit( EXIT_FAILURE );
}
// резервируем память для сообщений
event = (PhEvent_t *)malloc(sizeof(PhEvent_t) + 1000);
if(!event)
{
fprintf( stderr, "Couldn't allocate event buffer.\n" );
exit( EXIT_FAILURE );
}
// цикл приема сообщений
for(;;)
{
if(Ph_EVENT_MSG == PhEventNext(event, sizeof(PhEvent_t) + 1000 ))
{
//если это событие от клавиатуры
if(Ph_EV_KEY == event->type)
{
// добываем данные принесенные событием
key_struct = PhGetData( event );
// реагируем только на первое нажатие клавиши
// игнорируем повторы при удержании
if((key_struct->key_flags & Pk_KF_Key_Down) && !(key_struct->key_flags & Pk_KF_Key_Repeat))
if((Pk_KM_Alt == key_struct->key_mods) && (Pk_F1 == key_struct->key_cap))
PtHelpTopic("/Photon microGUI for QNX 6/Library Reference/Ph---Photon/PhRegionOpen");
}
}
}
}
Откомпилируем наш пример:
$ cc -l ph -o my_test my_test.c
И запустим:
$ ./my_test
Использование в приложениях.
Обычно в приложениях для приема событий используется не PhEventNext(), а функция PtMainLoop().
С этим связан ряд отличий: Открытие региона сводится в созданию виджета PtRegion.Функции приема событий и вызова обработчиков берет на себя PtMainLoop().Для того, чтобы присоединить обработчик события, используется функция PtAddEventHandler().
Исходя из это, функция создания региона теперь будет выглядеть так:
PtWidget_t *create_region()
{
PtWidget_t *region = 0;
PtArg_t args[10];
PhArea_t area;
unsigned arg_count = 0;
PgDisplaySettings_t settings;
// выясняем размеры экрана
PgGetVideoMode(&settings);
// геометрия региона
area.pos.x = 0; // левый верхний угол региона - в левом верхнем углу экрана
area.pos.y = 0;
area.size.w = settings.xres * 3; // регион перекрывает все пространство
area.size.h = settings.yres * 3;
// геометрия региона
PtSetArg(&args[arg_count++], Pt_ARG_AREA, &area, 0 );
// чувствительность в событиям типа Ph_EV_KEY
PtSetArg(&args[arg_count++], Pt_ARG_REGION_SENSE, Ph_EV_KEY, Ph_EV_KEY);
// прозрачный фон (по умолчанию он серый)
PtSetArg( &args[arg_count++], Pt_ARG_FILL_COLOR, Pg_TRANSPARENT, 0 );
// создание виджета
region = PtCreateWidget( PtRegion, NULL, arg_count, args );
// добавляем обработчик события
PtAddEventHandler( region, Ph_EV_KEY, key_pressed_cb, NULL );
PtRealizeWidget( region );
return( region );
}
Ниже приведен пример функции обработки событий. Обратите внимание на то, как передается указатель на событие PhEvent_t. Это член структуры PtCallbackInfo_t.
int key_pressed_cb( PtWidget_t *widget, void *data, PtCallbackInfo_t *cbinfo )
{
PhKeyEvent_t *key_struct = PhGetData(cbinfo->event);
// реагируем только на первое нажатие клавиши
// игнорируем повторы при удержании
if((key_struct->key_flags & Pk_KF_Key_Down) && !(key_struct->key_flags & Pk_KF_Key_Repeat))
if((Pk_KM_Alt == key_struct->key_mods) && (Pk_F1 == key_struct->key_cap))
PtHelpTopic("/Photon microGUI for QNX 6/Library Reference/Ph---Photon/PhRegionOpen");
return( Pt_CONTINUE );
}
И наконец, новый вариант функции main():
#include <stdio.h>
#include <Ph.h>
#include <Pt.h>
int main()
{
// инициализация библиотеки Photon
if(-1 == PtInit(NULL))
{
fprintf( stderr, "Couldn't initialize the widget library.\n" );
exit( EXIT_FAILURE );
}
// создаем регион
if(!create_region())
{
fprintf( stderr, "Couldn't create region.\n" );
exit( EXIT_FAILURE );
}
// цикл приема событий
PtMainLoop();
}
Что можно сделать еще?
Можно, например, создавать свой "ловчий" регион с индивидуальными настройками для каждого из девяти экранов.
Отслеживая координаты мыши, можно сделать "выпадающую" панель, на подобии Shelf, хотя для этого, наверное, есть и более простые средства.
И еще. Результаты ваших исследований поможет проанализировать утилита phview.
Литература.
|