Alterator/evolution
Простейший язык описания интерфейсов
(всё описанное касается alterator версии 2.9 и старше)
Документ - основной кирпичик в построении интерфейса. Каждый документ есть виджет, и наоборот, каждый виджет есть некоторый документ. Простейшие документы представляют простейшие виджеты, например, кнопку, метку и так далее. Документы сложнее, композиция простейших, представляет комплексные виджеты - самостоятельные части диалога с пользователем. Кроме того каждый документ можно рассматривать ещё как некоторую программу на простом подмножестве языка программирования Scheme. Ну обо всём по порядку.
1. Документ как файл
Документ прежде всего текстовый файл, размещающийся где-то на файловой системе. Документы адресуются любым распространённым способом: через указание полного пути, через URI по правилам RFC-3986, через URI по правилам alterator.
2. Документ как программа
Во вторую, как не странно, очередь документ это программа на подмножестве языка программирования Scheme. Прежде чем мы увидим графическое воплощение документа, он загружается и исполняется интерпретатором. Как в каждом языке программирования, часть документа можно скрывать от интерпретатора - делать комментарии. Остаток строки после символа ";" интерпретатором не рассматривается. В дальнейшем изложении мы будем пользоваться комментарием для добавления пояснений в текст примеров.
C этой точки зрения документ представлен как множество вложенных друг в друга окружений. Каждое окружение не что иное как область видимости локальных переменных. Окружения отдельных документов не пересекаются и соответственно вы не можете использовать в одном документе вспомогательные функции, определённые в другом документе.
Для удобства управления окружениями предоставлено несколько базовых конструкций: 1. (document:insert <идентификатор>) В результате происходит вставка "окружения" внутрь существующего, <идентификатор> - это идентификатор документа как файла, определённый одним из перечисленных выше способов.
Пример:
"our environment" (document:insert "/other/document") ; в этом месте документа окажется новое окружение "our environment again"
2. (document:envelop keyword) , (document:end-envelop keyword) - для создания вложенного окружения вовсе не обязательно создавать отдельные файлы, достаточно окружить его инструкциями document:envelop и document:end-envelop. Последняя конструкция не обязательна если вы хотите распространить вложенное окружение до конца файла.
Пример:
"our environment" (document:envelop some-keyword) "nested environment" (document:end-envelop some-keyword) "our environment again"
"our environment" (document:envelop some-keyword) "nested environment" ; вложенное окружение распространяется до конца данного файла
3. (document:surround <идентификатор>) - иногда бывает полезно добавить не вложенное окружение, а наоборот, охватить данный документ каким-то окружением из другого файла.
Пример:
(document:surround "/some/document") "here"; наше окружение вложено в окружение документа "/some/document"
О том какие бывают окружения мы познакомимся в следующем разделе
3. Документ как виджет
У любого виджета есть атрибуты: текст у кнопки, длина у списка. Чтобы с атрибутами можно было работать - их надо создать. Для этого существует стандартное окружение with-attributes. В качестве дополнительных параметров в инструкцию document:envelop передаётся список имён атрибутов, которые мы хотим определить.
Пример:
(document:envelop with-attributes (width height text)) ; далее можно работать с этими атрибутами.
Информация об этих атрибутах сообщается графическому представлению виджета после того как он будет создан, то есть это есть не что иное как рычаги управления графическим представлением виджета.
Однако некоторые параметры надо знать прямо при создании представления, как минимум надо знать тип создаваемого представления (кнопка или список), а также родительский виджет, в который нужно вставить данный.
Поэтому существует особая разновидность атрибутов - атрибуты инициализации, которые создаются в окружении with-init-attributes.
Пример:
(document:envelop with-init-attributes (parent type)) ;далее можно работать с этими атрибутами
В alterator существует заранеее подготовленный документ со списком распространённых атрибутов - /std/attributes, который можно включать в качестве охватывающего окружения.
Пример:
(document:surround "/std/attributes") ;далее можно работать со стандартными предопределёнными атрибутами
После того как атрибуты созданы, их можно задавать. Принцип простой: "имя значение" или "(имя значение значение)"
Пример:
(document:surround "/std/attributes") type "label" (layout-policy 100 100)
На самом деле во втором случае скобки можно опустить ибо атрибут layout-policy знает, что ему требуется обязательно не одно, а два значения, атрибуту сообщается количество обязательных аргументов прямо при его создании. Вместо простого указания имени, имя и количество параметров заключаются в круглые скобки
(document:envelop with-attributes (text (layout-policy 2) width height)) layout-policy 100 100
Поэтому предыдущий пример можно переписать следующим образом
(document:surround "/std/attributes") type "label" layout-policy 100 100
Вообще общее правило такое, скобки вокруг атрибутов стоит употреблять только для повышения читаемости или для передачи аргументов в количестве большем чем обязательное:
(document:surround "/std/atributes") type "label" (layout-policy 100 100 center)
Примитивным виджетам - примитивные документы. Так для того чтобы создать кнопку - достаточно документа, в котором будет указан её тип:
(document:surround "/std/attributes") type "label"
Аналогично и для списков, меню, и прочих компонент стандартного конструктора GUI.
А как же из готовых примитивов строить сложные виджеты? Переходим на следующий уровень игры:
4. Документ как контейнер
Каждый виджет ни что иное как контейнер свойств: атрибутов, callback'ов и других вложенных виджетов. Каждый контейнер имеет минимум два предопределённых атрибута: тип ( со значением по-умолчанию "root") и указатель на родительский контейнер. Те самые type и parent.
Чтобы отличать вложенные окружения от полноценных виджетов, которые вставляются в данный существует особая конструкция - (document:subdocument <идентификатор>).
Пример:
(document:surround "/std/attributes") type "groupbox" title "some groupbox" (document:subdocument "/other/widget") ; контейнер вставленный в данный
Если мы хотим удобно использовать какой-либо документ, его стоит оформить как контейнер. Делается это при помощи окружения with-container-presentations. В качестве параметров перечисляются: 1. Имя контейнера, которым будем в дальнейшем пользоваться 2. Идентификатор документа, в котором содержится описание контейнера. 3. атрибуты по-умолчанию.
Первый параметр - имя функции на языке scheme, которая будет обеспечивать удобную работу с виджетом, а третий - небольшое упрощение жизни. Например, кнопка всегда создаётся с каким-то текстом, поэтому гораздо удобнее писать (button "some-name") вместо (button text "some-name")
Пример:
(document:surround "/std/attributes") (document:envelop with-container-presentations ( (label '/std/label text) (button '/std/button text) (vbox '/std/vbox)) width 100 height 200 (label "text") (button "text")
В результате получаем документ заданной ширины и высоты с вставленными в него кнопкой и меткой. Существует документ с предопределёнными традиционными атрибутами и контейнерами - "/std/base". Поэтому предыдущий пример сокращается до:
(document:surround "/std/base") width 100 height 200 (label "text") (button "text")
Имея вспомогательные функции для работы с контейнерами можно объединять целлые россыпи виджетов:
(docunment:surround "/std/base") width 100 height 200 (vbox (label "text") (button "text" height 20) (hbox (label "text" ) (checkbox "text" state #t)))
Как вы должно быть заметили каждый виджет (точнее его контейнерная фунция) принимает атрибуты по тому же принципу, что и основной документ, через пробел следуют серии "имя значение".
5. Документ как совокупность виджетов
В реальной практике требуется ещё уметь передавать атрибутам функции - обработчики событий (callbacks), а также уметь именовать виджеты, чтобы к ним можно было потом обратиться.
Именование происходит с помощью конструкции (document:id <имя> <виджет>) .
(document:surrounf "/std/base") (document:id my-button (button "text"))
В этом примере my-button становится синонимом конкретной контейнерной функции и все обращения к ней буду равносильны обращениям к конкретному виджету
(my-button height 100 width 100) ; зададим высоту и ширину для той самой кнопки
Обработчики событий - тоже атрибуты, только их значение задаётся специальным образом, а именно с помощью фунции make-callback.
(button "text" clicked (make-callback (write "I'm clicked")))
Для более удобной работы существует специальная конструкция when. Предыдущая конструкция равносильна:
(button "text" (when clicked (write "I'm clicked")))
Уж коли можно писать обработчики событий, то неплохо бы было иметь возможность получать текущие значения атрибутов, а также вызывать обработчики событий других виджетов.
Всё очень просто, если контейнерной функции передаётся атрибут с количеством аргументов меньше обязательного (чаще всего без аргументов), то это воспринимается как желание получить атрибуты. Если в процессе получения атрибута выясняется, что это обработчик (callback), то он немедленно исполняется.
(my-button text) ; вернуть текст кнопки (my-button clicked) ; "щелкнули" по кнопке программно ;пример для более сложных атрибутов (my-listbox row-item-text 2 "aaa") ; выставим текст второй строки в "aaa" (my-listbox row-item-text 2) ; получим текст второй строки
Вот пример диалога, когда из строки ввода считывается её содержимое и передаётся в метку
(document:surround "/std/base") (document:id my-label (label "default-value")) (document:id my-edit (edit "default-value")) (button "Change" (when clicked (my-label text (my-edit text))))
В следующих разделах будут рассмотрены уже более сложные техники работы с документами, вполне возможно что для работы вам они и не потребуются.
6. Документ как совокупность компонент
В контейнере содержатся как правило только базовые атрибуты и для проведения более сложных модификаций приходится:
- вытащить атрибут
- изменить атрибут
- сохранить атрибут
Это не удобно, поэтому есть атрибуты второго уровня: они перехватывают запрос на изменение (или получение) данных и проводят соответствующие модификации примитивных атрибутов. Вот несколько примеров подобных аттрибутов:
- count у listbox - получает все строки (атрибут rows) и подсчитывает их количество
- append-text у textbox - получает предыдущее значение текста, добавляет к нему новый и сохраняет результат.
Все аттрибуты второго уровня создаются при помощи окружения with-meta-attributes. Пример:
(with-meta-attributes ((count ((meta-get self widget) (length (or (simple-get widget rows) '()) )))) ;далее можно работать с аттрибутом count
Для каждого аттрибута определяется
- имя
- методы которые будут вызваны в случае запросов на получение (meta-get) и изменение (meta-set)
Каждому методу передаются два параметра: он сам (self) и виджет к которому он принадлежит (widget). Также предоставляются вспомогательные функции value-of для выяснения своего содержимого и simple-get, для запросов к атрибутам первого уровня.
Работа с мета-аттрибутами уже более сложная, требует более глубоких познаний в alterator, поэтому подробно рассматриваться не будет, желающие могут посмотреть предопределённые мета-аттрибуты в файле "/std/meta-atributes" , который также как и "/std/attributes" автоматически подгружается из "/std/base".
Ну и наконец высший пилотаж - если есть мета-аттрибуты, то должны быть и мета-виджеты. Возможно вы сделали какой-то хороший виджет и решили использовать его повторно в других проектах. Причём оформить его так, чтобы пользователи и не догадались, что это именно комбинированный виджет, а не примитивный. Такая возможность есть. Что виджет , что контейнер - всё едино. Не хватает только аттрибутов.
Существует ещё один тип аттрибутов - прокси-аттрибуты. Их задача - перехватывать запросы на получение/изменение значения и вызывать ваши фунции внутри виджета. Например, вы сделали специальный виджет для смены пароля. Пользователь запрашивает у вашего виджета password1, этот запрос перехватывается и вызывается функция внутри вашего виджета, которая возращает содержимое определённого edit.
Итак, стало быть мета-виджет - это обычный документ, в котором дополнительно определены виртуальные прокси аттрибуты. Последние создаются с помощью окружения with-proxy-attributes. А вот и пример с паролями:
(document:surround "/std/base") (document:insert "/std/functions") (document:envelop with-proxy-attributes ((password1 ((proxy-get self) (p1 text)) ((proxy-set self value) (p1 text value))) (password2 ((proxy-get self) (p2 text)) ((proxy-set self value) (p2 text value))))) (vertical-spacer) (hbox layout-policy 100 -1 (label "First password:" layout-policy 30 -1) (document:id p1 (edit "" echo stars layout-policy -2 -1))) (hbox layout-policy 100 -1 (label "Second password:" layout-policy 30 -1) (document:id p2 (edit "" echo stars layout-policy -2 -1))) (vertical-spacer)
Если вы захотите где-то поработать с этим виджетом, то надо всего-лишь до-определить недостающие атрибуты (именно обычные атрибуты, чтобы контейнер понял с какой стороны к нему обращаются) и всё ... дальше работать как обычно.
(document:surround "/std/base") (document:envelop with-container-presentations ((password-box '/samples/presentation2)) ) (document:envelop with-attributes (password1 password2)) (groupbox "Subwidget demo" layout-policy 100 100 (vbox layout-policy 100 100 children-align center (document:id info (label "Passwords here...")) (document:id box (password-box layout-policy 70 50 password1 "123" password2 "1234" )) (button "Get passwords..." (when clicked (info text (string-append " 1:" (->string (box password1)) " 2:" (->string (box password2)))))) (button "Reset passwords..." (when clicked (box password1 "zzzz" password2 "q")))))
7. Переходы между документами
Доступны следующие команды для перехода между разными виджетами:
- document:replace-in-widget - замещает содержимое указанного виджета на указанный документ. Возможна передача url нового документа с параметрами.
- document:replace - вариант предыдущей команды для работы с текущим документом. Если документ был вставлен куда-то с помощью команды document:insert, до замена произойдёт во всём документе, если же он был вставлен куда-то с помощью команды document:subdocument (или как container-presentation), то замена содержимого произойдёт только в нём и не затронет родительского документа.
- document:popup - открывает указанный документ в новом модальном окне.
- document:window - открывает указанный документ в новом не-модальном окне, в качестве первого параметра передаётся процедура, которая будет вызвана после того как это окно будет закрыто.
- document:end - закрывает текущее модальное окно. Когда закроется последний документ - работа приложения будет завершена.