http://www.aros.orgAROS-ExecAROS-Exec ArchivesPower2People
kitty mascottop logo menu

Руководство разработчика приложений Zune

Предисловие

Что такое Zune?

Zune является объектно-ориентированным набором для разработки приложений с графическим интерфейсом пользователя (GUI). Это практически полный аналог MUI (Magic User Interface), как на уровне API, так и на уровне Look&Feel, самого популярного на Amiga shareware-интерфейса Стефана Штунтца. Таким образом, разработчики знающие MUI, почувствуют себя здесь "как дома", а остальные смогут изучить понятия и особенности, общие для обоих средств. Постулируется, что:

  • Программист может затратить намного меньше времени при проектировании интерфейса: в Zune нет привязки элементов интерфейса к абсолютным значениям, среда чувствительна к кеглям шрифтов и сама адаптирует размеры и расположение любых окон в зависимости от шрифтов пользователя. Zune предоставляет семантический доступ к элементам проектируемого интерфейса, а его свойства (такие, как отступ элемента от края окна в пикселях) регулируются автоматически.
  • Пользователю намного легче контролировать вид и поведение интерфейса, спроектированного программистом, он получает возможность специфической настройки параметров окружения Zune.

Zune основан на системе BOOPSI (Basic Object Oriented Programming System for Intuition), унаследованной от AmigaOS и используемой в объектно-ориентированном программировании на Си. Классы Zune не являются дочерними по отношению к существующим для элементов интерфейса классам BOOPSI (т.е., не являются простым расширением их возможностей). Напротив, базовым классом (в иерархии Zune) является класс Notify — дочерний относительно корневого класса BOOPSI.

Предпосылки

Для понимания концепции Zune, более чем приветствуется знание парадигмы объектно-ориентированного программирования (ООП). Вы можете воспользоваться Google для поиска и изучения образовательных материалов, посвящённых ООП.

Также, желательно, владение такими ключевыми понятиями AROS (и AmigaOS), как список тегов (taglist) и система BOOPSI. Хорошим подспорьем здесь, безусловно, является руководство "Amiga Reference Kernel Manuals" (известное как RKM).

Поскольку Zune является аналогом MUI, вся документация, имеющая отношение к MUI, применима и к Zune. В частности, последняя версия инструментария для разработчиков интерфейсов MUI доступна здесь. Среди прочих, в этом LHA-архиве есть 2 документа особенно рекомендуемых к прочтению:

  • MUIdev.guide, документация MUI-программиста.

  • PSI.c, исходный код приложения, демонстрирующего все современные методы

    проектирования и создания динамических объектов интерфейсов MUI.

Также этот архив содержит документацию (MUI autodocs), которая является описанием и для всех существующих классов Zune.

Реализация BOOPSI

Концепции

Класс

Класс (class), по сути, является лишь абстракцией, типом, описывающим объекты этого класса с общими структурой и поведением, и задаётся именем, родительским классом и диспетчером (dispatcher). Описание вводится путем указания типа и свойств класса:

  • имя: в случае, если класс является общим (public) - это строка, характеризующая название класса и его область видимости, что делает его доступным любому приложению в системе. В случае, если класс является локальным (private) - отсутствует, и такой класс не может использоваться нигде, кроме одного-единственного приложения.
  • родительский класс: все классы BOOPSI формируются в иерархическом порядке, и являются дочерними по отношению к корневому классу rootclass. Это позволяет каждому подклассу иметь собственную версию операции, производную от родительской, либо ту же самую, что и у родителя. Класс, содержащий подклассы, также называют базовым (base class) или суперклассом (super class).
  • диспетчер: предоставляет доступ ко всем операциям класса, которые называют методами. Гарантирует, что каждая операция будет обеспечена исполнением соответствующего ей кода или будет передана родителю данного класса (суперклассу).

В BOOPSI типом класса является Class * или IClass.

Объект

Объект является структурной единицей (реализацией) класса. Каждый объект обладает своими свойствами (состоянием), но при этом поведение всех объектов одного класса одинаково. Объект относится к нескольким классам, если исчислять их от его класса до корневого класса rootclass, что определяется свойством наследования.

Для объекта типом BOOPSI является Object *. Он не содержит доступных напрямую полей.

Атрибут

Атрибут находится в связи с структурой данных (переменными состояния) каждого объекта: изменять эти данные непосредственно вы не можете. Возможно только установить или получить значения атрибутов объекта (также называемых свойствами) для изменения его внутреннего состояния. Атрибуты объекта (со стороны системы) ассоциированы с тегами (Tag) (со стороны программиста) (это значения, имеющие тип ULONG и связанные с TAG_USER).

Для изменения атрибутов объектов используются функции GetAttr() и SetAttrs().

Атрибуты (один или несколько) могут быть следующих видов:

  • Назначаемый при установке (Initialization-settable)(I) : Атрибут может быть передан как параметр лишь при создании объекта.
  • Доступный для изменения (Settable)(S) : Можно установить значение этого атрибута в любое время (не только при создании объекта).
  • Доступный для чтения (Gettable)(G) : Вы можете лишь получить значение атрибута.

Метод

Методом в BOOPSI называется функция, которой в виде параметров передаются имя объекта, его класс и сообщение:

  • объект: имя объекта, над которым производится действие.
  • класс: класс, соответствующий объекту.
  • сообщение: содержит идентификатор (ID) метода, определяющий функцию, вызываемую диспетчером и передаваемые ей параметры

Для отправки сообщения объекту используется функция DoMethod(). Сначала метод будет применён к указанному классу. Если в классе определен этот метод, то сообщение будет обработано. В противном случае, будут перебираться родительские классы до тех пор, пока сообщение не будет обработано одним из них, или не будет достигнут rootclass (в этом случае так и не определённое сообщение будет молча отвергнуто).

Примеры

Рассмотрим основные приёмы объектно-ориентированного программирования с BOOPSI:

Получение атрибута

Попробуем запросить данные объекта MUI String:

void f(Object *string)
{
    IPTR result;

    GetAttr(string, MUIA_String_Contents, &result);
    printf("String content is: %s\n", (STRPTR)result);
}

Здесь string - объект, MUIA_String_Contents - получаемый атрибут, &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)"hello", TAG_DONE);
  • Указатели, передаваемые в качестве аргументов должны иметь тип IPTR (указатель на целую переменную, может содержать адрес значений типа int) иначе компилятор будет выдавать предупреждения.
  • Вслед за атрибутом, функции SetAttrs передаётся список тегов, поэтому перечисление должно заканчиваться на TAG_DONE.

Вам наверняка покажется полезным макрос set():

set(string, MUIA_String_Contents, (IPTR)"hello");

Однако, только с помощью SetAttrs() вы сможете установить несколько атрибутов за один раз:

SetAttrs(string,
         MUIA_Disabled, TRUE,
         MUIA_String_Contents, (IPTR)"hmmm...",
         TAG_DONE);

Вызов метода

Рассмотрим наиболее часто применяемый в программах Zune метод, метод обработки событий, вызываемый в основном цикле программы:

result = DoMethod(obj, MUIM_Application_NewInput, (IPTR)&sigs);
  • Параметры функций метода не являются списком тегов, и не должны заканчиваться с TAG_DONE
  • Указатели приходится типизировать как IPTR, чтобы избежать предупреждений, от чего смысл их применения не меняется

Hello world

/documentation/developers/zune-dev/images/hello.png

С начала, так с начала. Эта программа не разочарует новичка.

Исходный код

Рассмотрим наш первый реальный пример:

// 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;

    // Создание GUI: окна wnd, текста Hello world! и кнопки Ok
    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);

        // Реакция на нажатие кнопки выхода
        DoMethod(but, MUIM_Notify, MUIA_Pressed, FALSE,
                 (IPTR)app, 2,
                 MUIM_Application_ReturnID, MUIV_Application_ReturnID_Quit);

        // Открываем окно wnd
        set(wnd, MUIA_Window_Open, TRUE);

        // Проверяем, что окно wnd действительно было открыто
        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;
}

Комментарии

Замечание

Мы не открываем библиотеки вручную т.к. это делается за нас автоматически.

Создание GUI интерфейса

Мы использовали макросы для облегчения программирования интерфейса программы. Приложение Zune всегда имеет 1 (и только 1) объект Приложения (ApplicationObject):

:    app = ApplicationObject,

Приложение может иметь 0,1 или более объектов окон WindowObject. Чаще всего окно - одно единственное:

:        SubWindow, wnd = WindowObject,

Будет хорошо, если заголовок окна будет содержать название приложения:

:        MUIA_Window_Title, "Hello world!",

Окно может иметь 1 (и только 1) дочерний объект (Child), обычно это группа (group). Наша группа будет вертикальной (VGroup), это означает, что все входящие в неё дочерние объекты (children) будут группироваться по вертикали:

: 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;

И что, вы всё ещё нуждаете в графических инструментах для создания GUI ? :-)

Обработка ошибок

Если окажется невозможным создание любого из объектов в структуре описанной нами выше, Zune уничтожит все объекты (включая те, которые удалось создать) и возвратит код ошибки. В обратном случае, вы получите полностью рабочий 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 параметра.

В случае c кнопкой "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)

Этому методу передаются сигналы событий, которые он должен обработать (сообщения от Wait(), или 0), значение указателя sigs будет изменяться, принимая значения ожидаемых Zune сигналов (очередных сообщений от Wait()) и в результате это значение будет возвращено. Поэтому обнуление sigs в цикле необходимо. Этот механизм возврата значений исторически был единственным способом реакции на события. Однако, поскольку он был слишком "неудобоварим", впоследствии от него стали отказываться в пользу создания отдельных классов и объектно-ориентированной структуры приложения.

Тело самого цикла приложения весьма простое. Здесь мы видим лишь ожидание сигналов и обработку нажатия Ctrl + С для обеспечения принудительного выхода из цикла:

:        {
:            if (sigs)
:            {
:                sigs = Wait(sigs | SIGBREAKF_CTRL_C);
:                if (sigs & SIGBREAKF_CTRL_C)
:                    break;
:            }
:        }

Заключение

Эта программа позволила вам начать изучение Zune и немного поработать на дизайном GUI приложения, но не более того.

Сборка

Чтобы собрать эту программу кросс-компилятором i386-aros-gcc, используйте следующую команду:

i386-aros-gcc -o hello -D__AROS__ hello.c -lmui

Реакция на события

Согласно комментариям к hello.c, приведенным выше, вы должны использовать MUIM_Notify для вызова метода при возникновении ожидаемого вами события. Если же требуется описать более специфичную реакцию программы на события, необходимо воспользоваться одним из следующих алгоритмов:

  • MUIM_Application_ReturnID: можно возвращать некий идентификатор ID на последующих шагах цикла, и проверять это значение в цикле. Можно задать несколько таких идентификаторов и в зависимости от возвращаемого значения вызывать разные методы. Старый и дурацкий способ обхода цикла.
  • MUIM_CallHook: использование стандартного для Amiga механизма вызова (callback) пользовательских функий при возникновении событий, управляемого структурой Hook. Способ, не имеющий ничего общего с ООП, но вполне допустимый.
  • Использование ООП: вызов из тела цикла метода, принадлежащего одному из созданных вами ранее классов. Это лучшее решение с точки зрения объектно-ориентированной структуры программы, однако, оно мало подходит для новичков в ООП и программистов, не привыкших тратить много времени на "изящества".

Copyright © 1995-2018, The AROS Development Team. Все права защищены.
Amiga© является торговым знаком Amiga Inc. Все прочие торговые знаки принадлежат их собственникам.