AROS/Zune
Предисловие
правитьЧто такое Zune?
правитьZune является объектно-ориентированным графическим интерфейсом пользователя в операционной системе AROS. Это практически полный аналог MUI (как на уровне API, так и на уровне Look&Feel), самого популярного на Amiga shareware-интерфейса Стефана Штунтца. Таким образом, разработчики знающие MUI, почувствуют себя здесь «как дома», а остальные смогут изучить понятия и особенности общие для обоих средств. Постулируется, что:
- Программист может затратить намного меньше времени при проектировании интерфейса: в Zune нет привязки элементов интерфейса к абсолютным значениям, среда чувствительна к кеглям шрифтов и сама адаптирует размеры и расположение любых окон в зависимости от шрифтов пользователя. Zune предоставляет семантический доступ к элементам проектируемого интерфейса, а его свойства (такие как отступ элемента от края окна в пикселях) регулируются автоматически.
- Пользователь может намного лучше контролировать Look&Feel интерфейса спроектированного программистом и получает возможность специфической настройки параметров окружения Zune.
Zune основан на системе BOOPSI, унаследованной от AmigaOS и используемой для объектно-ориентированного программирования на языке [[:w:C++|C++]. Классы Zune не являются дочерними по отношению к существующим для элементов интерфейса классам BOOPSI (т.е. не являются простым расширением их возможностей). Базовым классом (в иерархии Zune) является Notify — дочерний относительно корневого класса BOOPSI.
Предпосылки
правитьДля понимания концепции Zunе, более чем приветствуется знание парадигмы объектно-ориентированного программирования. В противном случае, вы можете воспользоваться Google для поиска и изучения образовательных материалов посвещённых ООП.
Также, желательно, владение такими ключевыми понятиями AROS (и AmigaOS), как список тегов (taglist) и система BOOPSI. Хорошим подспорьем здесь, безусловно, является руководство «Amiga Reference Kernel Manuals» (известное как RKM).
Поскольку, Zunе является аналогом Magic User Interface (MUI), вся документация имеющая отношение к MUI, применима и к Zune. В частности, последняя версия инструментария для разработчиков интерфейсов MUI, доступна здесь. В этом LHA-архиве есть 2 документа особенно рекомендуемых к прочтению:
- MUIdev.guide — документация программиста интерфейсов MUI;
- PSI.c — исходный код приложения демонстрирующего все современные методы проектирования и создания динамических объектов интерфейсов MUI.
Также, этот архив содержит документацию «MUI autodocks», которая является описанием и для всех существующих классов Zunе.
Реализация BOOPSI
правитьКласс
правитьКласс является объектом обладающим свойствами: имя, родительский класс и диспетчер.
- имя: строка характеризующая название класса и его область видимости. Класс может быть общим (public) и использоваться любым приложением в системе, а может быть локальным (private) и не использоваться нигде, кроме одного единственного приложения.
- родительский класс: все классы BOOPSI формируются в иерархическом порядке, и являются дочерними по отношению к корневому классу rootclass. Это позволяет каждому подклассу иметь собственную версию операции, либо производную от родительской, либо ту же самую, что и у родителя. Класс содержащий подклассы, также называют базовым или супер-классом.
- диспетчер: предоставляет доступ ко всем операциям (называемым методами) принадлежащим данному классу. Гарантирует, что каждая операция будет обеспечена исполнением соответствующего ей кода или будет передана к родителю данного класса.
BOOPSI type for a class is Class * also known as IClass.
Объект
правитьОбъект является частным случаем класса: каждый объект обладает определёнными свойствами, но все объекты одного класса ведут себя одинаково. Объект имеет несколько классов, если исчислять их от него самого (один из существующих классов) до корневого класса rootclass.
Три основных свойства объекта:
- совместное использование данных и методов работы с ними;
- возможность создания нового класса на основе старого, или несколько старых, называемых родительскими;
- возможность изменения методов родительского класса.
BOOPSI type for an object is Object *. It has no field you can directly access.
Атрибут
правитьАтрибут обеспечивает доступ к данным принадлежащим объекту, однако вы не можете изменять эти данные непосредственно. Возможно только установить или получить значения атрибутов (также называемых свойствами) объекта для изменения их внутреннего состояния. Атрибуты объекта (со стороны системы) ассоциированы с тегами (со стороны программиста). Например: ULONG value or'ed with TAG_USER
GetAttr()
и SetAttrs()
используются для изменения атрибутов объектов.
С атрибутами (одним или несколькими) возможно выполнить следующее:
- Инициализовать с установкой (I) : атрибут может быть передан как параметр при создании объекта.
- Установить (S) : Вы можете установить значение атрибута в любое время (не только при создании объекта).
- Получить (G) : Вы можете получить значение любого атрибута.
Метод
правитьМетодом в BOOPSI считается функция, которой передаются имя объекта, его класс и сообщение в виде параметров:
- объект: имя объекта метод которого вызывается;
- класс: соответствующий класс для этого объекта;
- сообщение: содержит идентификатор метода, определяющий функцию вызываемую диспетчером и передаваемые для неё параметры.
Для отправки сообщения объекту используется функция DoMethod()
. Сначала метод будет применён к указанному классу. Если класс поддерживает этот метод, то сообщение будет обработано. В противном случае, будут перебираться родительские классы до тех пор пока сообщение не будет обработано одним из них, или не будет достигнут класс rootclass
(тогда, сообщение будет молча отвергнуто).
Примеры
правитьРассмотрим основные приёмы работы с BOOPSI:
Получение атрибута
правитьПопробуем запросить данные объекта MUI String:
void f(Object *string)
{
IPTR result;
GetAttr(string, MUIA_String_Contents, &result);
printf("String содержит: %s\n", (STRPTR)result);
}
Object *
— тип объектов BOOPSI.- Для типизации возвращаемого значения должен использоваться тип IPTR, поскольку это значение может оказаться как целым числом, так и указателем. Кроме того, это значение хранится в памяти, поэтому использование другого типа (не IPTR) привело бы к утечкам памяти!
- Когда мы запрашиваем данные объекта MUI
String: MUIA_String_Contents
, как и любой другой атрибут, имеет тип ULONG (это тег).
В интерфейсах Zune чаще используются макросы get()
и XGET()
. Например:
get(string, MUIA_String_Contents, &result);
result = XGET(string, MUIA_String_Contents);
Установка атрибута
правитьРассмотрим изменение данных полученной строки:
SetAttrs(string, MUIA_String_Contents, (IPTR)"превед", TAG_DONE);
- Указатели передаваемые в качестве аргументов должны иметь тип IPTR (это значит, указатель на целую переменную, может содержать адрес значений типа int) чтобы не читать потом предупреждения компилятора.
- Список параметров объекта (taglist) передаваемый в
SetAttrs
обязательно должен заканчиваться тегомTAG_DONE
.
Вы можете решить, что вам полезен макрос set()
:
set(string, MUIA_String_Contents, (IPTR)"превед");
Однако, только вызовом SetAttrs()
вы сможете установить несколько атрибутов за один раз:
SetAttrs(string,
MUIA_Disabled, TRUE,
MUIA_String_Contents, (IPTR)"Гммм...",
TAG_DONE);
Вызов метода
правитьМетоды в интерфейсах Zune, как правило, вызываются из тела цикла приложения, при обработке событий:
result = DoMethod(obj, MUIM_Application_NewInput, (IPTR)&sigs);
- Эти параметры не являются taglist, и следовательно не заканчиваются тегом
TAG_DONE
. - Мы всё равно будем передовать указатели как IPTR, ведь смысл их применения не меняется.
Hello world
правитьЭта программа, всегда самая первая! Мы не хотим разочаровать новичков.
Исходный код
правитьРассмотрим наш первый реальный пример :
// gcc hello.c -lmui
#include <exec/types.h>
#include <libraries/mui.h>
#include <proto/exec.h>
#include <proto/intuition.h>
#include <proto/muimaster.h>
#include <clib/alib_protos.h>
int main(void)
{
Object *wnd, *app, *but;
// Создаём интерфейс
app = ApplicationObject,
SubWindow, wnd = WindowObject,
MUIA_Window_Title, "Hello world!",
WindowContents, VGroup,
Child, TextObject,
MUIA_Text_Contents, "\33cHello world!\nHow are you?",
End,
Child, but = SimpleButton("_Ok"),
End,
End,
End;
if (app != NULL)
{
ULONG sigs = 0;
// Реакция на кнопку закрытия окна и клавишу Escape - выход
DoMethod(wnd, MUIM_Notify, MUIA_Window_CloseRequest, TRUE,
(IPTR)app, 2,
MUIM_Application_ReturnID, MUIV_Application_ReturnID_Quit);
// Реакция на нажатие кнопки "Ok" в интерфейсе - выход
DoMethod(but, MUIM_Notify, MUIA_Pressed, FALSE,
(IPTR)app, 2,
MUIM_Application_ReturnID, MUIV_Application_ReturnID_Quit);
// Открываем окно
set(wnd, MUIA_Window_Open, TRUE);
// Проверяем, что наше окно действительно было открыто
if (XGET(wnd, MUIA_Window_Open))
{
// Цикл приложения (интерфейса) Zune
while((LONG)DoMethod(app, MUIM_Application_NewInput, (IPTR)&sigs)
!= MUIV_Application_ReturnID_Quit)
{
if (sigs)
{
sigs = Wait(sigs | SIGBREAKF_CTRL_C);
if (sigs & SIGBREAKF_CTRL_C)
break;
}
}
}
// Уничтожаем наше приложение со всеми его объектами
MUI_DisposeObject(app);
}
return 0;
}
Комментарии
правитьЗамечание
правитьМы не открываем библиотеки вручную т.к. это делается за нас автоматически.
Создание интерфейса
правитьДля облегчения программирования интерфейса используются макросы. Интерфейс Zune всегда имеет 1 (и только 1) объект Приложения:
app = ApplicationObject,
</sorce>
Приложение может иметь 0, 1 или больше объектов Окна. Чаще всего окно одно единственное:
<syntaxhighlight lang="cpp">
SubWindow, wnd = WindowObject,
Будет хорошо, если заголовок окна будет содержать название приложения:
MUIA_Window_Title, "Hello world!",
Окно может иметь 1 (и только 1) дочерний объект (Child), обычно это группа. Наша группа будет "вертикальной" (VGroup
), это означает что все входящие в неё дочерние объекты будут группироваться по вертикали:
WindowContents, VGroup,
Группа должна иметь, как минимум 1 дочерний объект. В нашем случае, это будет обыкновенный текст (TextObject):
Child, TextObject,
Zune поддерживаются различные escape-коды (ниже, через \33c производится центрование текста) и перевод каретки ( \n ):
MUIA_Text_Contents, "\33cHello world!\nHow are you?",
Макрос End
должен завершать описание любого макроса вида xxxObject (в нашем случае, TextObject):
End,
Теперь добавим в нашу группу второй дочерний объект, кнопку! Помимо мыши, она будет откликаться на комбинацию клавиш RAmiga + o (укажем на это символом подчёркивания до буквы "O")
Child, but = SimpleButton("_Ok"),
Завершаем описание группы:
End,
Завершаем описание окна:
End,
Завершаем описание интерфейса:
End;
И что, вы всё ещё нуждаете в графических построителях интерфейсов? :-)
Обработка ошибок
правитьЕсли любой из объектов в структуре описанной нами выше, не сможет быть создан, Zune уничтожит все объекты (включая те, которые удалось создать) и возвратит код ошибки. В противном случае, вы получите полностью рабочий интерфейс приложения:
if (app != NULL)
{
...
Когда мы завершаем работу приложения, необходимо вызвать метод MUI_DisposeObject()
, передав ему указатель на созданную структуру приложения. Это необходимо для уничтожения всех созданных объектов и освобождения всех использованных ресурсов:
...
MUI_DisposeObject(app);
}
Обработка сообщений
правитьОбработка сообщений является простым путём создания реакций интерфейса на возникающие события (такие как, нажатие кнопки). Принцип: мы получаем сообщение, когда определённый атрибут, определённого объекта, будет установлен в определённое значение:
DoMethod(wnd, MUIM_Notify, MUIA_Window_CloseRequest, TRUE,
Здесь, мы ожидаем когда атрибут MUIA_Window_CloseRequest нашего объекта Окна (wnd) будет установлен в TRUE
(значит, пользователь нажал кнопку). Когда сообщение будет получено, мы предписываем нашему объекту приложения вернуть код MUIV_Application_ReturnID_Quit
на следующей итерации цикла приложения:
(IPTR)app, 2,
MUIM_Application_ReturnID, MUIV_Application_ReturnID_Quit);
Поскольку требуется определить, что же именно произошло, необходимо указать число параметров передаваемых MUIM_Notify: 2 параметра.
В случае кнопки «Ok», мы ожидаем когда атрибут MUIA_Pressed
окажется установленным в FALSE
, что будет означать нажатую и отпущенную пользователем кнопку «Ok» (реакция на простое нажатие кнопки является плохой практикой, т.к. вы можете захотеть отпустить кнопку мыши вне кнопки интерфейса, и таким образом отказаться от действия. К тому же, мы можем просто захотеть увидеть, как она выглядит в нажатом состоянии). В остальном, всё полностью аналогично предыдущему примеру:
DoMethod(but, MUIM_Notify, MUIA_Pressed, FALSE,
(IPTR)app, 2,
MUIM_Application_ReturnID, MUIV_Application_ReturnID_Quit);
Открытие окна
правитьОкно не будет открыто, пока вы не «попросите» Zune об этом:
set(wnd, MUIA_Window_Open, TRUE);
Если объекты согласно структуре описанной нами выше были созданы удачно, вы увидете окно. Но эта операция может завершиться с ошибкой! Таким образом, мы никогда не должны забывать проверять атрибут объекта окна, который должен быть установлен в TRUE:
if (XGET(wnd, MUIA_Window_Open))
Цикл приложения
правитьДорогие друзья, позвольте представить вам идеальный цикл интерфейса Zune:
ULONG sigs = 0;
Никогда не забывайте обнулять сигналы (sigs
) ... Тестовый цикл приложения с использованием метода MUIM_Application_NewInput
:
...
while((LONG) DoMethod(app, MUIM_Application_NewInput, (IPTR)&sigs)
!= MUIV_Application_ReturnID_Quit)
Обнуление sigs
необходимо, т.к. первые же сигналы обрабатываемых событий (возвращающие Wait()
, или 0), должны будут изменить sigs
, поместив туда ожидаемые Zune сигналы (до следующего Wait()
), и вернув полученное значение. Описанный здесь механизм возврата значений, исторически был единственным способом реакции на события. Но, поскольку, он «неудобоварим» в программировании, впоследствии от него стали отказываться в пользу создания собственных классов и объектно-ориентированной структуры приложения.
Тело цикла приложения. Ожидание сигналов и обработка нажатия Ctrl + С, для принудительного выхода из цикла:
{
if (sigs)
{
sigs = Wait(sigs | SIGBREAKF_CTRL_C);
if (sigs & SIGBREAKF_CTRL_C)
break;
}
}
Резюме
правитьИспользуя вызовы Zune вы можете реализовать интерфейс приложения, но не более того.
Реакция на события
правитьСогласно комментариям hello.c выше, вы должны использовать MUIM_Notify, для вызова метода при возникновении ожидаемого вами события. Если же требуется описать более специфичную реакцию интерфейса, необходимо воспользоваться одним из следующих алгоритмов:
- MUIM_Application_ReturnID: можно возвращать некий идентификатор на каждом шаге цикла, и проверять это значение в цикле. Можно задать несколько таких идентификаторов и в зависимости от возвращаемого значения вызывать разные методы. Старый дурацкий способ обхода цикла.
- MUIM_CallHook: использование стандартного для Amiga механизма вызова пользовательских функций при возникновении событий, управляемого структурой Hook. Способ не имеющий ничего общего с ООП, но вполне допустимый.
- Использование ООП: вызов из тела цикла метода принадлежащего одному из созданных вами ранее классов. Это лучшее решение с точки зрения объектно-ориентированной структуры интерфейса. Однако, он мало подходит для новичков в ООП и программистов привыкших не тратить много времени на интерфейс.