QNX RTP Logo QNX Realtime Platform: Русский Портал QNX
Thursday, 20 Nov 2008 16:07
Меню

Проект OpenNET - все о Unix
Перемещение виджетов и построение собственных виджетов на примере "двуручного" слайдера Print E-mail
Дмитрий Сошин, dmitry at mutex.ru

Перемещение виджетов и построение собственных виджетов на примере "двуручного" слайдера.

Дмитрий Сошин <dmitry@mutex.ru>

Версия для печати

  1. Половина первая. Перемещение виджетов.
  2. Половина вторая. Построение виджетов.
  3. Две половины. Виджет - слайдер с двумя ручками.
  4. На последок...
  5. Литература.

 


    [top] [next]  

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

[top]  Половина первая. Перемещение виджетов.

"Она может двигать собой... в полный рост..."
Б.Г.

В терминологии 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 );

Нас интересуют первые пять аргументов.

  1. PhRid t rid - идентификатор региона, который будет улавливать события Ph_EV_DRAG. Это регион контейнера и его идентификатор можно получить вызвав функцию PtWidgetRid().
  2. unsigned flags - набор флагов, определяющий поведение при перемещении. Если установить комдинацию Ph_TRACK_DRAG |Ph_DRAG_TRACK, мы сможем перемещать дочерний виджет в любом направлении, и перемещаться будет весь виджет. Если не устанавливать Ph_DRAG_TRACK, перемещаться будет только рамка.
  3. const PhRect t *rect - перемещаемый прямоугольник, то есть прямоугольник, ассоциируемый с дочерним виджетом.
  4. const PhRect t *boundary - область, в которой можно перемещать виджет. Если края этого прямоугольника совпадают с границами контейнера, перемещаемый виджет нельзя будет утащить за его край.
  5. 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

И дрыгаем предметами...:)

  


  [prev] [top] [next]  

[top]  Половина вторая. Построение виджетов.

"...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  - базовый для "измерителей", ассоциируемых с числовыми значениями.

[top]  Жизненный цикл виджета.

Рассмотрим (очень кратко) жизненный цикл виджета.

  1. Создание виджета.
    • Создание объекта.
    • Инициализация членов значениями по умолчанию.
    • Присвоение значений ресурсов переданных функцией PtCreateWidget().
  2. Реализация (realizing) виджета.
    • Инициализация.
    • Вычисление размеров.
    • Создание регионов.
    • Окончательная "утряска" всех атрибутов.
    • "Отрисовка" виджета.
  3. Функционирование.
    • Реагирование на изменение ресурсов.
    • Перерисовка, если изображение было повреждено.
  4. Разрушение (destroying).
    • Удаление всех регионов.
    • "Стирание" виджета с экрана.
    • Деланье виджета не интерактивным ("making the widget noninteractive", а как бы вы это перевели :)?).
    • Освобождение системных ресурсов, используемых виджетом.

Кроме того, если это контейнерный виджет, следует позаботиться обо всех дочерних виджетах.
У виджета PtCompaund есть еще одна особенность - экспорт подчиненных виджетов (Exporting subordinate widgets). При установке ресурсов, их значения передаются встроенным (подчиненным- subordinate) виджетам. Например, PtNumericInteger состоит из двух встроенных виджетов - PtText и PtUpDown. При попытке изменить фон виджета PtNumericInteger, изменяется фон виджета PtText.

[top]  Процедура построения виджета.

Ниже описана процедура построения виджета.

  1. Создание файла заголовков.
    • Определение набора новых ресурсов и калбеков.
    • Описание структуры виджета. При этом используется наследование от базового виджета средствами языка "С".
  2. Создание файла с исходным текстом.
    • Инициализация ссылки на класс (PtWidgetClassRef_t)
    • Методы класса.
    • Внутренние обработчики событий.
    • Функция создания виджета.

 


  [prev] [top] [next]  

[top]  Две половины. Виджет - слайдер с двумя ручками.

"А вы, товарищь, двурушничаете."
И. В. Сталин

Проще всего сделать "двуручный" слайдер на базу PtCompaund.

Выглядеть это должно примерно так.

Вдоль контейнера (PtCompaund) идет прорезь (PtBasic, конечно, с установленным флагом Pt_SET). По верх этой конструкции расположены две ручки. Их, я думаю, лучше сделать из PtButton. Во-первых, не надо помнить о флаге Pt_SELECTABLE, он там и так установлен по умолчанию. Во-вторых, стандартный серо-прямоугольный вид можно заменить, добавив рисунки. В-третих, можно менять внешний вид передвигаемой ручки (ресурсы Pt_ARG_ARM_…).

Для простого примера этого достаточно. О прочих полезных свойствах поговорим позже.

[top]  Описание виджета.

Называться он будет 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 (окончательный вариант - в архиве с исходными текстами).

[top]  Функции и методы.

Теперь займемся написанием функций.

Сейчас, наверное, полезно пролезть через описанную выше дыру и посмотреть каталог /qnx4/phtk/src/widgets :)...

[top]  Ссылка на класс.

Прежде всего, надо проинициализировать ссылку на класс:

Это то самое, что передается первым аргументом при создании виджета функции 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 - это функция создания класса.

[top]  Функция создания класса DoubleSlider.

Прототип функции выглядит так:

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 );

[top]  Метод установки значений по умолчанию.

Ниже приведен прототип функции:

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 );
}

[top]  Обработчики событий.

Обработчики событий, с небольшими изменения ми повторяют описанные в первой главе.

[top]  Установка и чтение ресурсов.

Остались две таинственные функции - установки и чтения ресурсов:

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 - в архиве с исходными текстами.

[top]  Добавление виджета в PhAB.

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

Заглянем в каталог $HOME/.ph/phab/templates. Каждому шаблону соответствует 2 или 3 файла, с именами:

  • txxxx - файл описания шаблона
  • txxxx.img - файл "иконки" для палитры компонентов
  • txxxx.cb - файл описания callback-ов (в нашем случае отсутствует).

Ищем в файле описания шаблона фрагмент

1005
dim
50,50
и заменяем последнюю строку на нужную нам:

300,19

Пример файла описания виджета для PhAB и тестовый проект, также находится в архиве.

  


  [prev] [top] [next]  

[top]  На последок...

Статья получилась несколько поверхностной, но я не ставил цели полностью перевести документацию QSSL. Основные ее цели - помочь преодолеть психологический барьер и доказать, что документация существует. Кроме того, хочу покаяться: все что описано в первой главе я не раз использовал и просто так, и в виде шаблонов PhAB. Все что написано во второй и третьей главах (ну, почти все), я узнал за два выходных, в процессе написания статьи. Учитывая мое знание языка, это, наверное, говорит о качестве документации. А еще о том, что примеры, хотя и работоспособны, но явно не идеальны.
На случай, если кому-то интересен именно этот виджет (мне, например, он интересен и сам по себе и в составе более сложно - для просмотра _не_runtime_ трендов), вот список того, что я еще сделал бы:

  • флаг ориентации (горизонтальная/вертикальная);
  • доступ к внешнему виду ручек (цвет, basic-флаги, ширина, рисунки), для каждой отдельно;
  • шкала (желательно, оцифрованная);
  • индикация значений (min, max и текущие);
  • управление заливкой прорези (для трех областей по отдельности).

[top]  Литература.

1.Пример по "widgets draggin"ftp.qnx.com/usr/free/qnx4/photon/examples/exdrag.tgz
2.Примеры написания виджетовftp.qnx.com/usr/free/qnx4/photon/widgets/*
3.Справка по PhInitDrag()На вашем компьютереВ интернете
4.Справка по Widget geometryНа вашем компьютереВ интернете
5.Справка по PtCalcCanvas()На вашем компьютереВ интернете
6.Справка по PtWidgetArea()На вашем компьютереВ интернете
7.Справка по PhDeTranslateRects()На вашем компьютереВ интернете
8.Справка по PtWidgetExtent()На вашем компьютереВ интернете
9.Справка по PhTranslateRect()На вашем компьютереВ интернете
10.Справка по PhEvent_tНа вашем компьютереВ интернете
11.Справка по PhDragEvent_tНа вашем компьютереВ интернете
12.Раздел документации Building Custom WidgetsНа вашем компьютереВ интернете
13.Раздел документации Building Custom Widgets - Binding Widget into PhABНа вашем компьютереВ интернете
14.Справка по PtCallbackInfo_tНа вашем компьютереВ интернете
15.Архив с примерамиdslider.tgz

  [back] [top]    

Дмитрий Сошин (DS)
dmitry@mutex.ru

[Вернуться к списку]
©   2000-2003 Команда проекта QNX.ORG.RU // QNX.ORG.RU Team
Авторы проекта: Дмитрий Алексеев [dmi] и Дмитрий Васильев. Техническое сопровождение проекта: Игорь Сорокин [isorokin]. Информационное сопровождение: Дмитрий Алексеев [dmi]
QNX - зарегистрированная торговая марка QNX Software Systems, Ltd., Canada. Остальные упоминаемые на сайте торговые марки и логотипы являются исключительно собственностью их уважаемых владельцев. Ничьи права не затронуты. Материалы сайта не могут быть скопированы и где-либо использованы в той или иной форме без письменного разрешения разработчиков сайта.
Powered by Mambo Open Source