Перемещение виджетов и построение собственных виджетов на примере "двуручного" слайдера.
Дмитрий Сошин <dmitry@mutex.ru>
Версия для печати
- Половина первая. Перемещение виджетов.
- Половина вторая. Построение виджетов.
- Две половины. Виджет - слайдер с двумя ручками.
- На последок...
- Литература.
Еще со времен Александра Македонского известно, что почти любую проблему можно решить методом деления пополам.
Половина первая. Перемещение виджетов.
"Она может двигать собой... в полный рост..." Б.Г.
В терминологии QSSL это называется "widgets dragging", а на русский переводится "дрыганье предметами":). Сразу замечу, что "widgets dragging" не следует путать с drag-n-drop.
Эта глава написана на основе примера ftp.qnx.com/usr/free/qnx4/photon/examples/exdrag.tgz.
Хотя пример написан для Photon 1.xx, он вполне пригоден, с несколькими исправлениями, и для Photon 2.xx.
Во-первых, изменился набор аргументов функции PhInitDrag()
- добавились еще два:
const PhPoint t *ptrpos и
const PhCursorDescription t *cursor.
Во-вторых, QSSL рекомендует больше не использовать ряд функций и предлагает заменить их другими (см. таблицу).
|
Старый вариант |
Новый вариант |
|
|
|
|
PtBasicWidgetCanvas |
PtCalcCanvas |
|
PtDeTranslateRect |
PhDeTranslateRect |
|
PtTranslateRect |
PhTranslateRect |
Запустим PhAB, создадим в нем модуль (базовое окно), в модуле - контейнер (PtPane), а в контейнере - дочерний виджет (PtButton).
Попробуем потаскать дочерний виджет внутри контейнера-родителя.
Все происходит свободно и легко. Виджет можно утащить даже за пределы видимой части контейнера и где-то там его потерять:).
В общих чертах это работает так:
Первое прикосновение к виджету мышкой (событие Ph_EV_BUT_PRESS), служит сигналом - мы собираемся его двигать. Обработчик этого события вызывает функцию PhInitDrag(). Эта функция инициализирует процесс перемещения. Теперь, если перемещать мышь, не отпуская кнопки, будут испускаться события Ph_EV_DRAG. События принимаются контейнером, в результате вызывается их обработчик, который и изменяет координаты дочернего виджета.
Итак, для инициализации процесса перемещения надо вызвать функцию PhInitDrag():
int PhInitDrag( PhRid t rid,
unsigned flags,
const PhRect t *rect,
const PhRect t *boundary,
unsigned int input_group,
const PhDim t *min,
const PhDim t *max,
const PhDim t *step,
const PhPoint t *ptrpos,
const PhCursorDescription t *cursor );
Нас интересуют первые пять аргументов.
PhRid t rid - идентификатор региона, который будет улавливать события Ph_EV_DRAG. Это регион контейнера и его идентификатор можно получить вызвав функцию PtWidgetRid().
unsigned flags - набор флагов, определяющий
поведение при перемещении. Если установить комдинацию Ph_TRACK_DRAG |Ph_DRAG_TRACK, мы сможем перемещать дочерний виджет в любом направлении, и перемещаться будет весь виджет. Если не устанавливать Ph_DRAG_TRACK, перемещаться будет только рамка.
const PhRect t *rect - перемещаемый прямоугольник, то есть прямоугольник, ассоциируемый с дочерним виджетом.
const PhRect t *boundary - область, в которой можно перемещать виджет. Если края этого прямоугольника совпадают с границами контейнера, перемещаемый виджет нельзя будет утащить за его край.
unsigned int input_group - входная группа, ассоциируемая с событием.
В качестве оставшихся аргументов передадим NULL.
С третьим и четвертым аргументами придется повозиться. Во-первых, их надо получить. Во-вторых, привезти к единой системе координат.
Начнем с контейнера. Нет, начнем с того, из чего этот контейнер состоит. Вот рисунок из раздела
Programmer's Guide - Introduction - Widget concepts - Widget geometry, документации на Фотон.
Перемещать виджет будем во внутренней области контейнера, называемой Canvas. Получить описание этой прямоугольной области можно с помощью функции PtCalcCanvas(). Регион контейнера привязан к внешнему прямоугольнику. Для получения описания внешнего прямоугольника, применим макрос PtWidgetArea(). Чтобы учитывалась поправка на ширину границ (border) и отступов (margin), применим функцию PhDeTranslateRect().
Теперь возьмемся за дочерний виджет. Сначала получим его размеры с помощью макроса PtWidgetExtent(). Затем, приведем его к системе координат контейнера, вызвав PhTranslateRect().
Ниже приведен полный текст функции инициализации движения.
PtWidget_t *dragging_wgt; // место для хранения указателя на перемещаемый виджет
int initiateDragging_cb(PtWidget_t *widget, void *data, PtCallbackInfo_t *cbinfo)
{
PhArea_t da_area; // внешний прямоугольник контейнера
PhRect_t da_canvas; // внутренний прямоугольник контейнера
PhRect_t dw_rect; // перемещаемый прямоугольник
PtWidget_t *container_wgt; // контейнерный виджет
dragging_wgt = widget; // виджет, который будет перемещаться
container_wgt = PtWidgetParent( widget ); // контейнерный виджет
// получаем внутренний прямоугольник контейнера
PtCalcCanvas(container_wgt, &da_canvas);
// получаем внешний прямоугольник контейнера
PtWidgetArea(container_wgt, &da_area);
// пересчитываем координаты
PhDeTranslateRect(&da_canvas, &da_area.pos);
// получаем внешний прямоугольник перемещаемого виджета
PtWidgetExtent(dragging_wgt, &dw_rect);
// пересчитываем координаты
PhTranslateRect(&dw_rect, &da_canvas.ul);
// инициализация процесса перемещения
PhInitDrag( PtWidgetRid(container_wgt),
Ph_TRACK_DRAG | Ph_DRAG_TRACK,
&dw_rect,
&da_canvas,
cbinfo->event->input_group,
NULL, NULL, NULL, NULL, NULL);
return( Pt_CONTINUE );
}
Теперь напишем обработчик события Ph_EV_DRAG.
Собственно события описываются структурой 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_DRAG, а subtype одно из значений, описаных в том же PhT.h:
#define Ph_EV_DRAG_INIT 0
#define Ph_EV_DRAG_MOVE 1
#define Ph_EV_DRAG_COMPLETE 2
#define Ph_EV_DRAG_KEY_EVENT 3
#define Ph_EV_DRAG_MOTION_EVENT 4
#define Ph_EV_DRAG_BOUNDARY 5
#define Ph_EV_DRAG_START 6
Нас интересует подтип Ph_EV_DRAG_MOVE.
Событие Ph_EV_DRAG описывается структурой PhDragEvent_t (см. файл PhT.h):
typedef struct Ph_ev_drag_data
{
PhRect_t rect;
PhRid_t rid;
PhRect_t boundary;
PhDim_t min;
PhDim_t max;
PhDim_t step;
PhPoint_t pos;
unsigned long key_mods; /* same as in PhKeyEvent_t */
long zero[2];
ushort_t flags;
unsigned short button_state; /* same as in PhRawPtrEvent_t */
} PhDragEvent_t;
Приведенное в документации описание несколько отличается, но в свете нашей задачи это роли не играет. Нам интересен только первый член структуры PhRect_t rect. Он содержит координаты перемещаемого прямоугольника.
Вот пример обработчика событий:
int dragging_cb(PtWidget_t *widget, void *data, PtCallbackInfo_t *cbinfo)
{
PhArea_t da_area; // внешний прямоугольник контейнера
PhRect_t da_canvas; // внутренний прямоугольник контейнера
PhRect_t dw_rect; // перемещаемый прямоугольник
PtWidget_t *container_wgt; // контейнерный виджет
PhDragEvent_t *evdata; // указатель на данные, пришедшие с событием
container_wgt = widget;
// нас интересует только процесс движения виджета
// все остальные подтипы отфильтровываются.
if(cbinfo->event->subtype != Ph_EV_DRAG_MOVE)
return(Pt_CONTINUE);
// получаем данные пришедшие с событием
evdata = PhGetData( cbinfo->event );
// evdata->rect содержит абсолютную координату перемещаемого прямоугольника
dw_rect = evdata->rect;
// приводим к системе координат региона контейнера
PhTranslateRect(&dw_rect, &cbinfo->event->translation);
// получаем внутренний прямоугольник контейнера
PtCalcCanvas(container_wgt, &da_canvas);
// получаем внешний прямоугольник контейнера
PtWidgetArea(container_wgt, &da_area);
// пересчитываем координаты
PhDeTranslateRect(&da_canvas, &da_area.pos);
// приводим перемещаемый прямоугольник к системе координат
// внутренней части контейнера
PhDeTranslateRect( &dw_rect, &da_canvas.ul );
// задаем перемещаемому виджету новую координату
PtSetResource(dragging_wgt, Pt_ARG_POS, &dw_rect.ul, 0);
return( Pt_CONTINUE );
}
Теперь напишем функцию создания окна.
int create_window()
{
PtWidget_t *window;
PtWidget_t *pane;
PtWidget_t *button;
PhPoint_t pos = {100, 50};
PhDim_t dim = {260, 34};
PtArg_t args[6];
int arg_count = 0;
int ret = 0;
window = PtCreateWidget(PtWindow, Pt_NO_PARENT, 0, NULL);
PtSetArg(&args[arg_count++], Pt_ARG_CONTRAST, 100, 0);
PtSetArg(&args[arg_count++],
Pt_ARG_BASIC_FLAGS,
Pt_REVERSE_GRADIENT | Pt_TOP_ETCH | Pt_TOP_INLINE |
Pt_BOTTOM_ETCH | Pt_BOTTOM_INLINE | Pt_LEFT_ETCH |
Pt_LEFT_INLINE | Pt_RIGHT_ETCH | Pt_RIGHT_INLINE,
Pt_FLAT_FILL | Pt_REVERSE_GRADIENT | Pt_TOP_ETCH |
Pt_TOP_INLINE | Pt_BOTTOM_ETCH | Pt_BOTTOM_INLINE |
Pt_LEFT_ETCH | Pt_LEFT_INLINE | Pt_RIGHT_ETCH | Pt_RIGHT_INLINE);
PtSetArg(&args[arg_count++], Pt_ARG_FLAGS, Pt_HIGHLIGHTED, Pt_HIGHLIGHTED);
PtSetArg(&args[arg_count++], Pt_ARG_POS, &pos, 0);
PtSetArg(&args[arg_count++], Pt_ARG_DIM, &dim, 0);
pane = PtCreateWidget(PtPane, window, arg_count, args);
arg_count = 0;
pos.x = 0;
pos.y = 0;
dim.w = dim.h = 26;
PtSetArg(&args[arg_count++], Pt_ARG_CONTRAST, 100, 0);
PtSetArg(&args[arg_count++], Pt_ARG_LABEL_TYPE, Pt_IMAGE, 0);
PtSetArg(&args[arg_count++], Pt_ARG_BEVEL_WIDTH, 3, 0);
PtSetArg(&args[arg_count++], Pt_ARG_POS, &pos, 0);
PtSetArg(&args[arg_count++], Pt_ARG_DIM, &dim, 0);
PtSetArg(&args[arg_count++], Pt_ARG_FLAGS, Pt_SELECT_NOREDRAW,
Pt_SELECT_NOREDRAW | Pt_GETS_FOCUS | Pt_FOCUS_RENDER);
button = PtCreateWidget(PtButton, pane, arg_count, args);
PtAddEventHandler( button, Ph_EV_BUT_PRESS, initiateDragging_cb, 0 );
PtAddEventHandler( pane, Ph_EV_DRAG, dragging_cb, 0 );
return(PtRealizeWidget(window));
}
И наконец, вариант функции 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_window())
{
fprintf( stderr, "Couldn't create region.\n" );
exit( EXIT_FAILURE );
}
// цикл приема событий
PtMainLoop();
}
Компилируем:
$ cc -l ph -o drag_test drag_test.c
Запускаем:
$ ./drag_test
И дрыгаем предметами...:)
Половина вторая. Построение виджетов.
"...not a nice thing to do, but ..." PtSampCompound.c
За всеми подробностями привычно отправляю
туда, где их ищут в последнюю очередь.
Кстати, с разделом документации "Building Custom Widgets" связана почти детективная история. В QNX4 этот раздел был, в QNX6.0 - исчез, а в QNX6.2 появился снова. Он почти полностью повторяет то, что было в QNX4. Правда в html-варианте глава "Using Widget Superclasses" обрывается на полуслове в первой своей трети. Подозреваю, что в фирме QSSL документацию готовят в редакторе ped, в котором нет функции "Undo". Зато за готовыми примерами отправляют в каталог /qnx4/phtk/src, который существует только в QNX4. Этой дырой в лицензионной политике QSSL мы вероломно воспользуемся в следующей главе.
При построении виджетов используется "классовый подход". За основу берется один из готовых виджетов (он называется superclass), и от него, путем наследования, создается новый (subclass) виджет. Как и положено, производный виджет наследует свойства и поведение от базового и добавляет что-то свое. Часть унаследованных признаков в производном виджете может быть заблокирована.
Для построения новых виджетов предлагаются 9 суперклассов:
|
PtWidget |
- базовый для всех виджетов Фотона |
|
PtBasic |
- базовый для всех "видимых" виджетов |
|
PtContainer |
- базовый для всех контейнерных виджетов |
|
PtCompound |
- основа для построения виджетов, состоящих из нескольких готовых виджетов (например,
PtCombobox, состоящий из PtText и PtList) |
|
PtGenList |
- базовый для виджетов-списков |
|
PtGenTree |
- базовый для виджетов-деревьев |
|
PtLabel |
- базовый для виджетов, содержащих текст и рисунки |
|
PtGraphic |
- базовый для виджетов-графических примитивов |
|
PtGauge |
- базовый для "измерителей", ассоциируемых с числовыми значениями.
|
Рассмотрим (очень кратко) жизненный цикл
виджета.
- Создание виджета.
- Создание объекта.
- Инициализация членов значениями по умолчанию.
- Присвоение значений ресурсов переданных функцией PtCreateWidget().
- Реализация (realizing) виджета.
- Инициализация.
- Вычисление размеров.
- Создание регионов.
- Окончательная "утряска" всех атрибутов.
- "Отрисовка" виджета.
- Функционирование.
- Реагирование на изменение ресурсов.
- Перерисовка, если изображение было повреждено.
- Разрушение (destroying).
- Удаление всех регионов.
- "Стирание" виджета с экрана.
- Деланье виджета не интерактивным ("making the widget noninteractive", а как бы вы это перевели :)?).
- Освобождение системных ресурсов, используемых виджетом.
Кроме того, если это контейнерный виджет, следует позаботиться обо всех дочерних виджетах.
У виджета PtCompaund есть еще одна особенность - экспорт подчиненных виджетов (Exporting subordinate widgets). При установке ресурсов, их значения передаются встроенным (подчиненным- subordinate) виджетам. Например, PtNumericInteger состоит из двух встроенных виджетов - PtText и PtUpDown. При попытке изменить фон виджета PtNumericInteger, изменяется фон виджета PtText.
Ниже описана процедура построения виджета.
- Создание файла заголовков.
- Определение набора новых ресурсов и калбеков.
- Описание структуры виджета. При этом используется наследование от базового виджета средствами языка "С".
- Создание файла с исходным текстом.
- Инициализация ссылки на класс (PtWidgetClassRef_t)
- Методы класса.
- Внутренние обработчики событий.
- Функция создания виджета.
Две половины. Виджет - слайдер с двумя ручками.
"А вы, товарищь, двурушничаете." И. В. Сталин
Проще всего сделать "двуручный" слайдер на базу PtCompaund.
Выглядеть это должно примерно так.
Вдоль контейнера (PtCompaund) идет прорезь (PtBasic, конечно, с установленным флагом Pt_SET). По верх этой конструкции расположены две ручки. Их, я думаю, лучше сделать из PtButton. Во-первых, не надо помнить о флаге Pt_SELECTABLE, он там и так установлен по умолчанию. Во-вторых, стандартный серо-прямоугольный вид можно заменить, добавив рисунки. В-третих, можно менять внешний вид передвигаемой ручки (ресурсы Pt_ARG_ARM_…).
Для простого примера этого достаточно. О прочих полезных свойствах поговорим позже.
Называться он будет CwDoubleSlider.
QSSL не рекомендует использовать префикс "Pt", дабы избежать конфликтов. "Cw" - custom widget - где-то я это подсмотрел.
Вот минимальный набор ресурсов:
#define Cw_ARG_DSLIDER_MIN Pt_RESOURCE(Pt_USER(1), 0)
#define Cw_ARG_DSLIDER_MAX Pt_RESOURCE(Pt_USER(1), 1)
#define Cw_ARG_DSLIDER_VAL1 Pt_RESOURCE(Pt_USER(1), 2)
#define Cw_ARG_DSLIDER_VAL2 Pt_RESOURCE(Pt_USER(1), 3)
#define Cw_CB_DSLIDER_CHANGED Pt_RESOURCE(Pt_USER(1), 4)
С четырьмя первыми все понятно, а последний - это callback на изменения значений. Он имеет два подтипа:
#define Cw_HANDLER1_CHANGED 1
#define Cw_HANDLER2_CHANGED 2
Макрос Pt_RESOURCE определен в файле PtT.h:
#define Pt_RESOURCE_RANGE 1000
#define Pt_RESOURCE( a, b ) ( Pt_RESOURCE_RANGE * a + b )
Значение a - уникальный идентификатор виджета.
Например, для PtButton, он равен 6 и ресурс "фон нажатой кнопки" определяется как
#define Pt_ARG_ARM_FILL Pt_RESOURCE( 6, 2 ).
Макрос Pt_USER тоже определен в файле PtT.h.:
#define Pt_USER( a ) ( 5000 + a )
и про него QSSL сообщает следующее:
The Pt_USER() macro is designed for widgets being created for in-house use only. If you intend to distribute the widgets you create as a public domain or commercial library, please contact QSSL Customer Service. They'll give you a unique range of widget numbers and assign your widget set a prefix. This will prevent your widget library from conflicting with another third party's commercial widget library.
Так что для "домашнего музицирования" нас пока устроит Pt_USER(1). Дальше, опишем структуру нового виджета:
typedef struct Cw_double_slider
{
PtCompoundWidget_t compound; // наследование от PtCompaumd в стиле "С"
long minimum; // минимальное значение
long maximum; // максимальное значение
long value1; // значение, привязанное к первой ручке
long value2; // значение, привязанное ко второй ручке
PtWidget_t *slot_wgt; // прорезь
PtWidget_t *handler1_wgt; // первая ручка
PtWidget_t *handler2_wgt; // вторая ручка
} CwDoubleSlider_t;
Осталось описать структуру данных, передаваемых в callback, через его третий аргумент:
typedef struct Cw_double_slider_callback
{
long value1;
long value2;
} CwDoubleSliderCallback_t;
Если подробней, то третий аргумент callback-а имеет тип PtCallbackInfo_t (файл PtT.h):
typedef struct Pt_callback_info
{
unsigned long reason;
unsigned long reason_subtype;
PhEvent_t *event;
void *cbdata;
} PtCallbackInfo_t;
Первый член структуры reason будет содержать значение Cw_CB_DSLIDER_CHANGED,
Второй - reason_subtype - либо Cw_HANDLER1_CHANGED, либо Cw_HANDLER1_CHANGED.
А последний, cbdata, и будет содержать указатель на структуру CwDoubleSliderCallback_t.
Все это оформим в виде файла CwDoubleSlider.h (окончательный вариант - в
архиве с исходными текстами).
Теперь займемся написанием функций.
Сейчас, наверное, полезно пролезть через описанную выше дыру и посмотреть каталог /qnx4/phtk/src/widgets :)...
Прежде всего, надо проинициализировать ссылку на класс:
Это то самое, что передается первым аргументом при создании виджета функции PtCreateWidget()
( например: dslider = PtCreateWidget(CwDoubleSlider, window, arg_count, args); ).
Описание структура, приведенное в файле PtT.h для Photon 2.xx, отличается от более ранних версий наличием третьего члена int id.
Скорее всего, это тот самый уникальный идентификатор, который применяется в макросе Pt_RESOURCE. В любом случае, похоже, он пока не используется.
typedef struct Pt_widget_class_ref
{
PtWidgetClass_t *wclass;
PtWidgetClass_t *(*init)(void);
int id;
} PtWidgetClassRef_t;
// Инициализация ссылки на класс
PtWidgetClassRef_t __CwDoubleSlider = { NULL, CwCreateDoubleSliderClass };
PtWidgetClassRef_t *CwDoubleSlider = &__CwDoubleSlider;
CwCreateDoubleSliderClass - это функция создания класса.
Прототип функции выглядит так:
PtWidgetClass_t * CwCreateDoubleSliderClass ( void );
Опишем ресурсы виджета. Для этого заполним массив структур PtResourceRec_t.
Эта структура описана в файле PtT.h:
typedef struct Pt_resource_rec
{
unsigned long type;
void (*mod_f)( PtWidget_t *, PtArg_t const * );
int (*query_f)( PtWidget_t *, PtArg_t * );
unsigned long arg_value;
unsigned long arg_len;
} PtResourceRec_t;
В этом массиве содержится вся информация о ресурсах виджета:
-
его типе (
type),
- типе значения(
arg_value),
- размере этого значения(
arg_len),
- и функциях для установки с чтения значения ресурса.
static const PtResourceRec_t resources[] =
{
Cw_ARG_DSLIDER_MIN,
dslider_modify,
dslider_qwery,
Pt_ARG_IS_NUMBER(CwDoubleSlider_t, minimum),
0,
Cw_ARG_DSLIDER_MAX,
dslider_modify,
dslider_qwery,
Pt_ARG_IS_NUMBER(CwDoubleSlider_t, maximum),
0,
Cw_ARG_DSLIDER_VAL1,
dslider_modify,
dslider_qwery,
Pt_ARG_IS_NUMBER(CwDoubleSlider_t, value1),
0,
Cw_ARG_DSLIDER_VAL2,
dslider_modify,
dslider_qwery,
Pt_ARG_IS_NUMBER(CwDoubleSlider_t, value2),
0,
Cw_CB_DSLIDER_CHANGED,
0,
0,
Pt_ARG_IS_CALLBACK_LIST( CwDoubleSlider_t, changed ),
0
};
Последний элемент массива - callback.
Макросы Pt_ARG_IS_NUMBER и Pt_ARG_IS_CALLBACK_LIST (и прочие подобные) описаны все в том же незаменимом PtT.h.
Далее, заполним массив смещений на подчиненные виджеты:
static ushort_t subs[] =
{
offsetof(CwDoubleSlider_t, slot_wgt ),
offsetof(CwDoubleSlider_t, handler1_wgt ),
offsetof(CwDoubleSlider_t, handler2_wgt ),
};
Заполняем таблицу ресурсов:
static CONST_BUG PtArg_t args[] =
{
{ Pt_SET_VERSION, 110},
{ Pt_SET_STATE_LEN, sizeof(CwDoubleSlider_t ) },
{ Pt_SET_DFLTS_F, (long) dslider_dflts },
{ Pt_SET_NUM_RESOURCES, sizeof( resources ) / sizeof( resources[0] ) },
{ Pt_SET_RESOURCES, (long)resources },
{ Pt_SET_NUM_SUBORDINATES, sizeof( subs ) / sizeof( subs[0] ) },
{ Pt_SET_SUBORDINATES, (long)subs, sizeof( subs ) / sizeof( subs[0] ) }
};
Дело в том, что не все ресурсы виджета можно установить извне. Часть устанавливается один раз при создании виджета. Назначение первых двух понятно по их названиям, далее идут указатели на методы класса.
У нас, например, { Pt_SET_DFLTS_F, (long) dslider_dflts } назначает функцию dslider_dflts, методом присвоения значений по умолчанию.
И наконец, создаем класс вызвав функцию PtCreateWidgetClass():
CwDoubleSlider->wclass = PtCreateWidgetClass(
PtCompound,
0,
sizeof( args )/sizeof( args[0] ),
args );
Ниже приведен прототип функции:
static void dslider_dflts( PtWidget_t *widget )
В этой функции устанавливаются значения по умолчанию. Кроме того, здесь должны быть созданы все экспортируемые виджеты (не экспортируемые могут создаваться в любой момент).
#define Cw_SLOT_WIDTH_DEF 8
#define Cw_HANDLER_WIDTH_DEF 19
#define Cw_CONTAINER_WIDTH_DEF 300
static void dslider_dflts( PtWidget_t *widget )
{
CwDoubleSlider_t *dslider = (CwDoubleSlider_t *)widget;
//..........................
//..........................
// установка значений по умолчанию
dslider->minimum = 0;
dslider->maximum = 100;
dslider->value1 = 0;
dslider->value2 = 100;
// ресурсы контейнера
widget->area.size.w = Cw_CONTAINER_WIDTH_DEF;
widget->area.size.h = Cw_HANDLER_WIDTH_DEF;
// создаем подчиненные виджеты
//..........................
//..........................
dslider->slot_wgt = PtCreateWidget( PtBasic, widget, arg_count, args );
//..........................
//..........................
//..........................
//..........................
//..........................
PtAddEventHandler( dslider->handler1_wgt, Ph_EV_BUT_PRESS,
dslider_butpress_callback, dslider );
PtAddEventHandler( dslider->handler2_wgt, Ph_EV_BUT_PRESS,
dslider_butpress_callback, dslider );
PtAddEventHandler( (PtWidget_t*)(&(dslider->compound)), Ph_EV_DRAG,
dslider_drag_callback, dslider );
}
Обработчики событий, с небольшими изменения ми повторяют
описанные в первой главе.
Остались две таинственные функции - установки и чтения ресурсов:
static void dslider_modify( PtWidget_t *widget, PtArg_t const *argt )
{
CwDoubleSlider_t *dslider = (CwDoubleSlider_t * )widget;
PhPoint_t pos = {0, 0};
PhRect_t da_canvas; // внутренний прямоугольник контейнера
PhArea_t area1; // первая ручка
PhArea_t area2; // вторая ручка
PtCalcCanvas(widget, &da_canvas);
PtWidgetArea(dslider->handler1_wgt, &area1);
PtWidgetArea(dslider->handler2_wgt, &area2);
switch(argt->type)
{
case Cw_ARG_DSLIDER_MIN:
dslider->minimum = argt->value;
pos.x = (dslider->value1 - dslider->minimum) *
((da_canvas.lr.x - da_canvas.ul.x)-area1.size.w) /
(dslider->maximum - dslider->minimum);
// а вот так, кстати, можно передавать ресурсы подчиненным виджетам
// например Cw_HANDLER_COLOR --> Pt_ARG_FILL_COLOR
PtSetResource(dslider->handler1_wgt, Pt_ARG_POS, &pos, 0);
pos.x = (dslider->value2 - dslider->minimum) *
((da_canvas.lr.x - da_canvas.ul.x)-area2.size.w) /
(dslider->maximum - dslider->minimum);
PtSetResource(dslider->handler2_wgt, Pt_ARG_POS, &pos, 0);
break;
case Cw_ARG_DSLIDER_MAX:
dslider->maximum = argt->value;
//..........................
//..........................
break;
case Cw_ARG_DSLIDER_VAL1:
dslider->value1 = argt->value;
//..........................
//..........................
break;
case Cw_ARG_DSLIDER_VAL2:
dslider->value2 = argt->value;
//..........................
//..........................
break;
}
}
static int dslider_qwery( PtWidget_t *widget, PtArg_t *argt )
{
CwDoubleSlider_t *ds = (CwDoubleSlider_t * )widget;
switch(argt->type)
{
case Cw_ARG_DSLIDER_MIN:
*(long*)(argt->value) = (long)(&ds->minimum);
return Pt_END;
case Cw_ARG_DSLIDER_MAX:
*(long*)(argt->value) = (long)(&ds->maximum);
return Pt_END;
case Cw_ARG_DSLIDER_VAL1:
*(long*)(argt->value) = (long)(&ds->value1);
return Pt_END;
case Cw_ARG_DSLIDER_VAL2:
*(long*)(argt->value) = (long)(&ds->value2);
return Pt_END;
default:
return Pt_END;
}
return Pt_CONTINUE;
}
Остается не забыть включить файл CwDoubleSlider.h и откомпилировать в динамическую библиотеку:
$cc -g -o libdslider.so -shared CwDoubleSlider.c
Полный текст файла CwDoubleSlider.c и пример использования виджета CwDoubleSlider - в архиве с исходными текстами.
Здесь нет ничего сложного. Все описано в документации, кроме одной мелочи (или, я ее проглядел):
после создания шаблона (template), добавленный нами виджет не обращает внимания на настройки по умолчанию, заданные в файле *.pal. Во всяком случае на заданный там размер. Когда помещаете виджет в окно, его размеры - 50х50 пикселов.
Исправить это можно, отредактировав файл описания шаблона.
Заглянем в каталог $HOME/.ph/phab/templates. Каждому шаблону соответствует 2 или 3 файла, с именами:
txxxx - файл описания шаблона
txxxx.img - файл "иконки" для палитры компонентов
txxxx.cb - файл описания callback-ов (в нашем случае отсутствует).
Ищем в файле описания шаблона фрагмент
1005
dim
50,50
и заменяем последнюю строку на нужную нам:
300,19
Пример файла описания виджета для PhAB и тестовый проект, также находится в
архиве.
На последок...
Статья получилась несколько поверхностной, но я не ставил цели полностью перевести документацию QSSL. Основные
ее цели - помочь преодолеть психологический барьер и доказать, что документация существует. Кроме того, хочу покаяться: все что описано в первой главе я не раз использовал и просто
так, и в виде шаблонов PhAB.
Все что написано во второй и третьей главах (ну, почти все), я узнал за два выходных, в процессе написания статьи. Учитывая мое знание языка, это, наверное, говорит о качестве документации. А еще о том, что примеры, хотя и работоспособны, но явно не идеальны.
На случай, если кому-то интересен именно этот виджет (мне, например, он интересен и сам по себе и в составе более сложно - для просмотра _не_runtime_ трендов), вот список того, что я еще сделал
бы:
- флаг ориентации (горизонтальная/вертикальная);
- доступ к внешнему виду ручек (цвет, basic-флаги, ширина, рисунки), для каждой отдельно;
- шкала (желательно, оцифрованная);
- индикация значений (min, max и текущие);
- управление заливкой прорези (для трех областей по
отдельности).
Дмитрий Сошин (DS)
dmitry@mutex.ru |