Pull to refresh

Пишем приложение на GTK+ используя C++ и GTKMM библиотеку

Reading time 5 min
Views 23K

Эпиграф


Однажды попросила меня жена написать ей простенькую программку, которая сможет вычислять площади фигур, периметры, и другие параметры при наличии достаточных данных. Например, нужна площадь треугольника, указаны его стороны. Вводим стороны нажимаем кнопочку и получаем площадь. Или указана только сторона и два угла. В общем любые данные, достаточные для того чтобы вычислить остальное.
Стоит отметить, что я являюсь последние лет 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++ — это процедурные языки.
Tags:
Hubs:
+15
Comments 20
Comments Comments 20

Articles