Создание новых виджетов на основе логик и компонент

Создание типовых виджетов на основе уже имеющихся компонент и логик.

Описание инструментов ( логик ,компонент...).


При конструировании виджетов в основу положено использование контейнера, как связуещего звена между компонентами и логиками. Компонентами являются такие объекты, которые могут отображать визуальное состояние виджета, например: картинки, текст, произвольное поле для рисования и т.п.,включая и стандартные gtk виджеты. Все компоненты хранятся в папке components. Логикой является такой объект,который визуально никак не выделяется или отрисовывается, но который может обрабатывать некоторые внешние события. Об этих событиях остановимся ниже подробнее.

События, обрабытываемые логикой.

Вообще понятие очень широкое, а в рамках виджетов gtk событиями являются реакция виджетов на какие-либо сигналы(нажатия клавишь мышь и т.д.). Так как библиотека Uniwidgets предназначена для работы совместно с библиотекой Uniset, то к виджетам добавляются события от Uniset. Основным видом событий Uniset является событие изменения значения какого-либо датчика. Виджеты Uniwidgets должны обрабатывать события изменения датчика/ов и выполнять какие-то визуальные действия. Далее для наглядности введем пример виджета : виджет лампочка,которая имеет три состояния: выключено, включено(зеленый) и мигающий(красный).(Физически сложно представить такую лампочку, но так как у нас все отрисовывается,то можно задавать цвет, мигание для лампочки и можно, впринципе, менять даже форму). Итак есть аналоговый датчик, который имеет три состояния 0 - выкл, 1 - вкл., 2 - мигает. Не важно кто их выставляет, важно что это значения которые мы должны обработать.
Логика должна обрабатывать события от нашего тестового датчика(далее просто датчика) и в зависимости от реализации подавать команду на отображение того или иного состояния. Это было основное описание логик и компонентов,а далее описание самой структуры постороенной из логик и компонент, как из кирпичиков. Связующим звеном между логикой и компонентой является контейнер SimpleObject. SimpleObject обеспечивает функции работы с датчиками(получение значения, сохранение значения и принимает сигналы об изменении состояния датчиков), также производит типизацию своих дочерних виджетов, чтобы можно было отделить логику от компонент и логику одного типа от логики другого типа. Для этого SimpleObject добавляет свойство type своим дочерним виджетам и возвращает список дочерних виджетов по типу(см. SimpleObject::get_children ). Прмер простейшей структуры выглядит следующим образом:
    SimpleObject
            |
            |-Logic1  (type 1)
            |-Logic2  (type 2)
            |-Image1  (type 3)
            |-Image2  (type 3)
            |-Text1   (type 4)
            |-Text2   (type 4)
Логики и объект SimpleObject помещаются в директорию objects. Для складирование готовых виджетов используется директория typical. Готовыми виджетами(или типовыми) являются полностью скомпановым виджет, который способен обрабатывать события от датчика или выполнять другие манипуляции с датчиками. Также в этой папке находятся структуры вида Typical....(TypicalState) - это не готовый виджет,а составная часть виджета, которыя служит для уменьшения кода в одном виджете и реализации повторного использования такой же структуры в другом виджете. Например, TypicalState - это типовой контейнер представляет собой набор картинок, логику для их отображения и логику для работы с датчиком. Его можно применять при создании сложных виджетов, таких, например, как ГДГ, цистрены, индикаторы с порогом и т.п. Но это уже составные виджеты,где лампочек может быть несколько и все они состовляют единую композицию и создание таких виджетов предназначено для удобства размещения их в glade файле, чтобы не создавать много вложенных или просто раздельных виджетов,а просто поместить один изадать ему свойства,а дальше в коде само все помещается куда нужно. Далее перейдем к созданию простого виджета лампочки с тремя состояниями на основе стандартной логики и компонент. В процессе создания буем разъяснять, что и зачем нужно. Начнем с простого способа - создание виджета в glade файле. Что нам нужно: SimpleObject - как главный контейнер, 3 картинки - 2 две Image и 1 мигающая ImageBlink, логику для обработки значений от датчика StateLogic и логика для отображения состояния виджета ShowLogic. Рассмотрим подробнее эти логики.

Логика.

StateLogic - умеет обрабатывать сигналы от датчика и собственно все что она делает, это принимает новое значение и выставляет соответствующее состояние виджета. Так как датчиков может быть не 1,а несколько, то необходимо какая-то логика которая будет обрабатывать сигналы от нескольких логик на выставление определенного состояния. Для этого и реализована логика ShowLogic. При инициализации StateLogic получает указатель на ShowLogic (т.к. она может быть только одна в контейнере) и через интерфейс выставляет команду определенного состояния(вообщем, это выгладит как показать состояние mOFF см. types.h - ObjectMode). ShowLogic в свою очередь при инициализации получает все картинки(т.е. состояния виджета, которые содержат mode и priority) и при выставлении очередного состояния проверяет приоритет нового и текущего выставленного состояния. У кого приоритет выше тот и отображается,а при равенстве отображается новое. Также возможна ситуация, что датчик аналоговый (как в нашем случае) и при выставлении нового состояния также проверяется его приоритет. Если датчик требует квитирования т.е. он мигает пока не прийдет сисгнал от датчика, подтверждающего что оператор среагировал на данный сигнал, то даже если сигнал с датчика поменяется на другой,но с меньшим приоритетом, все равно будет отображаться мигающее состояние т.к. у него больший приоритет. Скрыть его можно только заквитировав заквитирован или выставив состояния с большим приоритетом(см. описание работы логики отображения).

Далее структуру виджета можно представить как описано ниже
        <child>
          <widget class="gtkmm__CustomObject_simpleobject" id="simple_image_lamp">
            <property name="visible">True</property>
            <child>
              <widget class="gtkmm__CustomObject_imageblink" id="lamp_warn">
                <property name="width_request">42</property>
                <property name="height_request">37</property>
                <property name="visible">True</property>
                <property name="visible_window">False</property>
                <property name="image_path">./svg/sensors42x37/signal_h_!_grey.svg</property>
                <property name="mode">6</property>
                <property name="priority">2</property>
                <property name="image2_path">./svg/sensors42x37/signal_h_!_yellow.svg</property>
              </widget>
              <packing>
                <property name="type">1</property>
              </packing>
            </child>
            <child>
              <widget class="gtkmm__CustomObject_image" id="sensor_on">
                <property name="width_request">42</property>
                <property name="height_request">37</property>
                <property name="visible">True</property>
                <property name="visible_window">False</property>
                <property name="mode">1</property>
                <property name="priority">1</property>
                <property name="image_path">./svg/sensors42x37/signal_h_!_green.svg</property>
              </widget>
              <packing>
                <property name="type">1</property>
              </packing>
            </child>
            <child>
              <widget class="gtkmm__CustomObject_image" id="sensor_off">
                <property name="width_request">42</property>
                <property name="height_request">37</property>
                <property name="visible">True</property>
                <property name="visible_window">False</property>
                <property name="image_path">./svg/sensors42x37/signal_h_!_grey.svg</property>
              </widget>
              <packing>
                <property name="type">1</property>
              </packing>
            </child>
            <child>
              <widget class="gtkmm__CustomObject_showlogic" id="lamp_show">
                <property name="width_request">0</property>
                <property name="height_request">0</property>
              </widget>
              <packing>
                <property name="type">0</property>
              </packing>
            </child>
            <child>
              <widget class="gtkmm__CustomObject_statelogic" id="sensor_statelogic">
                <property name="width_request">0</property>
                <property name="height_request">0</property>
                <property name="state_ai">104</property>
                <property name="state_obj_ai">1004</property>
                <property name="lock_view">True</property>
              </widget>
              <packing>
                <property name="type">0</property>
              </packing>
            </child>
          </widget>
          <packing>
            <property name="type">10</property>
          </packing>
        </child>

Так как логика StateLogic работает в данном случае с аналоговым датчиком,то свойство "mode" остается по-умолчанию. Если ему будет заданно какое-то определенное значение, то при получаение не нулевого значения будет выставляться состояние соответствующее только этому "mode". "mode" так же задается для картинок(Image, ImageBlink) и для текста (Text, TextBlink) и в соответствии с этим значением логика выставляет состояние виджета. Выше представлен пример для простого виджета "лампочки" с тремя состояниями и выставлением состояния по аналоговому датчику. Иногда вместо картинки нужно вывести просто текст ,например,.выключен, включен и неисправность. Делается это аналогично примеру с картинками и показан выше. Пример для текста аналогичен только меняется тип компонент. Для индикаторов используется специальные логики: IndicatorLogic, которая отслеживает изменение аналогового датчика и подает команду на отрисовку нового значения; IndicatorStateLogic - для выставления сработавшего порога; IndicatorShowLogic - обрабатывает пороги для индикатора и выставляет наиболее приоритетное состояние. Для индикатора может существовать до 4-х порогов различного приоритета, 2 нижних порога(warning и alarm) и 2 верхних порога. Alarm пороги приоритетнее warning порогов,а между нижним и верхним попрогом приоритет одинаков. Логика IndicatorStateLogic взаимодействует с IndicatorShowLogic т.е. через указатель на IndicatorShowLogic выставляет ему команду показа состояния, на которое завязан данный IndicatorStateLogic. А IndicatorShowLogic сам определяет наиболее приоритетное состояния для виджета, которое нужно отобразить. Тут можно провести аналогию IndicatorShowLogic - аналогичен ShowLogic, а IndicatorStateLogic аналогичен StateLogic. IndicatorShowLogic может быть только одна в контейнере, как и сам компонент Indicator(?) или IndicatorBlink(?). Для объекта цистерна все тоже самое и фактически различия в том что цистерна отображается графически уровнем жидкости(0-100%), но для удобства еще присутствует индикатор.
Также нужно упомянуть две основные логики LinkLogic и QueueLogic, которая является базовым классом для логик. LinkLogic служит для реализации способности виджетов "сереть" при потери связи с SharedMemory. Эта логика через указатель на контейнер SimpleObject(который есть у всех логик) выставляет в нем специальную переменную и при отрисовки вся область виджета "сереет". QueueLogic служит базовым классом для логик(например, CisternShowLogic, IndicatorShowLogic ) и в ней реализуется очередь выставленных состояний по приоритетам. В отдельный момент отображается только одно состояние простого виджета с наибольшим приоритетом. Если это состояние снимается,то на его смену ищется другое выставленное состояние с наибольшим приоритетом или состояние "выключено" если других нет. Это состояни имеет наименьший приоритет соответственно(если нет специальных настроек для него). Имеется также класс ADLogic
        <child>
          <widget class="gtkmm__CustomObject_simpleobject" id="simple_text_lamp">
            <property name="visible">True</property>
            <child>
              <widget class="gtkmm__CustomObject_textblink" id="sensor_textwarn">
                <property name="visible">True</property>
                <property name="text">Some signal text WARNING</property>
                <property name="drop_shadow">True</property>
                <property name="abs_font_size">16</property>
                <property name="font_color">#1e1e1e1e1e1e</property>
                <property name="mode">6</property>
                <property name="priority">1</property>
                <property name="on_font_color">#ffffd0d00000</property>
                <property name="on_abs_font_size">16</property>
              </widget>
              <packing>
                <property name="type">1</property>
              </packing>
            </child>
            <child>
              <widget class="gtkmm__CustomObject_text" id="sensor_texton">
                <property name="visible">True</property>
                <property name="text">Some signal text ON</property>
                <property name="abs_font_size">16</property>
                <property name="font_color">#1e1e1e1e1e1e</property>
              </widget>
              <packing>
                <property name="type">1</property>
              </packing>
            </child>
            <child>
              <widget class="gtkmm__CustomObject_text" id="sensor_textoff">
                <property name="visible">True</property>
                <property name="text">Some signal text OFF</property>
                <property name="abs_font_size">16</property>
                <property name="font_color">#1e1e1e1e1e1e</property>
              </widget>
              <packing>
                <property name="type">1</property>
              </packing>
            </child>
            <child>
              <widget class="gtkmm__CustomObject_showlogic" id="sensor_showtext">
                <property name="width_request">0</property>
                <property name="height_request">0</property>
              </widget>
              <packing>
                <property name="type">0</property>
              </packing>
            </child>
            <child>
              <widget class="gtkmm__CustomObject_statetlogic" id="sensor_statetextlogic">
                <property name="width_request">0</property>
                <property name="height_request">0</property>
                <property name="state_ai">104</property>
              </widget>
              <packing>
                <property name="type">0</property>
              </packing>
            </child>
          </widget>
          <packing>
            <property name="type">10</property>
          </packing>
        </child>

Заметки:
Конечно существую уже готовые виджеты которые делают эту работу и о них будет разговор ниже. Но готовые уже заточены под определенные ограничения,а в случае задания виджетов в glade можно собрать виджет под любые условия.

Часто нужно вывести изображение значка с текстовым полем рядом с ним и для удобства это можно сделать объединив два примера кода, показанных выше, в один контейнер SimpleObkect. Внутренние контейнеры выравниваются по координатам.

Поле type виджета SimpleObject равняется 10.Описание типов смотри в include/types.h UniWidgetsTypes::ChildrenTypes.

<child>
          <widget class="gtkmm__CustomObject_simpleobject" id="sensor">
            <property name="visible">True</property>
// Блок из примера 1 //
              <child>
                <widget class="gtkmm__CustomObject_simpleobject" id="simple_image_lamp">
                  <property name="visible">True</property>
                  .......................
                </widget>
                <packing>
                  <property name="x">10</property>
                  <property name="y">10</property>
                  <property name="type">10</property>
                </packing>
              </child>
// Блок из примера 2 //
              <child>
                <widget class="gtkmm__CustomObject_simpleobject" id="simple_text_lamp">
                  <property name="visible">True</property>
                  .......................
                </widget>
                <packing>
                  <property name="x">40</property>
                  <property name="y">25</property>
                  <property name="type">10</property>
                </packing>
              </child>

  </widget>
  <packing>
    <property name="type">10</property>
  </packing>
</child>

Другие виды компонент и логик.

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

Использования типовых виджетов

Описанный выше пример показывает общую структуру создания виджетов. Значит в простом случае виджет - это контейнер содержащий ОДНУ логику отображения ShowLogic, одну или несколько(по количеству датчиков) логику состояния StateLogic и компоненты отображения - картинки(Image, ImageBlink) или текст(Text,TextBlink). Эта структура позволяет визуально отображать, например, лампочку или надпись. Так как часто такая структура входит в состав более сложной или просто часто повторно используются были сделаны типовые контейнеры, которые выполняют работу по компановке контейнера за нас. Такие контейнеры находятся в папке typical и наследуются от базового класса AbstractTypical и их имена начинаются с Typical...,например, TypicalState, TypicalTwoState, TypicalFourState, TypicalGDGControl, TypicalCistern, TypicalCisternBlink и т.д.(смотри папку typical/).Это типовые контейнеры, которые облегчают код(и вообще процесс) создания типовых виджетов, они не являются абсолютно универсальными,а только служат для конкретных задач(в том числе повтороное и как одно из основных - это использование). Если взглянуть на TypicalState, то он будет напоминать нам пример выше. В TypicalState есть только одна логика состояния StateLogic,а если нужно отображать работу лампочки от двух датчиков (допустим один дискретный датчик - включено,а второй - мигание,а для состояния выключено подразумевается что не один из датчиков не выставлен и также по-умолчанию выставляется значение выключено, если не задано настроек для выключенного состояния), то необходимо использовать TypicalTwoState, 4 - TypicalFourState. Больше не сделано. TODO Подумать об универсальном "TypicalMultiState". Исользуется Typical объекты в готовых составных виджетах(например Sensor1,M). Для этого в объявлении класса создается член-класса, а при событии on_realize или on_connect через методы типового объекта задаются свойства его внутренних компонент и логик, далее вызывается метод "configure" где происходит создание всех компонент по заданным свойствам(тип и путь(текст) ).

Создание компонент

В настоящий момент реализовано несколько видов компонент - это картинки и текст,а также специальный компонент для стрелочного прибора. картинки и текст наследуются от общего базового класса SimpleView. Эти классы имееют схожее назначение просто выводят информацию по разному. Существует компонент CisternImage, который наследуется от SimpleImage и представляет из себя набор картинок, которые выводятся на экран формируя единую картинку цистерны(смотри components/CisternImage.h, CisternImageBlink.h). SimpleImage релизует метод отрисовки картинки, поэтому если необходимо создать компонент который выводит картинку или картинки определенным способом, то нужно наследоваться от SimpleImage. Для текста такой же порядок. Основное предназначение компонент - это визуальное отображение информации, логика же отвечает за обработку событий от датчиков и работу с датчиками. Компонентой может быть любой визуальный элемент: кнопка, картинка, текст, различные области для рисования и т.д. важно что внутри них содержится логика только для механизма отрисовки и обработки стандартных событий виджетов(нажатие и т.п.). Пример для компоненты CisternImage:
  class CisternImage : public SimpleImage
  {
    public:
    CisternImage();
    explicit CisternImage(SimpleImage::BaseObjectType* gobject);
    virtual ~CisternImage();
  
    // Methods //
    void set_value(long value); 
    virtual void start_blink() {};
    virtual void stop_blink() {};
    virtual bool is_blinking();
  
    protected:
    // Event handlers //
    virtual bool on_expose_event(GdkEventExpose* event);
    Glib::RefPtr<Gdk::Pixbuf> background_off_image_;
    Glib::RefPtr<Gdk::Pixbuf> background_image_;
  
    Glib::RefPtr<Gdk::Pixbuf> filling_image_;
    Glib::RefPtr<Gdk::Pixbuf> filling_off_image_;
    void draw_value(Glib::RefPtr<Gdk::Pixbuf> image);
    int value_to_offset(const Gtk::Allocation allocation);
  
    // Variables //
    int value_;
    Glib::RefPtr<Gdk::Pixbuf> scale_image_;
  
    // Constants //
    static const int MaxValue;
    static const int MinValue;
  
    private:
    // Methods //
    void constructor();
    DISALLOW_COPY_AND_ASSIGN(CisternImage);
    // Properties //
    ADD_PROPERTY( background_image_path, Glib::ustring )
    ADD_PROPERTY( filling_image_path, Glib::ustring )
    ADD_PROPERTY( scale_image_path, Glib::ustring )
    ADD_PROPERTY( scale_switch, bool )
  };

Основным методом является on_expose_event(...), в котором отрисовывается виджет и определяется тем самым внешний вид виджета.

Создание логик

Для логик существует базовый класс - AbstractLogic(смотри objects/AbstractLogic.h). Класс содержит указатель на контейнер object_ и умеет работать с ним. У каждой логики должен быть методы on_init и connect, в первом происходит инициализация логики,а во второй обрабатывается когда SharedMemory подключается. Логика может взаимодействовать с датчиками SharedMemory: отслеживать значения, выставлять их, может работать каккакие с одним так и с несколькими датчиками. Поэтому логика может быть сложной и на входе иметь множество параметров. На выходе же из логики как правило содержется одно конкретное состояние. Напрмер, рассмотрим логику LinkLogic (смотри objects/LinkLogic.h)
  class LinkLogic : public AbstractLogic
  {
    public:
    LinkLogic();
    explicit LinkLogic(Gtk::EventBox::BaseObjectType* gobject);
    virtual ~LinkLogic();
  
    // Methods //
    virtual void connect();
  
    protected:
    // Methods //
    virtual void on_init();       
  
    private:
    // Methods //
    void set_sensor_handler();
    void set_current_link();
    void set_link(const long link);     
  SharedMemory //
    void constructor();
  
    // Handlers //
    void sensor_handler(UniSetTypes::ObjectId sensor, UniSetTypes::ObjectId
node,  long value);
  
    DISALLOW_COPY_AND_ASSIGN(LinkLogic);
  
    // Properties //
    ADD_PROPERTY( link_di, UniSetTypes::ObjectId )
    ADD_PROPERTY( node, UniSetTypes::ObjectId )
  };
Эта логика получает изменения от одного датчика
  void LinkLogic::set_sensor_handler()
  {
    assert(object_ != NULL);
    if( get_link_di() != UniSetTypes::DefaultObjectId )
      object_->set_sensor_handler(
          sigc::mem_fun(this, &LinkLogic::sensor_handler),
          get_link_di(),
          get_node());
    set_current_link();
  }
и выполняет метод LinkLogic::set_link(const long link) в зависимости от текущего значения датчика.
  void LinkLogic::set_link(const long link)
  {
    assert(object_ != NULL);
  
    if ( link == linkOn )
      object_->set_link(true);
    else if ( link == linkOff )
      object_->set_link(false);
    else
      cerr << get_name() << ": unexpected sensor value - " << link << endl;
  
    object_->queue_draw();
  }
Метод object_->set_sensor_handler()(смотри objects/SimpleObject.h) привязывает датчик к событию об его изменении и каждый раз при изменении данного датчика к нам приходит событие, по которому выполняется метод LinkLogic::sensor_handler(...). По событию connect() обычно проверяется текущее состотояние датчика и происходит обработка этого значения.

Создание типичных виджетов

Все готовые тичные виджеты находятся в "typical/" с названием начинающимся НЕ С "Typical..." (например, это GDG,VDG,ADG и т.д.). Типичные виджеты это комбинация всех объектов(состовных частей) описанных выше, являющаяся законченным виджетом с определенной логикой работы и определенными отображаемыми состояниями. Проще всего объяснить на примере готового виджета АДГ (typical/ADG.h). Основные члены класса:
  TypicalState state_back;
  TypicalTwoState state_key;
  TypicalState state_D;
  TypicalState state_G;
  TypicalFourState generator_states;
  LinkLogic link_;
Они отвечают за формирование основных частей виджета(отображения дизеля, генератора, автомата, состояния АДГ и логика состояния свзязи с SharedMemory). В самом виджете задаются свойства, которые в методе on_configure() задаются внутренним элементам. Внутренним элементам задаются размеры и другие параметры специфичные для каждого элемента. Затем для каждого элемента (state_back,state_key ) вызывается configure().

В общих чертах были описаны все элементы "архитектуры" виджета, их предназначение и общая компановка в виджете. TODO Далее нужно создать и расмотреть пример виджета и каждой компоненты. ======= Описанный выше пример показываеь общую структуру создания виджетов. Значит в простом случае виджет - это контейнер содержащий ОДНУ логику отображения ShowLogic, одну или несколько(по количеству датчиков) логику состояния StateLogic и компоненты отображения - картинки(Image, ImageBlink) или текст(Text,TextBlink). Эта структура позволяет визуально отображать, например, лампочку или надпись. Так как часто такая структура входит в состав более сложной или просто часто повторно используются были сделаны типовые контейнеры, которые выполняют работу по компановке контейнера за нас. Такие контейнеры находятся в папке typical и наследуются от бьазового класса AbstractTypical и их имена начинаются с Typical...,например, TypicalState, TypicalTwoState, TypicalFourState, TypicalGDGControl, TypicalCistern, TypicalCisternBlink и т.д.(смотри папку typical/). Это типовые контейнеры, которые облегчают код(и вообще процесс) создания типовых виджетов, они не являются абсолютно универсальными,а только служат для конкретных задач(в том числе повтороное и как одно из основных - это использование). Если взглянуть на TypicalState, то он будет напоминать нам пример выше. В TypicalState есть только одна логика состояния StateLogic,а если нужно отображать работу лампочки от двух датчиков (допустим один дискретный датчик - включено,а второй - мигание,а для состояния выключено подразумевается что не один из датчиков не выставлен и также по-умолчанию выставляется значение выключено, если не задано настроек для выключенного состояния), то необходимо использовать TypicalTwoState.

Документация по UniWidgets. Последние изменения: Fri Oct 10 09:57:50 2014. Создано системой  doxygen 1.5.9