Haiku — свободная операционная система для персональных компьютеров, первая версия которой нацелена на бинарную совместимость с операционной системой BeOS.

Haiku воплощает в себе основные идеи BeOS. Это будет модульная система, архитектурно решённая как «модульное ядро», способная динамически подгружать необходимые модули. К интересным особенностям системы следует отнести архитектуру трансляторов — системных интерпретаторов файловых форматов (например, JPEG).

О проекте

ad block

Поддержка сайта: Рекомендуем хорошую транспортную компанию со своим транспортом ребята очень хорошо работают, : : Купить в Наро-Фоминске карьерный песок для засыпки ям и котлованов.

Закладки

Ранние подарки к Рождеству?

Вчера Christof Lutteroth послал в рассылку для разработчиков Haiku e-mail, где представил результаты двух успешных проектов университета Окланда последнего года. Оба проекта возглавлял он и его коллега Gerald Weber.

Первый проект очень впечатлил нас в IsComputerOn, и мы надеемся, станет целым направлением в работе над app_server. Проект делали Ahmed Al Hassani и Mohannad Hammadeh и он называется "Более управляемый многооконный интерфейс". Ahmed и Mohannad расширили app_server Haiku функционалом распределения и складывания окон. Теперь окна могут располагаться, не перекрывая друг друга, бок к боку или располагаться, подобно табам в браузере. Довольно слов, давайте посмотрим их скринкаст, там всё видно (впечатляет!! - ред.)

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

Второй проект называется "Многоплатформенные документо-ориентированные GUI" и выполнен Джеймсом и Джоном Кимом. Они взяли Окландскую Модель (Auckland Layout Model (ALM)), она уже есть в Haiku. И приделали ей новый функционал, который позволяет пользователям переключать GUI в "режим редактирования", в котором пользователь пожет редактировать GUI через WYSIWYG редактор. Изменять его под себя и сразу же пользоваться. Лейауты могут сохраняться и загружаться из XML файлов, которые могут использоваться на разных платформах (Java, .NET, Haiku). Давайте посмотрим скринкаст

окончание

Разместить всё, Часть 1

Необходимость в системе автоматического расположения элементов на странице

Одним из главных недостатков Be API, на который жаловались серьёзные программисты, была зависимость от размера шрифтов. То есть вы могли сделать GUI для одних системных шрифтов по умолчанию, а затем пользователь мог бы поставить другой, больший или меньший шрифт и всё катастрофически разъежжалось (особенно, если шрифт был больше). Всё, что было выравнено раньше, переставало таковым быть, текстовые надписи наползали на элементы или вообще пропадали за краем окна. Это было особенно заметно для окон с жёстко заданным размером, вроде диалогов и панелей конфигураций:

Кроме чувствительности к размеру шрифта было сложно сделать GUI с точным расположением и размером каждого компонента. Требовалось добавлять новые элементы или двигать их более сложным, неочевидным образом.

Для решения этой проблемы применяют layout manager — менеджер разметки. Он размещает элементы, руководствуясь такими критериями, как минимальный, желаемый и максимальный размер каждого элемента. Если пользователь изменяет размер окна или шрифта, он элегантно переместит и увеличит/уменьшит те элементы, за которые он отвечает.

Как мы уже сказали в первом абзаце, в BeOS никогда не было встроенного менеджера разметки. Некоторые разработчики пытались это исправить, например Marco Nelissen со своей орденоносной liblayout, Brian Tietz с его Santa's Gift Bag library и Angry Red Planet с ARPCommon library. Все эти библиотеки достигали одного и того же, но разными способами. "Портирование" приложения из одной системы в другую было делом непростым.

К нашей радости, Ingo Weinhold пришёл к нам на помощь, выложив свою систему размещения элементов в дерево Haiku в августе 2006. С тех пор она была немного изменена, но до сих пор не очень используется. Я говорю об этом, потому что не многие о ней вообще знают. Инго человек занятой, поэтому я и пишу эту статью, чтобы познакомить вас с этой системой.

Первая статья опишет систему на верхнем уровне, а в следующих статьях я затрону их более детально.

Прежде всего, Инго нисколько не скрывает, что его система была сделана на основе QLayout и библиотеки Qt. Может быть, будет полезно прочитать и о них тоже, хотя я расскажу всё, что вам нужно знать о классах в Haiku.

Типы менеджеров разметки

Начну с описания типов менеджеров, которые сейчас присутствуют в Haiku и о том, как они работают:

  • BCardLayout: содержит views-виды и работает как колода карт — один элемент сверху, остальные под ним. Виден только верхний элемент, остальные не видны. Полезен для реализации интерфейса на закладках (табах), чтобы показать несколько элементов в одном окне или для окон с несколькими секциями (как в BeIDE или Firefox).
  • BGridLayout: всё views располагаются на сетке, каждый вид занимает одинаковое пространство (хотя может занимать несколько ячеек/колонок). Полезен, например, для приложения, показывающего превьюхи картинок.
  • BGroupLayout: содержит элементы, выравненные горизонтально или вертикально, в зависимости от желаемой ориентации. Комбинация (несколько горизонтальных BGroupLayout-ов внутри вертикального BGroupLayout-а) - самая гибкий и удобный способ расположения элементов. Я думаю очень многие приложения будут использовать этот класс.
  • BSplitLayout: работает почти как BGroupLayout, с той разницей, размер вида резиновый. Кроме того такой вид может быть уменьшен до нулевого размера и не показываться вообще. Этот класс не может использоваться напрямую, а только через класс BSplitView.
  • BALMLayout: новенький в нашем посёлке, был добавлен в феврале 2008 года из кода, созданного Christof Lutteroth'a и James Kim'a. ALM - сокращение от Auckland Layout Model, нового способа, придуманного Кристофом в Университете Окленда. Подробнее о нём можно прочитать на его странице на SourceForge. Думаю, я коснусь подробностей о том, как он работает, позже, может даже в отдельной статье. Впрочем, по ссылке вы можете ознакомиться с его концепцией самостоятельно.

Как построить GUI с использованием разметок

Чтобы построить GUI для Haiku с использованием системы разметок, вам надо понять, какой именно менеджер надо использовать. Для существующего GUI это скорее всего будет очевидно из того, что вы видите на экране, для нового интерфейса, возможно, было бы полезным его нарисовать, хотя это всегда полезно сделать.

После того, как вы определитесь с методами, убедитесь, что BView, которые вам понадобятся, работают с данными системами. Они, в принципе, должны работать (если не работают, сообщите об ошибке). Для самописных BView потребуется немного дополнительной работы, о которой я расскажу в последующей статье. В этой статья мы будем работать только с видами, которые работают с системой разметки.

Для примера можно было бы придумать простой произвольный GUI, но, думаю, интереснее было бы взять существующий интерфейс из Haiku, который чувствителен к размеру шрифта. На первом рисунке у нас была панель настройки e-mail клиента, но он довольно сложный, давайте возьмём что-нибудь попроще. Например, midi плеер.


Вид MIDI плеера со шрифтом по умолчаниию (размер 12) и размером 18. Даже первая версия могла бы выиграть при использовании системы разметок.

Итак, что нам понадобится? Для начала я вижу четыре секции по вертикали:

  1. Панель "Drop MIDI file here"
  2. Лейблы и селектов посередине
  3. Разделитель
  4. Кнопку Play

Это значит, что сверху у нас будет вертикальный BGroupLayout. Группа лейблов и селектов должна расположиться на сетке, для чего будет использован BGridLayout. Но прежде чем я покажу новый код для работы с разметчиком, давайте посмотрим изначальный код. Он лежит в MidiPlayerWindow.cpp в методе CreateViews(). Он использует несколько макросов. Код с номерами строк из r26407:


32 #define _W(a) (a->Frame().Width())
33 #define _H(a) (a->Frame().Height())

...

240 void MidiPlayerWindow::CreateViews()
241 {
242 scopeView = new ScopeView;
243
244 showScope = new BCheckBox(
245 BRect(0, 0, 1, 1), "showScope", "Scope",
246 new BMessage(MSG_SHOW_SCOPE), B_FOLLOW_LEFT);
247
248 showScope->SetValue(B_CONTROL_ON);
249 showScope->ResizeToPreferred();
250
251 CreateInputMenu();
252 CreateReverbMenu();
253
254 volumeSlider = new BSlider(
255 BRect(0, 0, 1, 1), "volumeSlider", NULL, NULL,
256 0, 100, B_TRIANGLE_THUMB);
257
258 rgb_color col = { 152, 152, 255 };
259 volumeSlider->UseFillColor(true, &col);
260 volumeSlider->SetModificationMessage(new BMessage(MSG_VOLUME));
261 volumeSlider->ResizeToPreferred();
262 volumeSlider->ResizeTo(_W(scopeView) - 42, _H(volumeSlider));
263
264 playButton = new BButton(
265 BRect(0, 1, 80, 1), "playButton", "Play", new BMessage(MSG_PLAY_STOP),
266 B_FOLLOW_RIGHT);
267
268 //playButton->MakeDefault(true);
269 playButton->ResizeToPreferred();
270 playButton->SetEnabled(false);
271
272 BBox* background = new BBox(
273 BRect(0, 0, 1, 1), B_EMPTY_STRING, B_FOLLOW_ALL_SIDES,
274 B_WILL_DRAW | B_FRAME_EVENTS | B_NAVIGABLE_JUMP,
275 B_PLAIN_BORDER);
276
277 BBox* divider = new BBox(
278 BRect(0, 0, 1, 1), B_EMPTY_STRING, B_FOLLOW_ALL_SIDES,
279 B_WILL_DRAW | B_FRAME_EVENTS, B_FANCY_BORDER);
280
281 divider->ResizeTo(_W(scopeView), 1);
282
283 BStringView* volumeLabel = new BStringView(
284 BRect(0, 0, 1, 1), NULL, "Volume:");
285
286 volumeLabel->ResizeToPreferred();
287
288 float width = 8 + _W(scopeView) + 8;
289
290 float height =
291 8 + _H(scopeView)
292 + 8 + _H(showScope)
293 + 4 + _H(inputMenu)
294 + _H(reverbMenu)
295 + 2 + _H(volumeSlider)
296 + 10 + _H(divider)
297 + 6 + _H(playButton)
298 + 16;
299
300 ResizeTo(width, height);
301
302 AddChild(background);
303 background->ResizeTo(width, height);
304 background->AddChild(scopeView);
305 background->AddChild(showScope);
306 background->AddChild(reverbMenu);
307 background->AddChild(inputMenu);
308 background->AddChild(volumeLabel);
309 background->AddChild(volumeSlider);
310 background->AddChild(divider);
311 background->AddChild(playButton);
312
313 float y = 8;
314 scopeView->MoveTo(8, y);
315
316 y += _H(scopeView) + 8;
317 showScope->MoveTo(8 + 55, y);
318
319 y += _H(showScope) + 4;
320 inputMenu->MoveTo(8, y);
321
322 y += _H(inputMenu);
323 reverbMenu->MoveTo(8, y);
324
325 y += _H(reverbMenu) + 2;
326 volumeLabel->MoveTo(8, y);
327 volumeSlider->MoveTo(8 + 49, y);
328
329 y += _H(volumeSlider) + 10;
330 divider->MoveTo(8, y);
331
332 y += _H(divider) + 6;
333 playButton->MoveTo((width - _W(playButton)) / 2, y);
334 }

Я не стану вдаваться в подробности, просто хочу обратить внимание на вручную заданные координаты и о том, сколько времени отняло расположение элементов. Другим побочным эффктом использования систем автоматичского расположения является более читый код, думаю, следующий ниже пример наглядно покажет это. Вот метод CreateViews(), переписанный для системы разметчиков:


246 void MidiPlayerWindow::CreateViews()
247 {
248 // Set up needed views
249 scopeView = new ScopeView;
250
251 showScope = new BCheckBox(
252 BRect(0, 0, 1, 1), "showScope", "Scope",
253 new BMessage(MSG_SHOW_SCOPE), B_FOLLOW_LEFT);
254 showScope->SetValue(B_CONTROL_ON);
255
256 CreateInputMenu();
257 CreateReverbMenu();
258
259 volumeSlider = new BSlider(
260 BRect(0, 0, 1, 1), "volumeSlider", NULL, NULL,
261 0, 100, B_TRIANGLE_THUMB);
262 rgb_color col = { 152, 152, 255 };
263 volumeSlider->UseFillColor(true, &col);
264 volumeSlider->SetModificationMessage(new BMessage(MSG_VOLUME));
265
266 playButton = new BButton(
267 BRect(0, 1, 80, 1), "playButton", "Play", new BMessage(MSG_PLAY_STOP),
268 B_FOLLOW_RIGHT);
269 playButton->SetEnabled(false);
270
271 BBox* divider = new BBox(
272 BRect(0, 0, 1, 1), B_EMPTY_STRING, B_FOLLOW_ALL_SIDES,
273 B_WILL_DRAW | B_FRAME_EVENTS, B_FANCY_BORDER);
274 divider->SetExplicitMaxSize(
275 BSize(B_SIZE_UNLIMITED, 1));
276
277 BStringView* volumeLabel = new BStringView(
278 BRect(0, 0, 1, 1), NULL, "Volume:");
279 volumeLabel->SetAlignment(B_ALIGN_LEFT);
280 volumeLabel->SetExplicitMaxSize(
281 BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
282
283 // Build the layout
284 SetLayout(new BGroupLayout(B_HORIZONTAL));
285
286 AddChild(BGroupLayoutBuilder(B_VERTICAL, 10)
287 .Add(scopeView)
288 .Add(BGridLayoutBuilder(10, 10)
289 .Add(BSpaceLayoutItem::CreateGlue(), 0, 0)
290 .Add(showScope, 1, 0)
291
292 .Add(reverbMenu->CreateLabelLayoutItem(), 0, 1)
293 .Add(reverbMenu->CreateMenuBarLayoutItem(), 1, 1)
294
295 .Add(inputMenu->CreateLabelLayoutItem(), 0, 2)
296 .Add(inputMenu->CreateMenuBarLayoutItem(), 1, 2)
297
298 .Add(volumeLabel, 0, 3)
299 .Add(volumeSlider, 1, 3)
300 )
301 .AddGlue()
302 .Add(divider)
303 .AddGlue()
304 .Add(playButton)
305 .AddGlue()
306 .SetInsets(5, 5, 5, 5)
307 );
308 }

Единственным значительным изменением в первой части метода, где создаются виды, стало удаление их ручного размещения, вроде playButton->ResizeToPreferred(); в строке 269. Мы добавили насколько подсказок, чтобы система поняла, как следует расположить элементы наилучшим образом. Например, неограниченная ширина и высота не более 1 назначена разделителю BBox, поэтому он всегда будет выглядеть как разделитель. В дополнение к нему, volumeLabel выравнен по левому краю и занимает всё доступное место. Он выравнен вместе с лейблами BMenuFields над ним.

Перейдём к «мясу», которое начинается у нас с 284 строки. Начнём с базовой разметки. Я решил начать с «пустой» горизонтальной группы, чтобы потом использовать вертикальное построение, которое мне на самом деле нужно. Возможно, это особенность системы, что менеджер должен быть установлен внутри окна, прежде чем вы сможете добавлять в него элементы. Я бы, возможно, предпочёл создать вертикальную группу вне сборщика и сделать её корневой разметкой, но, не думаю что код выглядел бы при этом красиво.

Если говорить о сборщиках, они созданы как раз для таких случаев, как у нас, когда каждый метод возвращает экземпляр сборщика, позволяющего создавать практически неограниченную сеть методов. С использованием правильных отступов, они выглядят красиво и показывают структуру GUI. За что я их и люблю. Итак у нас есть GridLayoutBuilder, GroupLayoutBuilder и SplitLayoutBuilder.


Новый интерфейс MIDI при шрифтах 12-го и 18-го размера.

В строке 286 мы добавили вызов AddChild() с вызовами разметчиков внутри него. Первый аргумент для BGroupLayoutBuilder — это ориентация группы, а второй - расстояние между элементами. Оно необходимо, чтобы прорядить элементы, иначе они слипнутся. Поскольку у нас есть главная группа, мы можем добавлять в неё элементы методом Add(), и для начала добавим чекбокс scope. Потом мы хотим в середине поместить сетку, поэтому вызовем её сборщик. Для BGridLayoutBuilder аргументами будут расстояния между элементами, на этот раз по горизонтали и по вертикали.

В строке 289 мы размещаем элементы на сетке, используя glue (клей). Что такое клей? Это то, что заполняет пространство, чтобы наши элементы были выравнены правильно. В нашем случае чекбокс showScope должен быть выравнен с меню, которые под ним. Ещё клей используется в главной вертикальной группе, чтобы склеить сетку, разделитель, кнопку play и низ окна. Наше окно полуается красивым и похожим на исходное.

Другими аргументами в методе Add() для BGridLayoutBuilder будут номер строки и колонки, в которых мы хотим увидеть данный элемент. Мы хотим, чтобы клей был в 0 строке 0 колонки, чекбокс showScope в 0 строке и 1 колонке, лейбл reverb в 0 колонке и 1 строке и т.д.. Что касается пункта reverb в меню, то мы видим, что специальные методы BMenuField создают два разных элемента для одного вида. Это интересный аспект, говорящий нам о том, что виды состоящие из разных компонентов,могут на самом деле иметь разные секции, которые разметчиком управляются индивидуально. Другим примером такого подхода может служить ActivityMonitor, который использует эту технику, чтобы показать активность и легенду.

Напоследок вызывается метод SetInsets(), который нужен, чтобы поместить поля вокруг нашего интерфесйса, слева, сверху, справа и снизу. Пять пикселей вполне сойдёт для полей. Я мог бы использовать и 8, как в оригинале, но и 5 вполне хватит.

Нужно сделать ещё несколько изменений в коде, чтобы получить GUI, как на картинке. Во-первыха добавим заголовки. Во-вторых в конструктор окна MidiPlayer надо добавить B_AUTO_UPDATE_SIZE_LIMITS, чтобы он автоматически изменял размер с помощью системы разметки:

41 MidiPlayerWindow::MidiPlayerWindow()
42 : BWindow(BRect(0, 0, 1, 1), "MidiPlayer", B_TITLED_WINDOW,
43 B_ASYNCHRONOUS_CONTROLS | B_NOT_RESIZABLE | B_NOT_ZOOMABLE | B_AUTO_UPDATE_SIZE_LIMITS)

В третьих, методы, которые использовались для построяения меню, следует изменить на более дружественный конструктор. Также следует удалить код ручной разметки. Для примера, код CreateReverbMenu() меняется с такого:

230 reverbMenu = new BMenuField(
231 BRect(0, 0, 128, 17), "reverbMenu", "Reverb:", reverbPopUp,
232 B_FOLLOW_LEFT | B_FOLLOW_TOP);
233
234 reverbMenu->SetDivider(55);
235 reverbMenu->ResizeToPreferred();

на такой:

235 reverbMenu = new BMenuField("Reverb:", reverbPopUp, NULL);

Тоже самое сделаем с методом CreateInputMenu().

И, наконец, расширим конструктор ScopeView, чтобы в него влезал 18й шрифт. Это «дешёвый» способ, но он прекрасно работает. «Правильным» способом было бы добавление методов, которые сообщили бы разметчику минимальный, максимальный и желаемый размер этого элемента. В этой статье я не хочу на этом останавливаться, но, может, расскажу об этом позже.

Новый код MIDI плеера можно найти в репозитории Haiku в src/apps/midiplayer. Старый код можно найти там же, до ревизии 26408, когда я послал эти изменения.

Заключение

Если хотите найти идеи или советы о том, как использовать систему разметки, пока не будет написана исчерпывающая документация, посмотрите на тестовую программу Инго, которая называется LayoutTest1. Исходник её лежит в tests/kits/interface/layout/LayoutTest1.cpp и она может быть использована в Haiku добавлением вот такой строки в файл UserBuildConfig:

AddFilesToHaikuImage home config bin : LayoutTest1 ;

она может быть выполнена из треккера в /boot/home/config/bin или из терминала. Там также есть примеры построения GUI с использованием сборщиков и без, в зависимости от надобности.

Код всех классов разметчика лежит в Haiku Interface Kit, так что если вас он заинтересовал, смотрите заголовки Interface Kit-а в headers/os/interface и реализацию в src/kits/interface and src/kits/interface/layouter.

Предупреждение: поскольку эти классы новые и не тестировались с API, они считаются private API и должны использоваться только внутри Haiku, для встроенных приложений и панелей настройки. Это делается потому, что API может меняться, и может нарушить работу программ других людей (внутренние программы мы можем поправить сами). Поэтому, имейте ввиду: используя сейчас классы разметчика, у вас есть риск того, что в будущем код перестанет работать.

Ryan Leavengood

Архивы

Сентябрь 2007| Октябрь 2007| Январь 2008| Февраль 2008| Март 2008| Апрель 2008| Май 2008| Июнь 2008| Июль 2008| Август 2008| Сентябрь 2008| Октябрь 2008| Ноябрь 2008| Декабрь 2008| Январь 2009