Салимóненко Дмитрий Александрович

Операционные системы

Задание 7: Создание графического интерфейса пользователя (GUI)

(C++, Linux)

Введение


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

Для создания GUI необходимо использовать ту или иную графическую библиотеку. Их, на самом деле, в настоящее время очень много, причем, кроссплатформенных. Ниже я приведу лишь небольшой перечень, в порядке убывания популярности (хотя, этот порядок выбран лично мной и, возможно, несколько субъективен): Qt, GTK, wxWidgets, OpenGL, FOX toolkit, SFML, FLTK, mppg, IUP, CEGUI... И, в общем-то, так далее. А есть еще, сейчас уж порядком подзабытая и считающаяся устаревшей, библиотека ncurses (позволяющая реализовать псевдографический интерфейс). Надо сказать, что подавляющему большинству программистов в настоящее время известны, разве что, первые три-четыре из них. Тогда как последняя половина списка может выглядеть довольно неожиданной, ибо, что называется, не у всех на устах. Однако, это не значит что эти библиотеки – плохие. Скорее, наоборот. Хотя, it depends, как говорится.


В свое время, я, в качестве дополнительного задания, просил студентов реализовать GUI для консольных версий их программ. Да, наслушавшись от «спецов» разговоров о солидных библиотеках Qt/GTK. Однако, ну, раздражало как-то, что программка объемом несколько десятков или сотен килобайт получала «в награду» графический интерфейс, занимавший объем больше даже не на порядок, а на 2…3 порядка. Что - несколько противоестественно.

Так вот, я, помнится, просил студентов использовать известные графические библиотеки, типа той же Qt.  Объем исполняемого файла при ее использовании может занимать от 2…3 МБ (это уж самый минимум) даже, повторюсь, для простейших приложений типа “Hello Word”. Что очень сильно напоминает, скажем, использование автомобиля КАМАЗ для перевозки ОДНОГО компьютера.

Какую библиотеку выбрать для создания GUI?

На самом деле, вопрос – далеко не прозаический. И отнюдь не следует открывать мануалы той же Qt или GTK только потому, что они популярны и «все их используют». В первую очередь следует, конечно, определиться, ЧТО мы хотим от этой библиотеки. Если GUI и только GUI – тогда можно смело забыть о первых двух-трех-четырех библиотеках, перечисленных выше в списке.

Почему? Да, потому, что, скажем, пресловутая Qt, на самом деле, это не просто графическая библиотека. Это – целый фреймворк - кроссплатформенный, со своими отдельными функциями, классами и конструкциями. Удобный и универсальный, но… очень большой. Целесообразный, разве что, для больших проектов. Но, на мой взгляд, категорически неприемлемый для разработки небольших программок, утилит.

Другое дело, если необходимо, к примеру, использование того или иного игрового движка. Или стоит цель – создать высокоинтерактивное приложение. Тогда, конечно, простыми библиотеками абсолютно не обойтись.

Мое внимание привлекла библиотека FLTK

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

Честно говоря, я тестировал и другие аналогичные библиотеки, ту же SFML, wxWidgets. Первая из них попросту не понравилась (да, это очень субъективно). А для второй, увы, не нашлось простых примеров. Те, что есть на официальном сайте, все-таки, достаточно сложны и их проблематично освоить, скажем, за полдня. Те «руководства», которые мне довелось видеть по wxWidgets, содержали попросту неработающие примеры (за исключением, конечно, совсем уж примитивных). Впрочем, эта библиотека вовсе не заслуживает такого отзыва. Просто документация у нее - не очень. Да и популярность в России - тоже (поэтому через поиск в интернете, если делать русскоязычные запросы, "находится" практически одно и то же).

Хотя, это – достаточно серьезная библиотека. Может, как-нибудь все-таки разберусь и с нею.

Поэтому, в итоге,  приглянулась библиотека FLTK.

Чем хороша FLTK?

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

Установка библиотеки FLTK

Для начала, неплохо бы посмотреть, что содержится в репозиториях Ubuntu с именем FLTK. Для этого набираем команду:

$ sudo apt-cache search libfltk*


Получаем:

  • libfltk-cairo1.3 - Fast Light Toolkit - Cairo rendering layer support
  • libfltk-forms1.3 - Fast Light Toolkit - Forms compatibility layer support
  • libfltk-gl1.3 - Fast Light Toolkit - OpenGL rendering support
  • libfltk-images1.3 - Fast Light Toolkit - image loading support
  • libfltk1.1 - Fast Light Toolkit - shared libraries
  • libfltk1.1-dbg - Fast Light Toolkit - shared libraries with debugging symbols
  • libfltk1.1-dev - Fast Light Toolkit - development files
  • libfltk1.3 - Fast Light Toolkit - main shared library
  • libfltk1.3-compat-headers - Fast Light Toolkit - compatibility header symlinks
  • libfltk1.3-dbg - Fast Light Toolkit - shared libraries with debugging symbols
  • libfltk1.3-dev - Fast Light Toolkit - development files

Т.е., с одной стороны, можно установить вообще все. А можно и ограничиться только пакетами для разработчиков. Как видно, предлагаются две версии: 1.1 и 1.3. Что же, возьмем версию поновее: 1.3. Следовательно, нас интересует, в первую очередь, самый последний пакет из списка.

Итак, для установки FLTK в Ubuntu можно использовать команду

sudo aptitude install libfltk1.3-dev

Примечание: окончание -dev означает, что будут устанавливаться библиотеки для разработчиков (для developers), т.е. статические библиотеки.

где 1.3 – это версия, которая будет установлена. А вообще, список доступных версий можно посмотреть на официальном сайте разработчика. После установки библиотека будет расположена в пользовательском библиотечном каталоге, подкаталог fltk (точнее, usr/local/fltk-1.3.5). У меня установка прошла без каких-либо проблем и довольно быстро, не в пример той же wxWidgets, коя устанавливалась, наверное, минут 15.

Хотя, можно установить и иначе, вручную. Например, скачать библиотеку FLTK с официального сайта. Это рекомендуется, если предыдущие операции не привели к желаемому результату.

Для Linux скачается файл типа fltk-1.3.5-source.tar.bz2 (на дату 10.05.2019)

Далее, разархивируем этот файл:

$ tar xzvf fltk-1.3.5-source.tar.bz2

Затем переходим в только что созданный (архиватором) каталог:

$ cd fltk-1.3.5

Далее:

# ./configure --prefix=/usr/local/fltk-1.3.5 --enable-xft --enable-threads

# make && make install

Для Windows, конечно, установка будет отличаться. Впрочем, можно FLTK установить кроссплатформенно и сразу делать кроссплатформенные GUI-приложения. Об этом, возможно, напишу попозже.

Простейшее графическое приложение, запускающее консольную команду ls

Рассмотрим программный код. Назовем файл, его содержащий, именем FLTKbegining.cpp:

  1. // Компиляция: g++ -std=c++11 FLTKbegining.cpp -o FLTKdynamic -lfltk
  2. #include <FL/Fl.H>
  3. #include <FL/Fl_Window.H>
  4. #include <FL/Fl_Box.H>
  5. #include <FL/Fl_Button.H>

  1. int main(int argc, char **argv) {
  2. Fl_Window *window = new Fl_Window(100, 200, 600,400);
  3. Fl_Box *box = new Fl_Box(20,40,400,100,"Вывод в консоль \n результата команды ls \n при каждом нажатии на кнопку");
  4. box->box(FL_UP_BOX);
  5. box->labelfont(FL_BOLD+FL_ITALIC);
  6. box->labelsize(22);
  7. box->labeltype(FL_SHADOW_LABEL);

  1. window->color(FL_WHITE);

  1. Fl_Button but(40, 180, 170, 50, "Нажмите!");
  2. but.callback([](Fl_Widget* w, void*) {
  3. Fl_Widget* p = w->parent();
  4. p->color(p->color() == FL_WHITE ? FL_RED : FL_WHITE);
  5. p->redraw();
  6. system("ls");
  7. });

  1. window->end();
  2. window->show(argc, argv);
  3. return Fl::run();
  4. }

Как видим, не настолько уж это все и объектно-ориентированное. Ну, так, в меру. Поэтому, есть смысл разобраться.

Теперь посмотрим, как же можно скомпилировать программу. Это можно сделать двумя способами: динамически и статически.

Динамическая компиляция

Динамическая компиляция применяется для того, чтобы сделать объем исполняемого файла поменьше. За счет того, что исполняемые библиотеки FLTK в нем присутствовать не будут. Но, при этом сама FLTK должна быть установлена на компьютере пользователя, иначе программа не запустится.

Динамически программа компилируется как обычно (ну, с указанием стандарта С++11):

Компиляция: g++ -std=c++11 FLTKbegining.cpp -o FLTKdynamic -lfltk

Последний параметр представляет собой указание на необходимость подключения библиотеки FLTK.

Посмотрим зависимости, с которыми связана эта программа, при помощи команды ldd, набрав в консоли:

$ ldd FLTKdynamic

Результат получится примерно следующим:

  • linux-vdso.so.1 =>  (0x00007ffc90d98000)
  • libfltk.so.1.3 => /usr/lib/x86_64-linux-gnu/libfltk.so.1.3 (0x00007f194bdd9000)
  • libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f194ba57000)
  • libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f194b841000)
  • libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f194b477000)
  • libXfixes.so.3 => /usr/lib/x86_64-linux-gnu/libXfixes.so.3 (0x00007f194b271000)
  • libXext.so.6 => /usr/lib/x86_64-linux-gnu/libXext.so.6 (0x00007f194b05f000)
  • libXft.so.2 => /usr/lib/x86_64-linux-gnu/libXft.so.2 (0x00007f194ae4a000)
  • libfontconfig.so.1 => /usr/lib/x86_64-linux-gnu/libfontconfig.so.1 (0x00007f194ac07000)
  • libXinerama.so.1 => /usr/lib/x86_64-linux-gnu/libXinerama.so.1 (0x00007f194aa04000)
  • libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f194a7e7000)
  • libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f194a5e3000)
  • libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f194a2da000)
  • libX11.so.6 => /usr/lib/x86_64-linux-gnu/libX11.so.6 (0x00007f1949fa0000)
  • /lib64/ld-linux-x86-64.so.2 (0x00007f194c101000)
  • libfreetype.so.6 => /usr/lib/x86_64-linux-gnu/libfreetype.so.6 (0x00007f1949cf6000)
  • libXrender.so.1 => /usr/lib/x86_64-linux-gnu/libXrender.so.1 (0x00007f1949aec000)
  • libexpat.so.1 => /lib/x86_64-linux-gnu/libexpat.so.1 (0x00007f19498c3000)
  • libxcb.so.1 => /usr/lib/x86_64-linux-gnu/libxcb.so.1 (0x00007f19496a1000)
  • libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007f1949487000)
  • libpng12.so.0 => /lib/x86_64-linux-gnu/libpng12.so.0 (0x00007f1949262000)
  • libXau.so.6 => /usr/lib/x86_64-linux-gnu/libXau.so.6 (0x00007f194905e000)
  • libXdmcp.so.6 => /usr/lib/x86_64-linux-gnu/libXdmcp.so.6 (0x00007f1948e58000)

Как видим, подключено множество динамических библиотек (они имеют расширение .so.*). И среди них одна из самых первых – как раз FLTK в соответствующей версии 1.3.

Объем исполняемого файла составил 17,5 кБ. Т.е. – вообще мизер. Как я уже говорил, для этого и применяется динамическая компиляция.

Статическая компиляция

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

g++ -std=c++11 fltk1.cpp -o FLTKstatic `fltk-config --cxxflags --ldstaticflags`

То, что находится во второй половине команды, заключено в ОБРАТНЫЕ апострофы. Там – особые параметры компиляции/сборки, которые задал разработчик библиотеки FLTK. Для нас интерес представляет последний параметр --ldstaticflags, который как раз и задает режим статической компиляции (сборки) программы.

Примечание: статическая сборка будет производиться только в отношении самой FLTK. Тогда как остальные библиотеки (см. ниже) будут также подключаться динамически. Отчасти это – системные библиотеки Linux, отчасти – установленные позже.

Что же, проверяем связи нашей программы при помощи утилиты ldd:

ldd FLTKstatic

И, примерно вот что получится:

  • linux-vdso.so.1 =>  (0x00007ffe490db000)
  • libXfixes.so.3 => /usr/lib/x86_64-linux-gnu/libXfixes.so.3 (0x00007f906ffc4000)
  • libXext.so.6 => /usr/lib/x86_64-linux-gnu/libXext.so.6 (0x00007f906fdb2000)
  • libXft.so.2 => /usr/lib/x86_64-linux-gnu/libXft.so.2 (0x00007f906fb9d000)
  • libfontconfig.so.1 => /usr/lib/x86_64-linux-gnu/libfontconfig.so.1 (0x00007f906f95a000)
  • libXinerama.so.1 => /usr/lib/x86_64-linux-gnu/libXinerama.so.1 (0x00007f906f757000)
  • libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f906f53a000)
  • libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f906f336000)
  • libX11.so.6 => /usr/lib/x86_64-linux-gnu/libX11.so.6 (0x00007f906effc000)
  • libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f906ec7a000)
  • libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f906e971000)
  • libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f906e75b000)
  • libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f906e391000)
  • libfreetype.so.6 => /usr/lib/x86_64-linux-gnu/libfreetype.so.6 (0x00007f906e0e7000)
  • libXrender.so.1 => /usr/lib/x86_64-linux-gnu/libXrender.so.1 (0x00007f906dedd000)
  • libexpat.so.1 => /lib/x86_64-linux-gnu/libexpat.so.1 (0x00007f906dcb4000)
  • /lib64/ld-linux-x86-64.so.2 (0x00007f90701ca000)
  • libxcb.so.1 => /usr/lib/x86_64-linux-gnu/libxcb.so.1 (0x00007f906da92000)
  • libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007f906d878000)
  • libpng12.so.0 => /lib/x86_64-linux-gnu/libpng12.so.0 (0x00007f906d653000)
  • libXau.so.6 => /usr/lib/x86_64-linux-gnu/libXau.so.6 (0x00007f906d44f000)
  • libXdmcp.so.6 => /usr/lib/x86_64-linux-gnu/libXdmcp.so.6 (0x00007f906d249000)

Как видим, практически все то же самое, что и при динамической компиляции. Но, в списке зависимостей теперь нет libfltk.so.1.3. Это означает, что для запуска программы FLTKstatic вовсе необязательно устанавливать FLTK (в отличие от FLTKdynamic).

Примечание: НО, другие библиотеки, которые указаны в списке зависимостей, должны быть установлены в обязательном порядке, иначе программа не запустится.

Посмотрев объем файла FLTKstatic (например, в менеджере файлов через свойства файла), видим, что он составляет где-то 462 кБ. Т.е. больше объема  FLTKdynamic в 27 с лишним раз. Конечно, за счет того, что FLTKstatic включает в себя файлы библиотеки FLTK.

Запуск программы

Запуск программы производится из консоли обычным образом:

./FLTKdynamic

или

./FLTKstatic

Или можно найти исполняемый файл программы в менеджере файлов и кликнуть по нему два раза левой кнопкой мыши. В конце концов, раз уж графический интерфейс - GUI, то вполне можно действовать как в Windows, забыв о консоли (да-да, только для запуска и использования программы, не более того, конечно).

Скрыть пояснение
Еще раз: GUI - это графический интерфейс пользователя. А не программиста. Программист должен одинаково хорошо отлично владеть и тем, и другим. А вот пользователю, чтобы не напрягаться излишне, вполне достаточно умения нажимать на нужные кнопки GUI.

После чего на экране появится окно с надписью «Вывод в консоль результата команды ls (для примера) при каждом нажатии на кнопку». Ниже надписи будет расположена кнопка с надписью «Нажмите!». После каждого нажатия на кнопку в консоль, из которой была запущена программа, будет выводиться содержимое текущего каталога и, одновременно, фон окна программы будет менять свой цвет с белого на красный и, наоборот.

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

Кроме того, попробуйте позапускать эту программу с параметрами (из консоли). Вначале введите произвольный параметр, например, xyz. После запуска программа отобразит в консоль список возможных опций для команды show() (см. строчку 22). Как видите, функциональность библиотека столь высока, что она, при вводе неверных параметров, даже сама подсказывает перечень возможных параметров. Так что такая вот мини-IDE. На мой взгляд, очень удобная и при этом – минималистическая.

Таким образом, данная библиотека вполне может служить для создания графического интерфейса пользователя, предназначенного для запуска консольных программ в Linux, Windows, а также и в MacOS. Как раз для этого и только для этого она и предназначена. В чем ее плюс, в отличие от тяжеловесных Qt и т.д..

Скрыть пояснение
Но, конечно, если надо Android, а то и OpenWMS, то там понадобится, скорее всего, как раз пресловутая Qt.
Примечание. Хотя, конечно, вот, тут я что-то там написал (хотя, это что-то – вполне работает), но, все-таки, не дает покоя – знаете, что? MS-DOS/UNIX прошедшего столетия! Реально, похоже, даже засыпаю теперь плохо из-за этого. Это когда вполне хорошая, достаточно функциональная программа, обладающая графическим(!) интерфейсом, вполне умещалась на дискете, занимая, уж во всяком случае, не более 1,45 МБ (видимо, потому, что тогда еще не было библиотек типа Qt). Вот, друзья, к ЧЕМУ надо бы нам стремиться! Вот она – цель развития/оптимизации программ! Все-таки, если будет время, пожалуй, доберусь я в GUI до низу, почти до системного интерфейса / ядра. Что-то подсказывает мне, что конкретно дурят нас все эти Qt/GTK, java, .NET и прочие Python (не говоря уж о разных однодневках, типа Go/D/Kotlin…), просто так, не глядя, накручивая мегабайты объема исполняемых файлов, использующих GUI.
Собственно, такая мысль у меня была уже давно, лет так 20…30 назад. Но, со временем она стала более отчетливой. Выкристаллизовалась, что ли.


Да еще и утверждая, что, мол, без этих многих мегабайтов – «никак не обойтись».

Windows до ядра добраться, видимо, так и не даст (ну, типа, исходные коды закрыты; ну, конечно, крутая нынче корпорация, куда уж там… не подойди, как говорится). Ну, да и бог с ним, с этим Windows. А если они еще и введут сетевую операционную систему, начнут воспрепятствовать работе в Windows без наличия подключенной сети интернет – да, как-то уж не обижусь; просто, забуду тогда о них, об этих Microsoft, да и все. А вот в Linux, мне кажется, вполне можно будет делать, в итоге, экономичные и быстрые GUI, по объему не намного превышающие объемы консольных программ. Ну, а если начать хорошо делать GUI – так это все, это уже - начало массовых удобств для работы в Linux. И, что хорошо – эти удобства можно делать такие, какие только захочется. Ну, разве что, соображать более-менее нужно, да и все.


Вывод значения счетчика нажатия кнопки в текстовое поле

Теперь потренируемся работать с текстовыми полями библиотеки FLTK. Они пригодятся нам в дальнейшем, когда будем разрабатывать GUI для системной оболочки (и в итоге получится нечто, напоминающее графический файловый менеджер - по типу проводника (Explorer) в Windows или Nautilus в Linux). Вот программный код, назовем программу fltk2.cpp:

// g++ -std=c++11 fltk2.cpp -o fltk2 `fltk-config --cxxflags --ldstaticflags`  && ./fltk2

  1. #include <FL/Fl.H>
  2. #include <FL/Fl_Window.H>
  3. #include <FL/Fl_Box.H>
  4. #include <FL/Fl_Button.H>
  5. #include <FL/Fl_Light_Button.H>
  6. #include <FL/Fl_Text_Display.H>
  7. #include <FL/Fl_Int_Input.H>

  1. #include <iostream>
  2. #include <cstring>
  3. #include <stdlib.h>
  4. #include <stdio.h>
  5. #include <unistd.h>  

  1. using namespace std;

  1. Fl_Text_Buffer *buff; // = (Fl_Text_Buffer *)0; // Попробуйте объявить эти переменные
  2. Fl_Light_Button *but; //=(Fl_Light_Button *)0; // как указатели, убрав символы комментирования.
  3. Fl_Text_Display *disp; //  = (Fl_Text_Display *)0; // Что изменится в работе программы, почему?
  4. Fl_Int_Input *field;

  1. string ex = "";
  2. char* str;
  3. int i = 0;

  1. void changer(Fl_Widget* w,   void *){ // Процедура – обработчик нажатия кнопки but

  1. Fl_Widget* p = w->parent(); // Обращаемся к родительскому виджету, т.е. к главному окну
  2. p->color(p->color() == FL_WHITE ? FL_BLUE : FL_WHITE);
  3. p->redraw(); // Перерисовываем главное окно

  1. printf(" Fl_Light_Button = %d \n", ((Fl_Light_Button*)but)->value()); // Флаг нажатия кнопки (0 или 1) выводим в консоль (если программа запущена из консоли)
  2. i=i+1; // Предыдущее значение i будет сохраняться при очередном запуске процедуры changer
  3. string iStr = to_string(i); // Работает в -std=c++11
  4. ex = "№ нажатия: " + iStr; // Счетчик нажатия кнопки
  5. str = (char*)ex.c_str();

  1. disp->buffer(buff); // прикрепляем текстовый буфер к display widget
  2. buff->text((const char*)str); // добавляем текст в текстовый буфер

  1. // buff->text("123456789"); // В итоге, при каждом очередном срабатывании функции changer, в Fl_Text_Buffer выводится именно эта строка, а значение счетчика, записанное предыдущей командой, затирается
  2. }

  1. int main(int argc, char **argv) {
  2. ex = "main begining...";
  3. str = (char*)ex.c_str();

  1. setlocale(LC_ALL, "UTF-8"); // Устанавливаем кодировку для кириллицы

  1. Fl_Window *window = new Fl_Window(100, 200, 600,400); // Рисуем главное окно
  2. Fl_Box *box = new Fl_Box(20,30,350,120,"Это  \n просто  \n надпись");
  3. box->box(FL_UP_BOX);
  4. box->labelfont(FL_BOLD+FL_ITALIC);
  5. box->labelsize(22);
  6. box->labeltype(FL_SHADOW_LABEL);

  1. window->color(FL_WHITE);

  1. //Fl_Text_Buffer *
  2. buff = new Fl_Text_Buffer();
  3. //Fl_Text_Display *
  4. disp = new Fl_Text_Display(420, 20, 170, 280); // Создаем display widget (многострочное текстовое поле)
  5. disp->buffer(buff);
  6. buff->text(str);

  1. but =  new Fl_Light_Button(40, 180, 170, 50, "Нажмите!"); // Создаем кнопку
  2. but->callback(changer); // Устанавливаем обработчик нажатия этой кнопки

  1. field = new Fl_Int_Input(190, 350, 405, 25, "Можно что-то написать:"); // Текстовое поле - так, на всякий случай

  1. window->end();
  2. window->show(argc, argv);
  3. return Fl::run();
  4. }

После запуска программы, при очередном нажатии кнопки в текстовое поле (справа) должно выводиться значение счетчика нажатий (1, 2, 3 и т.д.). Как видим, программа получилась простой, даже очень простой.

Скрыть пояснение
Простой - настолько, что (пока) не нужна никакая IDE. Хотя, цена тому - четкое знание объектов, которые использованы в программе, т.е. тех, которые задают кнопку, текстовые поля. Конечно, в какой-нибудь Delphi все будет гораздо проще: там-то можно попросту выбрать необходимый объект, перетянуть его мышью на поле формы, также мышью задать свойства, методы, обработчики и... всё. Ну, а без IDE придется потрудиться немножко побольше.
Задание для самостоятельной работы:

1. Установите библиотеку, скомпилируйте и запустите данный программный код.

2. Реализуйте вывод в файл информации, выводимой командой ls. При этом вывод должен происходить независимо от того, как была запущена программа: то ли из консоли, то ли из менеджера файлов.

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

Сам я использовал это руководство.

4. Посмотрите самостоятельно, как работает IDE под названием «FLUID», предназначенная для этой библиотеки. Ее использование позволит несколько упростить создание GUI. Описание находится здесь.

5. Посмотрите реализацию примеров GUI, подготовленных разработчиками библиотеки FLTK. Примеры (исходные коды и исполняемые файлы) находятся в каталоге fltk-1.3.5/test.


Вывод списка элементов текущего каталога в текстовые поля Fl_Text_Buffer и Fl_Browser

Теперь попробуем прочитать список текущего каталога (в котором находится исполняемый файл программы) и вывести его содержимое в консоль, а также в два текстовых поля: Fl_Text_Buffer и Fl_Browser. Рассмотрим такой программный код (назовем его fltk3.cpp):

// g++ -std=c++11 fltk3.cpp -o fltk3 `fltk-config --cxxflags --ldstaticflags`  && ./fltk3

  1. #include <FL/Fl.H>
  2. #include <FL/Fl_Window.H>
  3. #include <FL/Fl_Box.H>
  4. #include <FL/Fl_Button.H>
  5. #include <FL/Fl_Light_Button.H>
  6. #include <FL/Fl_Text_Display.H>
  7. #include <FL/Fl_Int_Input.H>
  8. #include <FL/Fl_Select_Browser.H>

  1. #include <iostream>

  1. #include <cstring>
  2. #include <stdlib.h>
  3. #include <stdio.h>
  4. #include <unistd.h>  
  5. #include <sys/types.h>
  6. #include <dirent.h>
  7. #include <errno.h>

  1. using namespace std;

  1. Fl_Text_Buffer *buff; // = (Fl_Text_Buffer *)0;
  2. Fl_Light_Button *but; //=(Fl_Light_Button *)0;
  3. Fl_Text_Display *disp; //  = (Fl_Text_Display *)0;
  4. Fl_Int_Input *field;
  5. Fl_Select_Browser *browser;

  1. string ex = "";
  2. char* str;
  3. int i = 0;

  1. void changer(Fl_Widget* w,   void *){
  2. Fl_Widget* p = w->parent(); // Обращаемся к родительскому виджету, т.е. к главному окну
  3. p->color(p->color() == FL_WHITE ? FL_BLUE : FL_WHITE); // Меняем цвет при очередном нажатии кнопки
  4. p->redraw(); // Перерисовываем главное окно

  1. printf(" Fl_Light_Button = %d \n", ((Fl_Light_Button*)but)->value()); // Флаг нажатия кнопки (0 или 1) выводим в консоль (если программа запущена из консоли)
  2. i=i+1; // Предыдущее значение i будет сохраняться при очередном запуске процедуры changer
  3. string iStr = to_string(i); // Работает в -std=c++11
  4. ex = "№ нажатия: " + iStr + "\n";
  5. str = (char*)ex.c_str();

  1. DIR *dir;
  2. int u;
  3. struct dirent *sdir;
  4. dir=opendir(".");
  5. if(dir==NULL) {cout<<"Error: "<<strerror(errno)<<"\n"; }
  6. chdir("."); // Или в другой каталог - тогда следует указать к нему путь
  7. browser->clear(); // Очищаем окно "браузера", т.к. иначе там будут оставаться данные, помещенные в процессе предыдущих срабатываний функции changer

  1. while((sdir=readdir(dir))!=NULL) // Как обычно, читаем каталог до тех пор, пока ядро не вернет функции readdir значение NULL. А это случится после того, как будет прочитан последний элемент каталога и более нечего будет читать
  2. {
  3. // if(strcmp(".", sdir->d_name)==0 || strcmp("..", sdir->d_name)==0)
  4. // continue; // Узнаете знакомый код (с которым Вы, возможно, уже встречались в процессе создания системной оболочки)?.
    Скрыть пояснение
    Так как из года в год у немалого количества студентов эти строчки вызывают непонимание, придется мне потрудиться и рассказать о ней подробнее. Эти строчки использовались в функции (рекурсивного) удаления каталога. Итак, в предыдущих двух строчках вначале проверяется, совпадает ли имя очередного, считанного при помощи функции readdir, элемента текущего каталога с именем текущего каталога (обозначаемого как ".") или с именем родительского каталога (".."). Если да, то нам следует пропустить все нижеидущие действия, для чего служит команда continue, передающая управление к началу цикла while. Если же НЕ совпадает, то в этом случае можно делать дальнейшие процедуры, в частности, делать рекурсивный вызов и т.д.

    А почему нельзя применять рекурсивный вызов функции (удаления каталога) к таким элементам текущего каталога, как "." и ".."? Рассмотрим, для примера, элемент с именем ".". Это - ссылка на текущий каталог. Если мы не вызовем команду continue, то ниже для этого элемента сработает рекурсивный вызов той же самой функции (удаления каталога). В ходе этого вызова вновь встретится элемент "." (ссылка на текущий каталог). Вновь будет сделан рекурсивный вызов и т.д. В итоге - возникнет множество рекурсивных вызовов, что приведет к зацикливанию и переполнению стека. Аналогичная ситация будет с элементом "..".
  5. ex = ex + sdir->d_name + "\n";
  6. str =  (char*)ex.c_str();

  1. browser->add(sdir->d_name, 0); // Добавляем в окно "браузера" очередную строку текста
  2. }
  3. cout << ex << "\n" ; // Выводим в консоль содержимое (текущего) каталога
  4. closedir(dir);  

  1. disp->buffer(buff); // прикрепляем текстовый буфер к display widget
  2. buff->text((const char*)str); // добавляем текст (cодержимое каталога сразу, в виде единой строки) в текстовый буфер
  3. // buff->text("rfrfrfrf"); // Попробуйте раскомментировать: несмотря на дублирование команды, добавление текста в буфер сработает только 1 раз при каждом очередном срабатывании функции changer
  4. }

  1. void b_cb(Fl_Widget* o, void*) { // Вывод в консоль информации о строке "браузера" при клике мышью на ней
  2. int browLine = ((Fl_Browser*)o)->value();
  3. printf("callback, selection = %d, name = %s, event_clicks = %d\n",
  4. browLine, ((Fl_Browser*)o)->text(browLine), Fl::event_clicks()); // Выводим в консоль номер строки окна "браузера", имя элемента каталога, содержащееся в строке, по которой был сделан клик кнопкой мыши в окне "браузера" и число БЫСТРЫХ кликов мышью по одной и той же строке. Т.е. можно идентифицировать одиночные, двойные, тройные, четверные и т.д. клики.
  5. }

  1. int main(int argc, char **argv) {
  2. ex = "main begining...";
  3. str = (char*)ex.c_str();

  1. setlocale(LC_ALL, "UTF-8");

  1. Fl_Window *window = new Fl_Window(100, 100, 700,600);
  2. Fl_Box *box = new Fl_Box(20,30,350,120,"Это  \n просто  \n надпись");
  3. box->box(FL_UP_BOX); // Определяем тип box
  4. box->labelfont(FL_BOLD+FL_ITALIC); // и параметры шрифта в нем
  5. box->labelsize(22);
  6. box->labeltype(FL_SHADOW_LABEL);

  1. window->color(FL_WHITE); // Первоначально устанавливаем белый цвет главного окна

  1. //Fl_Text_Buffer *
  2. buff = new Fl_Text_Buffer();
  3. //Fl_Text_Display *
  4. disp = new Fl_Text_Display(420, 20, 170, 280);
  5. disp->buffer(buff);
  6. buff->text(str);

  1. but =  new Fl_Light_Button(40, 180, 170, 50, "Нажмите!");
  2. but->callback(changer); // Устанавливаем обработчик нажатия кнопки

  1. browser = new Fl_Select_Browser(40,400,200,150,0);
  2. browser->type(FL_MULTI_BROWSER);
  3.  //browser->type(FL_HOLD_BROWSER); //  попробуйте разные типы "браузеров":
  4.  //browser->type(FL_SELECT_BROWSER); // или
  5.  //browser->type(FL_BROWSER); //
  6.  //browser->color(42);
  7. browser->callback(b_cb); // Устанавливаем обработчик клика мыши на окне "браузера"

  1. field = new Fl_Int_Input(195, 350, 405, 25, "Можно написать цифры:"); // Текстовое поле - так, на всякий случай; данные из него никуда не передаются

  1. window->end();
  2. window->show(argc, argv);
  3. return Fl::run();
  4. }

Запускаем - как обычно. Примерно вот что получается при работе программы:

Результат работы GUI, выводящего список элементов текущего каталога
Примечание: вместо fltk4 в консоли (при запуске программы) должно быть, конечно, fltk3 (на рисунке - опечатка).


Задание для самостоятельной работы:

1. Проанализируйте, скомпилируйте и запустите программный код.

2. Попробуйте разные типы "браузеров", выясните отличия в их работе. Сделайте отчет об отличиях (краткий или подробный - на Ваше усмотрение).


С уважением, Салимоненко Д.А.