Программирование FLTK, часть 1

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

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

Что в имени тебе моем?

Название пакета происходит от имени библиотеки Forms Library (FL), которая использовалась на легендарных рабочих станциях SGI. Официально аббревиатура FLTK расшифровывается как Fast Light Toolikit, что звучит довольно странно и немного коряво. 

Знакомство

FLTK — открытый кросс-платформенный набор виджетов, написанный на C++ и предназначенный для программирования на этом языке (хотя существуют также версии для Python и Ruby). Перечень поддерживаемых платформ стандартен — Windows, MacOS и различные Юниксы. Список приложений, написанных с помощью FLTK гораздо короче, чем список приложений wxWidgets (не говоря уже о Qt и GTK+), но, в отличие от wxWidgets, у FLTK есть свой десктоп — менеджер окон flwm, оформление которого позволяет мысленно перенестись в начало девяностых. Как и у многих других открытых проектов, у FLTK одновременно активны несколько версий. Для обеспечения совместимости со старыми программами поддерживаются версии FLTK 1.x, а для новых программ развивается FLTK 2. Если вы старый разработчик программ на FLTK, то вряд ли станете читать эту статью, а у новых разработчиков нет причин использовать прежние версии библиотеки, поэтому мы сосредоточимся на FLTK второй версии.

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

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

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

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

 

Рисунок 1.  Программа flphoto

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

Первая программа

Практическое знакомство с 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 2.0 и 1.x

Мы не будем разбирать эти отличия подробно (в конце концов, если вы захотите писать программы с помощью FLTK первой версии, документация всегда к вашим услугам), рассмотрим только фндаментальные различия, которые могут повлиять на работу примеров из этой статьи. В старой версии FLTK имена классов начинались с префикса Fl_, например Fl_Window. В новой версии все классы объявлены в собственном пространстве имен fltk и их имена не имеют префиксов. В старой версии метод run() был объявлен как статический метод класса Fl, его вызов выглядел так:
return Fl::run();
В новой версии, как мы видели, run() - самостоятельная функция. Заголовочный файлы в старой версии обычно располагались в директории FL, а не fltk. Помимо прочего, в любой проект нудно было включать файл FL/Fl.H, содержащий объявления класса Fl. 

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

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

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

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

 

Рисунок 2. FLUID в работе

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

 

Рисунок 3. Настройка главного окна

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

созданное 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 прилежно создает все заданные нами объекты:

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.

 

Рисунок 5. Наша программа

В следующей статье этого цикла мы рассмотрим то, что некогда делало FLTK предпочтительной библиотекой в глазах множества разработчиков — поддержку OpenGL.

Исходные тексты примеров 

Часть 2 >>


Статья впервые опубликована в журнале Linux Format

© 2008  Андрей Боровский anb@symmetrica.net


На главную