LXF113-114:FLTK

Материал из Linuxformat.

Перейти к: навигация, поиск
Программируем с FLTK Быстрый, легкий, поддерживающий OpenGL: выберите любые три!

Содержание

Старый конь борозды не испортит

FLTK
ЧАСТЬ 1 Считаете GTK+ и Qt слишком тяжеловесными и желаете чего-то попроще? Взгляните на FLTK: Андрей Боровский объяснит, чем же этот инструментарий привлек разработчиков браузера Dillo.
Что в имени тебе моем?

Название пакета происходит от имени библиотеки Forms Library (FL), которая использовалась на легендарных рабочих станциях SGI. Официально аббревиатура FLTK расшифровывается как Fast Light Toolkit, что звучит довольно странно и немного коряво. Кстати, «FLTK» произносится как «фуллтик» [fulltick], а не «эфэлтикей», как можно было бы подумать.

Лет 15 тому назад я разговаривал с руководителем ИТ-отдела одного успешного (в то время) предприятия. Разговор запомнился мне тем, что мой собеседник убеждал меня, будто профессия программиста, которой я собирался себя посвятить, в скором времени не будет востребована. Мол, все необходимое ПО давно уже написано, и остается только подобрать то, что нужно каждому конкретному работнику. Мне хотелось встретить этого человека сейчас и спросить, как последние 15 лет согласуются с его теорией, но увы – фирмы, в которой он работал, больше нет (и почему меня это не удивляет?), и даже всевидящие социальные сети не позволяют мне отыскать его следы.

Урок этой истории в том, что будущее развитие технологии предвидеть невозможно. Нам кажется, что некоторое направление исчерпало себя, а в это время именно в нем назревает очередная революция. Вот, например, библиотеки виджетов. При всем богатстве выбора я не могу сказать, что нашел ту, которая бы удовлетворяла меня во всем. Набор виджетов моей мечты должен быть компактным (с точки зрения объемов двоичного кода), многопоточным и простым в использовании (последнее особенно относится к механизму обработки событий). Это набор должен допускать статическое связывание библиотек с кодом приложения без резкого увеличения размеров программы, ну и, конечно, я хочу, чтобы виджеты выглядели элегантно. Инструментарий FLTK не является библиотекой моей мечты; тем не менее, в нем реализовано немало такого, чему разработчики других библиотек и программисты приложений могли бы поучиться.

Будем знакомы

FLTK – открытый кросс-платформенный набор виджетов, написанный на C++ и предназначенный для программирования на этом языке (хотя существуют также версии для Python и Ruby). Перечень поддерживаемых платформ стандартен – Windows, Mac OS и различные Unix’ы. Список приложений, написанных с помощью FLTK, гораздо короче, чем у той же wxWidgets (не говоря уже о Qt и GTK+), но, в отличие от последней, у него есть свой рабочий стол – EDE или Equinox Desktop Environment, а также менеджер окон flwm, оформление которого позволяет мысленно перенестись в начало девяностых.

Как и у многих других открытых проектов, у FLTK одновременно активны несколько версий. Для обеспечения совместимости со старыми программами поддерживается ветка FLTK 1.x, а для новых программ развивается FLTK 2. Если вы – ветеран разработки с FLTK, то вряд ли станете читать эту статью, а у новых разработчиков нет причин использовать старые версии библиотеки, поэтому мы сосредоточимся на FLTK 2.

Одна из отличительных черт FLTK – статическое связывание. Разумеется, другие наборы виджетов тоже можно использовать в виде набора статических библиотек (а FLTK, при желании, можно скомпилировать в виде разделяемого модуля), но так уж исторически сложилось, что другие популярные библиотеки по умолчанию пользуются динамической компоновкой, а FLTK – статической. У каждого подхода есть свои достоинства и недостатки. Связывание с разделяемыми объектами позволяет сэкономить место на диске в том случае, если библиотека используется множеством разных программ. Кроме того, разделяемые модули упрощают процесс обновления. Если же библиотека не относится к числу часто используемых, у статического связывания есть одно преимущество – интеграция кода библиотеки в исполняемые файлы приложения упрощает установку последних. Ирония ситуации заключается в том, что если бы библиотека FLTK была более популярна, статическое связывание следовало бы рассматривать как недостаток, тогда как при нынешнем положении дел это скорее достоинство. Важно отметить, что предпочтительный способ связывания накладывает определенный отпечаток на структуру библиотеки. Чтобы сделать статическое связывание более эффективным, разработчики FLTK постарались свести к минимуму количество внутренних зависимостей в коде библиотеки.

С самого начала FLTK обладал еще одной отличительной характеристикой: встроенной в пакет широкой поддержкой OpenGL. После выхода Qt 4, в которой OpenGL используется для вывода не только трехмерной, но и традиционной для виджетов двумерной графики, FLTK больше нельзя считать лидером в этой области; но в свое время многие программисты выбирали FLTK именно ради OpenGL.

Еще одно важное отличие FLTK от Qt, GTK+ и wxWidgets заключается в том, что FLTK до сих пор остается только набором виджетов, в то время как остальные библиотеки стремятся, похоже, охватить все сферы прикладного программирования. Если вам нужно, чтобы используемая вами библиотека предоставляла вам готовые решения для всех сколько-нибудь распространенных задач, начиная с многопоточности и закачивая взаимодействием с базами данных, FLTK – не ваш выбор. Но учтите, что за универсализм библиотек приходится платить избыточным кодом, необходимым для того, чтобы привести разные задачи к единой модели программирования.

Рис. 1 Рис. 1. Программа flPhoto: ничто не должно отвлекать вас от просмотра снимков.

Интерфейсы, создаваемые с помощью FLTK, отличаются аскетичностью. Типичным приложением FLTK является простая программаменеджер фотографий flPhoto (рис. 1). В общем случае, FLTK хорошо подходит для написания программ, не требующих «навороченного» пользовательского интерфейса. Одной из областей применения FLTK может служить создание графических дополнений к «преимущественно неграфическим» программам (пример – система вывода графиков Octplot для консольного математического пакета Octave). Как ни странно, подобный подход может иметь смысл и в случае приложений, ориентированных исключительно на графику – таких, как программа flPhoto. Многие дизайнеры графических интерфейсов придерживаются точки зрения, что в программе, основное предназначение которой – показывать картинки, собственный интерфейс должен быть как можно более скромным, дабы форма не отвлекала пользователя от содержания. Если вы один из адептов этой теории, смело используйте FLTK.

FLTK поддерживает многобайтовые текстовые кодировки (иначе ее вообще нельзя было бы использовать на современных платформах), но специального типа данных для работы с текстом в этих кодировках нет, так что функции, принимающие строки, используют тип char *. Впрочем, никаких проблем с русским текстом в кодировке UTF-8 в FLTK 2 я не обнаружил, тогда как некоторые программы, скомпилированные с FLTK 1.x, вместо русских букв отображали иероглифы.

Наше первое приложение

Отличия FLTK 2.0 и 1.x

Мы не станем останавливаться на данном вопросе подробно (в конце концов, если вы захотите писать программы с помощью FLTK 1.x, документация всегда к вашим услугам), а рассмотрим только фундаментальные различия, которые могут повлиять на работу примеров этого урока. В старой версии FLTK имена классов начинались с префикса Fl_ – например, Fl_Window. В новой версии все классы объявлены в собственном пространстве имен, fltk, и их имена не имеют префиксов. В старой версии метод run() был объявлен как статический метод класса Fl, его вызов выглядел так:

return Fl::run();

В новой версии, как мы видели, run() – самостоятельная функция. Заголовочные файлы в старой версии обычно располагались в директории FL, а не fltk. Помимо прочего, в любой проект нужно было включать файл FL/Fl.H, содержащий объявления класса Fl.

Практическое знакомство с FLTK мы начнем, как водится, с программы Hello World:

#include <fltk/Window.h>
 #include <fltk/Widget.h>
 #include <fltk/run.h>
 using namespace fltk;
 int main(int argc, char **argv) {
   Window *window = new Window(200, 100);
     window->begin();
     Widget *box = new Widget(40, 20, 120, 60, "Привет, Мир!");
     box->labelsize(16);
     window->end();
     window->show(argc, argv);
     return run();
 }

На первый взгляд, все здесь делается по стандартной схеме: мы создаем объект класса fltk::Window, представляющий глав ное окно, затем – простейший виджет (объект класса fltk::Widget), настраиваем его внешний вид, вызываем метод show() объекта главного окна и запускаем цикл обработки сообщений с помощью функции fltk::run() (обратите внимание, что в FLTK нет класса Application, отвечающего за работу приложения в целом, так что run() – это самостоятельная функция в стиле C). Однако при внимательном рассмотрении в этом примере можно найти немного магии. Приглядитесь к конструктору объекта box. Мы вправе ожидать, что одним из аргументов будет указатель на объект главного окна, но его здесь нет. Волшебным образом FLTK «знает», что виджеты, созданные между вызовами методов begin() и end() объекта главного окна, принадлежат именно ему (секрет этого фокуса заключается в использовании статических переменных в недрах FLTK). Обратите внимание также на то, что для формирования прямоугольника с текстом мы воспользовались классом fltk::Widget. Как вы правильно догадались, fltk::Widget – базовый класс для различных виджетов, но, в отличие от других библиотек, в FLTK он не является абстрактным, а может сам формировать изображения. Программу можно скомпилировать с помощью команды

  g++ helloworld.cxx -o helloworld -lfltk2

Имя библиотеки, переданной в ключе -l, указывает на то, что мы пользуемся версией FLTK 2.0; для FLTK 1.x нужно было бы указать -lfltk.

Визуальное программирование

Как и у всякого уважающего себя набора виджетов, у FLTK есть собственный визуальный редактор под названием Fluid (в настоящее время программа доступна в двух версиях – fluid для версий 1.x и fluid2 для второй версии, соответственно). Современные визуальные редакторы принадлежат к одной из двух категорий: генераторов описаний интерфейса и генераторов исходных текстов. На выходе первых получается особый файл (обычно на диалекте XML), который затем читается специальными классами приложения, выполняющими построение интерфейса на основе хранящихся в файле инструкций. К редакторам этого типа относится, например, Glade (начиная с третьей версии). Редакторы второй категории генерируют исходные тексты на целевом языке программирования. Их ярким представителем является визуальный редактор Windows Forms платформы .NET. Первый подход лучше тем, что внешний вид программ может быть настроен во время их выполнения. Кроме того, редактор, создающий описания интерфейса на своем собственном языке, может использоваться совместно с разными языками программирования. Редактор Fluid относится ко второй категории, то есть конечным результатом его работы являются файлы исходных текстов на C++.

Сам по себе Fluid совершенно не похож на визуальные редакторы Qt, GTK+ и wxWidgets. Честно говоря, мне даже трудно сказать, на что он похож. Наверняка разработчик вдохновлялся каким-то древним произведением (разработка FLTK началась в 1998 году). Проектирование интерфейса с помощью Fluid включает в себя, если можно так выразиться, визуальное проектирование кода (довольно утомительный, кстати сказать, процесс).

Рис. 2 Рис. 2. Палитра инструментов FLUID: угадай, где что.

Проектирование интерфейса программы FLTK начинается с объявления ее главного класса (во Fluid мы будем создавать главный класс программы визуальными методами). В палитре инструментов (Widget Bin) программы fluid2 щелкаем кнопку Class (рис. 2).

В рабочей области редактора появляется иконка нового класса, а перед нами открывается окно, в котором мы должны ввести его имя (поле name). Назовем наш класс DemoUi. Далее мы должны сделать то, что мы обычно делаем, объявляя новые классы: создать конструктор. Выделяем класс DemoUi в окне редактора и щелкаем кнопку function (которая с овалом). В окно редактора будет добавлена заготовка нового метода класса DemoUi. В окне описания функции (function/method) в поле name/args вводим значение DemoUi(). Как нетрудно догадаться, мы определили заголовок метода. Поскольку имя метода совпадает с именем класса, редактор «поймет», что имеет дело с конструктором.

Рис. 3 Рис. 3. Настройка главного окна: фанатов KDE порадует обилие опций.

Дальше начинается самое интересное – проектирование собственно интерфейса. Все объекты, представляющие элементы главного окна, будут полями главного класса. Эти поля инициализируются в конструкторе DemoUi(), поэтому главное окно приложения должно быть дочерним элементом конструктора (я не шучу!). Выделите значок конструктора в окне редактора, а в окне Widget Bin щелкните кнопку Window. В окно редактора будет добавлен элемент Window. Диалог свойств этого элемента выглядит гораздо сложнее, чем окно настроек класса (рис. 3).

На вкладке GUI вы можете настроить внешний вид окна, но наиболее интересный для нас элемент расположен на вкладке «C++». Переключитесь на нее и введите mainWindow в поле Name. Таким образом мы задали имя объекта главного окна. Все виджеты, расположенные в окне, должны быть дочерними элементами Window (наконец-то нечто знакомое!). Добавьте в окно элемент Button (кнопка) и Output (текстовая метка). Объекту класса Button присвойте имя button1 (делается это так же, как и в случае элемента Window), а объекту класса Output – имя textOut1. Теперь иерархия визуальных элементов приложения должна быть похожа на представленную на рис. 4.

Рис. 4Рис. 4. Иерархия элементов главного класса программы.

Добавим действия

Теперь, когда у нас есть кнопка, будет вполне логично создать функцию-обработчик щелчка по ней. Это делается просто (вот где FLTK оставляет далеко позади и Qt, и GTK+!). В окне редактора дважды щелкаем по пиктограмме элемента button1 и в открывшемся окне настроек переходим на вкладку C++. В поле ввода Callback добавляем строку:

textOut1->value("Нажато");

Попросту говоря, нажатие на кнопку приведет к изменению текста метки. Проблема чтения и присвоения значений полям объектов решена в FLTK весьма элегантно. Чтобы присвоить новую строку текстовой метке, мы вызываем метод value() с параметром типа char *. Если бы мы хотели прочитать текст метки, мы бы вызвали перегруженный вариант метода value() без параметров (он возвращает значение char *).

Проектирование интерфейса нашей программы почти закончено, осталось выполнить несколько служебных операций. Редактор Fluid позволяет добавлять в заготовку класса методы, не имеющие прямого отношения к визуальным объектам (и стоит отметить, что если уж мы начали проектировать класс с помощью Fluid, то следует проектировать его во Fluid целиком). Мы добавим в класс DemoUi метод showWindow(). Выделите в редакторе пиктограмму класса DemoUi и щелкните знакомую кнопку Function в окне Widget Bin. Заголовок нового метода должен иметь вид

showWindow(int argc, char ** argv)

а в качестве возвращаемого значения укажем void. Проследите за тем, чтобы метод showWindow(), как и конструктор DemoUi(), был публичным (для этого нужно установить флажок public в окне настроек). Мы добавили метод в объявление класса, но не определили его код. Если вы думаете, что сейчас мы наконец-то перейдем к старому доброму текстовому редактору, то рано радуетесь. Даже блок кода в визуальном редакторе Fluid следует определять визуально. Выделите метод showWindow() в окне редактора и щелкните кнопку Code (зеленый прямоугольник) в окне Widget Bin. Теперь у метода появился блок кода showWindow(). Щелкните дважды мышью по пиктограмме блока в окне редактора и в открывшемся окне введите строку:

mainWindow->show(argc, argv);

Нетрудно догадаться, что предназначением метода showWindow() является вызов метода show() объекта mainWindow (сам объект mainWindow является закрытым элементом класса DemoUi, так что получить прямой доступ к его методам за пределами DemoUi мы не можем). На этом визуальное проектирование закончено, и мы можем сохранить наш проект (выберите для него имя test.fl). Между прочим, файлы проектов Fluid представляют собой обычный текст, и их можно редактировать в любом редакторе, при условии, что вы разберетесь в синтаксисе (редактор Fluid создавался еще до полной победы XML).

Соберемся воедино

Наша следующая задача – сгенерировать код C++ для класса DemoUi. Это делается с помощью команды File > Write code. В результате на диске появятся файлы test.cxx и test.h. Теперь мы можем посмотреть, что именно создал редактор Fluid, исходя из нашего описания. Вот как выглядит объявление класса DemoUi, созданное Fluid:

class DemoUi {
 public:
    DemoUi();
    fltk::Window *mainWindow;
 private:
      fltk::Button *button1;
      inline void cb_button1_i(fltk::Button*, void*);
      static void cb_button1(fltk::Button*, void*);
      fltk::Output *textOut1;
 public:
    void showWindow(int argc, char ** argv);
 };

Фактически, это объявление воспроизводит последовательность наших действий в редакторе Fluid, причем даже порядок действий не изменился (видите два раздела public?).

Обратите внимание на методы cb_button1_i() и cb_button1(). Мы не определяли их явно: они были сгенерированы автоматически для обработки нажатия кнопки. Заданный нами код обработчика содержится в методе cb_button1_i(), а метод cb_button1() представляет собой «обертку», предназначенную для вызова всех обработчиков, связанных с кнопкой. Конструктор класса DemoUi прилежно создает все заданные нами объекты:

Рис. 5 Рис. 5. Вот она, наша вторая программа на FLTK!

DemoUi::DemoUi() {
   fltk::Window* w;
     {fltk::Window* o = mainWindow = new fltk::Window(325, 135);
      w = o;
      o->shortcut(0xff1b);
      o->user_data((void*)(this));
      o->begin();
        {fltk::Button* o = button1 = new fltk::Button(8, 14, 223, 31, "...");
         o->callback((fltk::Callback*)cb_button1);
      }
      textOut1 = new fltk::Output(55, 52, 178, 28, "output:");
      o->end();
      o->resizable(o);
   }
 }

Посмотрите на вызов метода callback() объекта button1. Это самый простой и изящный способ назначения обработчика из всего, что я видел. Нам осталось написать функцию main() для нашей программы:

#include <fltk/run.h>
 #include "test.h"
 int main (int argc, char **argv) {
   DemoUi * ui = new DemoUi();
   ui->showWindow(argc, argv);
   return fltk::run();
 }

Главное окно программы создается в конструкторе объекта ui. Все, что нам нужно сделать после этого – вызвать метод showWindow(), делающий его видимым, и запустить цикл обработки сообщений. Для сборки визуальной программы компилятор вызывается с теми же опциями, что и в предыдущем примере. Результат наших трудов показан на рис. 5.

Вы спросите: а где же здесь интеграция с OpenGL, так широко разрекламированная мною в начале урока? Терпение, все будет: нужно только дождаться февральского номера. LXF

Личные инструменты
  • Купить электронную версию
  • Подписаться на бумажную версию