Cистема плагинов Qt, построение графиков и Qt Script

Очень обзорная статья, описывает вершки некоторых интересных элементов библиотеки Qt. Речь идет про:

  • библиотеку Qwt, позволяющую строить графики, гистограммы, круговые (и другие) диаграммы;
  • систему плагинов библиотеки Qt, предназначенную для предоставления возможности модульного расширения программы;
  • Qt Script — язык, позволяющий, как и система плагинов, дополнять программу, но не требующий компиляции (плагина).

Статья не претендует на полноту и описывает ровно столько, сколько требуется для решения придуманной задачи. В конце статьи есть ссылки, по которым можно почитать более подробно. Так, например, важнейшая возможность скриптов Qt Script соединяться со слотами и сигналами в статье совсем не затронута, как и различные виды диаграмм Qwt.

Решаемую задачу можно сформулировать следующим образом:

Разработать программу численного интегрирования функции на заданном интервале с фиксированным шагом. Программа должна предоставлять возможность расширения за счет подключения модулей, реализующих отдельные методы интегрирования. Программа должна выводить график интегрируемой функции и визуализировать результаты интегрирования. Интегрируемая функция вводится пользователем во время выполнения программы (рис. 1).

рис. 1 снимок окна программы

рис. 1 снимок окна программы

За счет системы плагинов реализуется подключение дополнительных методов интегрирования. Библиотека Qwt используется для визуализации, а Qt Script — для предоставления возможности ввода интегрируемой функции.

1. Система плагинов и архитектура приложения

1.1 Использование плагинов Qt

Наши плагины будут уметь выполнять 2 операции — возвращать свое имя (отображается в выпадающем списке методов интегрирования) и выполнять численное интегрирование. В результате интегрирования, плагин формирует 2 дополнительных вектора с координатами точек, используемых для визуализации.

Скомпилированные плагины помещаются в подкаталог plugins, в котором осуществляется их поиск после запуска приложения.

#include "mainform.h"
#include <QApplication>
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
MainForm form;
form.loadPlugins();
form.show();
app.exec();
}

Каждый загруженный плагин представляет собой объект с заданным интерфейсом, который должен быть заранее зарегистрирован макросом Q_DECLARE_INTERFACE.

#ifndef PLUGININTERFACE_H
# define PLUGININTERFACE_H
# include <functional>
# include <QString>
# include <QtPlugin>
class PluginInterface {
public:
virtual ~PluginInterface() { }
virtual QString text() const = 0;
//!< возвращает текст для идентификации плагина
virtual double calc(const std::function<double(double)>& f,
const double a, const double b, const int n,
QVector<double> &x, QVector<double> &y) = 0;
//!< \brief функция интегрирования
//!< интегрирует f на интервале [a, b]
//!< делит интервал на n частей
//!< возвращает данные для визуализации
};
Q_DECLARE_INTERFACE(PluginInterface, "com.pro-prof.PluginInterface")
#endif // PLUGININTERFACE_H

Вторым аргументом Q_DECLARE_INTERFACE принимает строку, которая должна идентифицировать плагин. Макрос объявляет несколько шаблонных функций, в том числе qobject_cast, используемую для проверки соответствия загружаемого плагина нашему интерфейсу.

void MainForm::loadPlugins() {
QDir dir(QApplication::applicationDirPath());
if (false == dir.cd("../plugins")) return;
foreach (QString fileName, dir.entryList(QDir::Files)) {
QPluginLoader loader(dir.absoluteFilePath(fileName));
QObject *plugin = loader.instance();
if (nullptr == plugin) continue;
PluginInterface *pI = qobject_cast<PluginInterface*>(plugin);
if (nullptr == pI) continue;
m_ui->methods->addItem(pI->text(), (unsigned int) pI);
}
}

Для загрузки плагинов используется объект QPluginLoader, возвращающий указатель на загруженный плагин. В нашем примере после щелчка по кнопке «Считать» должен вызваться метод объекта того плагина, который пользователь выбрал в выпадающем списке. Хранить где-то список загруженных плагинов — было бы правильное и хорошее решение. Однако, в примере используется грязный трюк — адрес объекта плагина помещается в качестве «пользовательских данных» элемента выпадающего списка (второй аргумент метода addItem).

void MainForm::on_run() {
// ...
PluginInterface
*pI = (PluginInterface*)m_ui->methods->currentData().toUInt();
// ...
r = pI->calc(integfun, a, b, n, xp, yp);
// ...
}

В слоте-обработчике кнопки «Считать» адрес объекта плагина восстанавливается из данных, хранимых элементом выпадающего списка, а затем, используется как обычно.

Теперь мы умеем взаимодействовать с плагинами Qt — загружать их и обращаться к методам. Разберемся с созданием собственных плагинов.

1.3 Разработка своих плагинов Qt

Для плагина нет необходимости описывать точку входа (функцию main), однако, необходимо задать некоторые опции проекта и использовать пару макросов.

TEMPLATE = lib
CONFIG += plugin

#ifndef LEFT_RECT_PLUGIN_H
# define LEFT_RECT_PLUGIN_H
# include "../app/plugininterface.h"
# include <QObject>
class Left_rect_plugin : public QObject, public PluginInterface {
Q_OBJECT
Q_PLUGIN_METADATA(IID "com.pro-prof.Left_rect_plugin")
Q_INTERFACES(PluginInterface)
public:
explicit Left_rect_plugin(QObject *parent = 0);
virtual QString text() const;
virtual double calc(const std::function<double(double)>& f,
const double a, const double b, const int n,
QVector<double> &x, QVector<double> &y);
};
#endif // LEFT_RECT_PLUGIN_H

Плагин использует множественное наследование — он является объектом Qt, но реализует заданный нами интерфейс. Макрос Q_INTERFACES определяет функцию qt_metacast, позволяющую системе связывать плагин с именем интерфейса. Макрос Q_PLUGIN_METADATA позволяет задавать файл с метаданными плагина (необязательный второй параметр, в нашем примере не используется) и настраивает экспорт плагина (выполняет работу устаревшего макроса Q_EXPORT_PLUGIN2).

2. Построение графиков

Для построения графиков с использованием Qt, можно использовать библиотеку Qwt, которая не является частью стандартной поставки и должна быть собрана отдельно. Библиотека позволяет строить графики, поверхности и различные виды диаграмм, с настройками масштабирования, легендой и прочими полезными штуками.

Библиотека Qwt предоставляет плагин для Qt Creator, добавляющий в Qt Designer дополнительные виджеты. При этом сборка Qwt должна выполняться тем же компилятором, что и Qt Creator. Если не хочется собирать плагин — на форме Qt Designer можно разместить какой-нибудь QVBoxLayout и программно привязать к нему виджет Qwt.

В нашем примере будет выполняться построение двух графиков различной толщины и цвета. Нам потребуются классы холста для рисования (QwtPlot) и линии графика (QwtPlotCurve).

MainForm::MainForm(QWidget *parent)
: QDialog(parent), m_ui(new Ui::Form),
m_plot(new QwtPlot(this)),
m_curve(new QwtPlotCurve()), m_approx(new QwtPlotCurve()) {
m_ui->setupUi(this);
m_ui->graph_layout->addWidget(m_plot);
m_curve->attach(m_plot);
m_approx->attach(m_plot);
m_curve->setVisible(true);
m_approx->setVisible(true);
m_curve->setPen(QPen(Qt::red, 2));
m_approx->setBaseline(0);
m_approx->setBrush(QColor(0, 0, 255, 50));
connect(m_ui->run, SIGNAL(clicked()), SLOT(on_run()));
}

В приведенном листинге задается 2 линии:

  • m_curve — график интегрируемой функции, рисуется красным цветом с толщиной в 2 пикселя;
  • m_approx — график, визуализирующий результаты интегрирования. Выводится черным (по умолчанию) цветом, пространство между линией и горизонтальной осью (настройка baseline) заливается синим цветом.

Слот-обработчик нажатия кнопки «Считать» для построения графика функции разбивает интервал на заданное (константой m_points_num) количество точек и заполняет 2 вектора (по вектору на каждую ось).

h = qAbs(b - a) / m_points_num;
for (int i = 0; i < m_points_num; ++i) {
x.push_back(a + i * h);
y.push_back(integfun(a + i * h));
}
m_curve->setSamples(x, y);
m_plot->replot();

После выполнения QwtPlot::replot все графики на поверхности будут перерисованы. Аналогичным образом в функциях плагинов формируются векторы графика визуализации решения:

double Left_rect_plugin::calc(const std::function<double (double)> &f,
const double a, const double b, const int n,
QVector<double> &x, QVector<double> &y) {
double h = (b - a) / n;
double s = 0;
for (int i = 0; i < n; ++i) {
x.push_back(a + i * h);
y.push_back(f(a + i * h));
y.push_back(f(a + i * h));
x.push_back(a + (i + 1) * h);
s += f(a + i * h);
}
return s * h;
}

3. Qt Script

От Qt Script в этой статье мы будем требовать только вычисление функции, заданной пользователем. Все возможные вычисления в Qt Script возложены на класс QScriptEngine. В качестве переменных используются экземпляры QScriptValue, каждый из которых имеет набор именованных изменяемых свойств.

Для задания вычисляемого выражения используется метод QScriptEngine::evaluate, принимающий строку (выражение) и возвращающий QScriptValue. Однако, в нашем примере должна быть создана функция, которая будет многократно вызываться. Наверняка это не самое элегантное решение (буду рад увидеть более красивые и правильные варианты в комментариях):

QScriptEngine engine;
QScriptValue scriptFun;
engine.evaluate(tr("function fun(x) { return ") + fun + "; }");
scriptFun = engine.globalObject().property("fun");
auto integfun = [&engine, &scriptFun](double x)->double {
return scriptFun.call(QScriptValue(), QScriptValueList() << x).toNumber();
};

В этом фрагменте, fun — строка с функцией, введенная пользователем. Строка приводится к формату функции, понятному QScriptEngine (1 строка). Созданная функция имеет имя «fun» и является глобальным свойством QScriptEngine. С этим свойством связывается переменная scriptFun (типа QScriptValue). Вычисление значения этой функции для заданного аргумента выполняется как показано в 5 строке. Однако, наши плагины не ожидают увидеть на входе QScriptValue — им нужен экземпляр std::function — поэтому вызов функции завернут в лямбда-функцию (integfun).

Исходный код проекта: использование Qwt, Qt Script, системы плагинов Qt

Список использованных источников

  1. статья про Qt Script (на русском языке) \ http://citforum.ru/programming/application/quickqt.shtml
  2. сайт, посвященный Qwt (на русском языке) \ http://qtlinux.narod.ru/install_qwt.htm
  3. Qwt на хабрахабре \ https://habrahabr.ru/post/82614/

One thought on “Cистема плагинов Qt, построение графиков и Qt Script

Добавить комментарий