Управление графическим интерфейсом с помощью echo и cat

Содержание

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

Предыстория

Однажды я заинтересовался вопросом о том, каким образом можно было бы создавать пользовательский интерфейс программ и познакомился с 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.

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

Ну, например, помощник при установки программного обеспечения:
23aa29b36a90e06bd0b7447bdfa911c4
Рис 1. Шаг лицензионного соглашения.

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

Можно даже просто о чем-то спрашивать грозного пользователя с помощью графических форм.
81f9a18390cbd0bb59b5b72d2c718a02
Рис 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

Ну или… а может, написать графический калькулятор на баше.
49645e03ce1440b026b22ef4e66a6c7b
Рис 4. Графический калькулятор с обработкой выражений на баше.

Или даже так:
367cd478d8201a5b802d614c0473a276
Рис 5. Графический калькулятор на PHP.