Или независимые управляемые формы графического интерфейса.


Предыстория


Однажды я заинтересовался вопросом о том, каким образом можно было бы создавать пользовательский интерфейс программ и познакомился с Qt Designer — утилиты для разработки графических интерфейсов, которая входит в стандартный состав Qt.

Кроме всего прочего Qt Designer впечатлил своей способностью сохранять все формы в файлах формате XML, которые могут уже в последствие подключаться непосредственно в программу.

Мне это показалось очень удобным, так как я бы мог рисовать формочки с кнопочками не отходя от текстового редактора.
И тут мысль было уже не остановить: если весь интерфейс описан в стороннем файле, то программный код может вообще не содержать элементы работы непосредственно с формами, а только использовать какой-то программный интерфейс намертво изолирующий бизнес логику и представление.
Если так, то такая программа с пользовательским интерфейсом может быть написана не только на С++, о, священный GNU, она может быть вообще может без языка программирования.

Пока не отпустило, надо было срочно выдумывать как такое можно провернуть.

OpenForm


И так появился на свет концепт OpenForm — создание и управление формами с помощью командной строки.

Где основная идея базируется на том, что любое состояние интерфейса можно представить в формате XML.

Значит если я хочу отрисовать форму, мне нужно всего лишь какая-то утилита, которая возьмет на себя непосредственно задачу отображения форм, а вся логика будет на стороне.
Другими словами, требуется такой мини Qt Designer, который прочитает описание форм и виджетов, и это дело уже отобразит. А чтобы поменять состояние интерфейса, нужно просто как-то передать программе новое описание, например, в виде XML.

Вот и получается, что OpenForm сперва читает файл описания форм, отрисовывает их, и вешает на заданные события определенные триггеры, т. е. действия.
Если событие случается, например кто-то куда-то кликнул, то выполняется какое-то действие. А действие — это ничто иное как исполнение какой-то внешней программы, которая должна возвратить новое состояние пользовательского интерфейса.

А теперь по пунктам.
1. Qt Designer сохраняет дизайн в виде *.ui файлов формата XML.
2. OpenForm читает эти файлы и отображает.
3. На определенные события вешаются триггеры.
4. Если событие произошло, то выполняется сторонняя команда или программа, которая должна вернуть новое состояние интерфейса, т. е. вывести XML новой версии диза��на на стандартное устройство вывода.

Мануал


OpenForm работает с *.ui файлами, которые создаются в среде Qt Designer.
<ui version="4.0" >
  <class>MainWindow</class>
  <widget class="QMainWindow" name="MainWindow">
  <property name="windowTitle">
    <string>MainWindow</string>
  </property>
  </widget>
</ui>

И кроме дефолтных способностей просто отобразить форму, обладает еще и дополнительными плюшки.

Решетки комментируют

Для тех, кто фигачит, комментарии штука очень нужная, если не сказать необходимая.

<ui version="4.0" >
  <class>MainWindow</class>
  <widget class="QMainWindow" name="MainWindow">
  # This is a comment
  <property name="windowTitle">
    <string>MainWindow</string> # title of window
  # <string>MainWindow changed</string> # unneeded title
  </property>
  </widget>
</ui>

Разделяй и подключай

Полный набор для тех, кого не отпускает: знакомая директива для препроцессора #include.
Теперь можно интерфейс разделять на модули.

По традиции, если *.ui исполняемые файлы, то заголовочные должны быть как *.hui

file: geometry.hui:
<property name="geometry" >
  <rect>
    <x>0</x>
    <y>0</y>
    <width>800</width>
    <height>600</height>
   </rect>
</property>

file: widget.ui:
<ui version="4.0" >
  <class>MainWindow</class>
  <widget class="QMainWindow" name="MainWindow">
  <property name="windowTitle">
    <string>MainWindow</string>
  </property>
  #include "geometry.hui"
  </widget>
</ui>

События, сигналы и триггера

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

<triggers>
  <trigger object="btnSubmit">
    <event signal="clicked">cat update_widget.uui</event>	
  </trigger>
</triggers>

Это означает, что если пользователь нажмет на кнопку с именем btnSubmit, выполнится команда
cat update_widget.uui, выводящая содержимое файла с новым состоянием интерфейса.

<triggers>
  # Триггер для объекта с именем OBJECT_NAME
  <trigger object="OBJECT_NAME">
    # Список событий над текущим объектом.
    #   signal: стандартный Qt сигнал
    #   action: тип триггера
    #     "execute" - COMMAND должна быть выполнена, и возвратить новое состояние интерфейса в XML
    #     "return"  - COMMAND должна быть выведена на стандартное устройство вывода
    <event signal="SIGNAL" action="execute|return">COMMAND</event>
  </trigger>
</triggers>

action=return необходим, когда требуется спросить что-то у пользователя или узнать состояние интерфейса из внешней программы, которая вызывает OpenForm (смотри пример с диалоговым окном).

Доставай и обрабатывай

OpenForm обладает еще одним необычайной возможностью получения значения свойств виджетов. Т.е. если надо узнать, что там пользователь напечатал в поле ввода, то можно пользоваться конструкцией {WIDGET_NAME.PROPERTY_NAME}

Например
<event signal="clicked">php script.php —user-input={lineEdit.text}</event>

Где {lineEdit.text} — это означает, что из виджета lineEdit возвратить значение свойства с именем text. Если пользователь что-то введет, то это можно передать стороннему скрипту.

То же самое работает и для булевых свойств.
<event signal="clicked">php script.php --checked={checkBox.checked}</event>

Возвратит true или false.

Экранирование

Если надо использовать { в командах, то можно просто написать так \{

Точечное обновление

Если триггер возвратит дизайн в виде
<ui version="4.0">
...
</ui>

то форма заново перерисуется.
Но в случаях, когда не требуется заново пересоздавать форму, а нужно просто обновить состояние одного элемента, можно использовать :
<update>
...
</update>

И, наконец, управление графическим интерфейсом с помощью echo

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

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

<event signal="clicked">echo "[[update]]
[[widget name='label']]
[[property name='text']]
[[string]]{lineEdit.text}[[/string]]
[[/property]]
[[/widget]]
[[/update]]"
</event>

По событию нажатия, выполнится команда echo, где [[ заменится на <, а ]] — на >. Результатом будет копирование текста из поля ввода lineEdit в объект label.

Ну и зачем все это надо?


Ну, например, помощник при установки программного обеспечения:

Рис 1. Шаг лицензионного соглашения.


Рис 2. Пример анимации, прогресс бара.

Можно даже просто о чем-то спрашивать грозного пользователя с помощью графических форм.

Рис 3. Диалоговое окно с ожиданием действия от пользователя.

Работает следующим образом:
#!/bin/bash
an=`./dialog.ui`
if [ $an = 'yes' ]; then
    echo 'There should be real installation of something very cool'
else
    echo 'Ok, we will do nothing...'
fi

Ну или… а может, написать графический калькулятор на баше.

Рис 4. Графический калькулятор с обработкой выражений на баше.

Или даже так:

Рис 5. Графический калькулятор на PHP.