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

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


    Предыстория


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

    Подробнее
    Реклама

    Комментарии 14

      +13
      Идеальное пользовательское соглашение!
        +13
        Да и расширения заголовочного файла *.hui не отстает) теперь можно человека обзывать заголовочном файлом )
          0
          Я знал, что найду такой комментарий!
        +5
        Собственно, Вы сделали расширенный аналог Zenity и Yad на Qt. В целом, это может быть очень интересным, если будет кроссплатформенным.
          +3
          Кстати, если сделать поддержку опций zenity или yad, то будет прямой конкурент с рядом преимуществ.
            +3
            Или, если идти к истокам, классического (и до сих пор используемого) dialog, xdialog, а также 4DOS и Norton Utilities! Пусть расцветают все цветы.
              0
              Весело кстати используемого. Я на их основе сбацал меню для настройки конфига на базе ассоциативных массивов баша.
            +3
            Если бы не XML, то было бы неплохо. Даже не менее универсальный json выглядит на порядок дружелюбнее, а специализированные форматы значительно удобнее и читабельнее.
              0
              А в чём был смысл брать XML, который и так не подарок для редактирования, но хотя бы есть средства редактирования, и запиливать «свой формат» XML подобный но с костылями типа комментариев # и [[ ]]?
              Много работы перекладывается на плечи пользователя — логичнее было бы передавать в вызываемые скрипты ВСЮ форму в удобном для обработки стандартными средствами баша виде, или ещё лучше — организовывать временный файл состояния формы доступный для грепания.
              Update без возможности удалять виджеты — face palm
                0
                # нужен был для комментирования кода, особенно при отладке. А #include вообще спас от слепоты.
                [[ ]] вынуждено добавлено, потому что < и > в иксемели зарезервированные и их нельзя использовать.
                Ну а остальное, это взгляд на роад мэп. Можно как угодно.
                А к удалению виджетов дело не дошло, потому что к тому времени уже отпустило.
                  +1
                  Так в XML есть же комментарии <!-- --> Зачем эти непонятные извращения?
                    0
                    Сделать было не сложно, но удобно. На то время, когда не отпускало, казалось это очень хорошей идеей.
                    0
                    Откройте для себя CDATA
                  +5
                  Большое спасибо за интересный пост. Ваша разработка напомнила мне весьма занимательный подход для построения простеньких мобильных приложений с UI. Этот подход когда-то был использован в смартфонах Motorola, работающих под управлением GNU/Linux. Интерфейс этих гаджетов, кстати, тоже использовал библиотеку Qt, вернее одну из ее версий для Embedded-устройств — Qt/Embedded версии 2.3.8 (библиотека Qt в то время принадлежала компании Trolltech ASA). Правда, Motorola внесла свои изменения в Qt/Embedded и дополнила библиотеку собственным фреймворком. Так вот, для упрощения разработки различных программ под эти устройства, были созданы так называемые «ShowModules», представлявшие собой простенькие C++/Qt-based программы, принимающие несколько аргументов командной строки или читающие настройки из специального файла. На основе полученных данных и строился интерфейс приложения. Создание программ и полезных утилит с помощью «ShowModules» было очень простым занятием, начинающий разработчик мог написать собственную программу используя Shell-кодинг или известный ему язык программирования, который поддерживал вызовы внешних утилит. Например, для того чтобы отобразить на экране телефона вот такой простенький диалог:

                  image

                  Нужно было всего-лишь выполнить скрипт:

                  #!/bin/sh
                  I4="Scan Complete"
                  I5="Your SD card is clean"
                  showMsg "$I4" "$I5" 2

                  Таким образом, с помощью набора «ShowModules» получались вполне работающие приложения с нативным интерфейсом. Например, такие:

                  image

                  Исходный код утилиты, в котором весьма просто разобраться даже новичкам. Обратите внимание, что все «ShowModules» имеют префикс «show» и несколько аргументов, отвечающих за отображаемую информацию и элементы управления.

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

                  image

                  Использовали специальные конфигурационные файлы, описывающие не только UI, но и поведение программы при выборе каких-либо пунктов. Если посмотреть на файл по ссылке выше, то можно увидеть, что при выборе пункта списка «Scan SD card», будет выполнен скрипт из текущего каталога с именем «SD_scan.sh», который просканирует карту памяти.

                  Несмотря на то, что для этих смартфонов можно было разрабатывать приложения и на C++/Qt, разработчики довольно-таки часто использовали именно такие «костыли». Всё-таки порог вхождения в C++/Qt на этой платформе был неоправданно высок (2006(7) год, нет даже нормальных IDE для C++/Qt, полностью отсутствует необходимая документация); создание чисто нативного C++/Qt приложения требовало большого количества времени, нервов и усидчивости. Ведь нормальный SDK для этих платформ Motorola так и не выпустила, поэтому для разработки использовался любительский, с отреверсенными на коленке библиотеками, следовательно, отлов багов в приложении был тем ещё джедайством. Но это уже другая история…

                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                  Самое читаемое