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

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