Эпиграф
Однажды попросила меня жена написать ей простенькую программку, которая сможет вычислять площади фигур, периметры, и другие параметры при наличии достаточных данных. Например, нужна площадь треугольника, указаны его стороны. Вводим стороны нажимаем кнопочку и получаем площадь. Или указана только сторона и два угла. В общем любые данные, достаточные для того чтобы вычислить остальное.
Стоит отметить, что я являюсь последние лет 5 только веб-разработчиком, в основном PHP, хотя конечно иногда что-то нужно сделать и на ruby и на perl. В общем язык для меня особо не проблема, главное понять смысл процессов в компьютере, а дальше хоть Assembler (когда-то даже занимался дизасемблированием и небольшим патчингом приложений под Windows). Но все-таки когда писал десктопные приложения уже и не помню. Но тут решил написать именно десктопное, чтобы жене было удобно им пользоваться при отсутствии интернета и не нужно было на ее ноутбук ставить вебсервер с PHP. Кроме того уже давно хотел попробовать себя в использовании языка C++. Ну что ж. У жены стоит на ноутбуке Linux Ubuntu. Графическая система — Unity, основанная на Gnome3. А там где Gnome, там GTK+.
Вот так и было решено написать десктопное приложение под Linux используя Gtk+. Интересно? Добро пожаловать под кат!
Понимание библиотеки GTK+
Стоит заметить, что большинство библиотек и приложений в Linux выполнены с использованием процедурного подхода. У меня же настолько укоренилось объектно ориентированное мышление что без полноценного использования ООП мне жутко не комфортно. Решил я обернуть функции вызовы GTK+ в свои самописные классы. Понятное дело, сделал я это абсолютно непрофессионально, без использования специфических для C++ конструкций. Что и говорить, Страуструп был прочитан менее чем на половину, а весь мой ООП исходит оттуда, где я его начал использовать — из PHP. Некрасиво, но сработало. Легче получилось нарисовать элементы, отобразить окошко, обработать события. Но все же не то. Реализовав таким образом приложение я оставил его на год. Заморозил. Но вдруг на глаза мне попалась библиотека GTKMM — официальный C++-интерфейс для GUI-библиотеки GTK+. И вот взял я это заброшенное приложение и начал его преобразовывать. Понятное дело основные самописные классы я выкинул, так как заменил их классами GTKMM. Вот своим опытом знакомства с этой замечательной библиотекой я хочу с вами поделиться, мои дорогие читатели. Итак, приступим.
Сразу прошу прощения, что опущу создание интерфейса нашего приложения в начале, хотя, считаю что именно с создания внешнего вида (точнее расположения элементов управления на форме, для того чтобы в дальнейшем оживлять их), но все же приложение было написано и отрисовка в первой версии происходила с помощью вызовов определенных функций. Теперь же было решено использовать GtkBuilder, но об этом немного позднее.
Точка входа
Точкой входа в приложение на C++ является функция
main()
. В нашем приложении она будет загружать из GtkBuilder
файла нашу форму, отображать ее и запускать цикл обработки сообщений приложением.Инициализируем приложение
Glib::RefPtr<Gtk::Application> app = Gtk::Application::create(argc, argv, "org.apmpc.geometry");
Дальше создадим нашу форму
builder = Gtk::Builder::create_from_file(UI_FILE);
Привяжем форму к классу — обработчику (класс используется собственный, который является наследником от
Gtk::Window
, поэтому будем использовать метод get_widget_derived
.builder->get_widget_derived("MainWindow", appwindow);
Для справки, если вы хотите описывать обработчики в виде функций и не хотите плодить классы для небольшого приложения, небольшой формы или по религиозным причинам, вы можете использовать объект класса
Gtk::Window
, как указано в документации и в таком случае будет использоваться метод get_widget
.Ну и конечный пункт — запуск цикла сообщений приложения
return app->run(*appwindow);
Приложение
Поздно, но все же немного подробнее опишу составные части приложения. Оно состоит из двух форм. Первая — главное окно. На нем расположены кнопки фигур.
Нажимаем на кнопку — выбираем фигуру. Далее открывается вторая форма — окно фигуры.
Слева находятся поля ввода значений и результата, справа — рисунок выбранный фигуры. При проектировании учитывалось расширение функциональности, добавление фигур, поэтому кнопки выбора фигур создаются при запуске главного окна.
Для окна главной формы создан класс MainWindow, наследующийся от
Gtk::Window
. В конструкторе этого класса происходит создание кнопок фигур. Хранилищем указателей на кнопки является вектор m_pvShapeButtons
.m_pvShapeButtons.push_back(new Gtk::Button(CShape::getShapeName(i+1)));
К каждой кнопке на событие
clicked
вешается метод MainWindow::onShapeButtonClick(int iShape)
m_pvShapeButtons.at(i)->signal_clicked().connect(
sigc::bind<int> (sigc::mem_fun( *this,
&MainWindow::onShapeButtonClick), (i+1) ) );
Мне необходимо передавать индекс фигуры в метод обработки события clicked, так как существует единственный класс обработки фигуры
CShape
. В нем находится информация о остальных классах фигур, которые наследуются от класса CShape
. В них уже информация о полях для ввода данных, названия этих полей и формулы для расчета.Ну и не забываем прикрепить кнопку к контейнеру и отобразить
m_pShapeButtonBox->pack_end(*m_pvShapeButtons.at(i));
m_pvShapeButtons.at(i)->show();
При нажатии на кнопку мы открываем другое окно. По индексу мы определяем фигуру и выводим поля для ввода данных. Поле для ввода состоит из трех частей — контейнера
Gtk::Box
, содержащего метку Gtk::Label
для вывода названия поля ввода и поле для ввода текста Gtk::Entry
. Все это было решено объединить в один класс с названием CEditBox
. Так же он будет содержать методы, необходимые для ввода и вывода данных с преобразованием типов.В конструкторе ShapeWindow создадим необходимое количество
CEditBox
для выбранной фигуры. Отрисуем фигуру в правой стороне окна, где находится объект класса CCanvas
, который наследуется от Gtk::DrawingArea
. Отрисовка происходит по событию onDraw
. Информация о фигуре содержится в классе фигуры. Метод draw класса фигуры вызывает методы класса CCanvas
для отрисовки графических примитивов (Линии, окружности, текст)Заключение
Вот и все что хотелось бы сообщить о моем первом приложении на GTK+ с использованием GTKMM, да и вообще на C++. Весь код находится на github. Дальнейшие планы — сделать реализацию расчета площади параллелограмма, добавить возможность сбора пакета debian и выложить на ppa. Буду рад ответить на все вопросы и комментарии к данной статье.
UPD. Исправил функциональный подход на процедурный. Как оказалось, словосочетание функциональный подход, или точнее функциональное программирование используется в случаях когда происходит вычисление функций в математическом понимании, а не функций как подпрограмм, как я думал изначально. Для примера, языки которые используют функциональный подход: LISP, Erlang, Scala. C и C++ — это процедурные языки.