Евгений Легоцкой21 квітня 2018 р. 16:18

Qt/C++ - Урок 076. Визуализация математических формул на Qt

Недавно на форуме появился довольно интересный вопрос, о том, как можно сделать визуализацию формул в Qt. К сожалению я не имел возможности длительное время заниматься сайтом и форумом, но решил, что представить своё возможное видение проблемы даже спустя некоторое время, будет полезным.

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

Например, вы печатаете в некотором поле ввода sqrt(5), а в каком-то виджете будет выведено графическое отображение корня квадратного из 5, то есть не результат, а сама формула. Подобный функционал реализован в Latex и LibreOffice.

Выглядеть это будет следующим образом.

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

Основные положения

Для реализации подобного функционала требуется:

  • Используя регулярные выражения выделить запись формулы
  • Отобразить формулу на виджете

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

Для отрисовки формул создадим класс, наследованный от QWidget и переопределим его метод paintEvent(), который отвечает за отрисовку содержимого данного виджета.

Главное окно приложения

В качестве главного окна приложения будет использоваться класс, наследованный от QWidget. Вы выбираете его при создании проекта. Также он будет иметь файл графической формы, в котором нужно будет расположить интересующие нас виджеты:

  • QLineEdit - куда будем записывать формулу
  • FormulaWidget - где будет отображаться формула

В графическом редакторе необходимо будет добавить объект обычного виджета и с помощью контекстного меню преобразовать его в созданный предварительно класс FormulaWidget . В контекстном меню имеется для этого пункт "Преобразовать в..." или в английском варианте "Promote to...".

Касаться содержимого файла main.cpp не буду, поскольку всё создаётся по умолчанию.

widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();

private:
    Ui::Widget *ui;
};

#endif // WIDGET_H

widget.cpp

Для передачи набранного текста в виджеты формулы воспользуемся подключением сигнала textChanged у QLineEdit к слоту setFormula, который создадим в классе FormulaWidget .

#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    connect(ui->lineEdit, &QLineEdit::textChanged, ui->formulaWidget, &FormulaWidget::setFormula);
}

Widget::~Widget()
{
    delete ui;
}

Классы для отрисовки формул

А теперь рассмотрим сам класс для визуализации формул и класс самой формулы. Оба этих класса будут расположены в одном заголовочном файле ( FormulaWidget.h ) и файле реализации ( FormulaWidget.cpp ).

FormulaItem

FormulaWidget.h

// Класс визуализации формулы
class FormulaItem
{
public:
    explicit FormulaItem(QString value) : m_value(value){}

    static const QString REGULAR_EXPRESSION; // Строка регулярного выражения для поиска формулы
    // Метод отрисовки формулы
    QPoint draw(const QPoint& pos, QPainter& p) const;

private:
    QString m_value; // Значение формулы
};

FormulaWidget.cpp

const QString FormulaItem::REGULAR_EXPRESSION = "sqrt\\((?<value>\\d+)\\)";

QPoint FormulaItem::draw(const QPoint& pos, QPainter& p) const
{
    int valueWidth = p.fontMetrics().width(m_value);
    int valueHeight = p.fontMetrics().height();

    p.drawLine(pos.x(), 4 + valueHeight / 2, pos.x() + 5, 4 + valueHeight);
    p.drawLine(pos.x() + 5, 4 + valueHeight, pos.x() + 10, pos.y() + 1);
    p.drawLine(pos.x() + 10, pos.y() + 1, pos.x() + 14 + valueWidth, pos.y() + 1);

    p.drawText(QRect(pos.x() + 12, pos.y() + 4, pos.x() + 12 + valueWidth, pos.y() + 4 + valueHeight), m_value);
    return QPoint(pos.x() + valueWidth + 20, pos.y());
}

Статическая постоянная переменная REGULAR_EXPRESSION является строкой, которая содержит регулярное выражение для поиска вхождения формулы.

В Qt существует два класса для работы с регулярными выражениями:

  1. QRegExp - я использовал его в статье по написанию подсветки синтаксиса html разметки.
  2. QRegularExpression - по нему я статей ещё не писал, и это будет первой статьёй с его использованием

Первый класс был введён раньше, второй был введён только в Qt 5.0. Первый, как я понимаю, это собственная реализация регулярных выражений компании Qt. А второй класс - это уже реализация регулярных выражений с поддержкой синтаксиса исключительно языка Perl . На данный момент в документации рекомендуется ипользовать QRegularExpression , а для изучения синтаксиса регулярных выражений изучать документацию на Perl .

Что касается страшного метода draw , то его реализация отвечает за красивую отрисовку формулы с установкой положения значения внутрь квадратного корня. Там достаточно псевдоматематики для попиксельной отрисовки всех линий и чисел с наличием магических чисел. В реальных проектах старайтесь избегать такого и использовать именованные константы наподобие PADDING_BOTTOM, OFFSET и т.д.

Одним из важных моментов в методе draw является то, что объект QPainter должен передаваться по ссылке в качестве аргумента, но при этом ссылка не может быть константной, поскольку данный объект будет модифицироваться. Так что здесь всё в порядке. По своему опыту скажу, что это нормально для работы с отрисовкой кастомных виджетов, когда их QPainter передаётся по неконстантной ссылке в другие методы.

В приведённом регулярном выражении есть именованное захваченное значение, по которому мы можем извлечь значение, находящееся в корне, чтобы в дальнейшем отрисовывать его.

"sqrt\\((?<value>\\d+)\\)" - в данном регулярном выражении таким именем является value .

FormulaWidget

FormulaWidget.h

// Класс для отрисовки всех формул
class FormulaWidget : public QWidget
{
    Q_OBJECT
    using BaseClass = QWidget;
public:
    explicit FormulaWidget(QWidget* parent = nullptr);

public slots:
    // Слот для установки формулы
    void setFormula(const QString& formula);

protected:
    virtual void paintEvent(QPaintEvent* event) override;

private:
    QList<FormulaItem> m_items;
};

В классе присутствует два важных метода. Первый для установки строки, из которой будем извлекать формулы, а второй переопределённый метод paintEvent(). Переопределённый метод отвечает за отрисовку виджета, в нём будет также будем отрисовывать все формулы. Данный метод вызывается в стеке методов обработки событий в Qt. Напрямую данный метод никогда не вызывают, он вызывается при возникновении определённых событий, но чтобы его вызвать достаточно вызвать метод update().

FormulaWidget.cpp

FormulaWidget::FormulaWidget(QWidget* parent) :
    BaseClass(parent)
{
    // Установим цвет фона виджета, по умолчанию он такой же, как в системном оформлении ОС
    QPalette pal = palette();
    pal.setColor(QPalette::Background, Qt::white);
    setAutoFillBackground(true);
    setPalette(pal);
}

void FormulaWidget::setFormula(const QString& formula)
{
    // Очищаем все формулы
    m_items.clear();

    // Создаём объект регулярного выражения для поиска формулы
    QRegularExpression sqrt_value(FormulaItem::REGULAR_EXPRESSION);
    // Ищем все вхождения формулы
    QRegularExpressionMatchIterator i = sqrt_value.globalMatch(formula);

    // создаём все объекты формул
    while (i.hasNext())
    {
        QRegularExpressionMatch match = i.next();
        if (match.hasMatch())
        {
            m_items.append(FormulaItem(match.captured("value")));
        }
    }

    // Запускаем перерисовку
    update();
}

void FormulaWidget::paintEvent(QPaintEvent* event)
{
    // Для перерисовки используется объект QPainter,
    // который обязательно должен получить объект за отрисовку которого он отвечает
    QPainter p(this);
    p.setRenderHint(QPainter::Antialiasing);
    p.setPen(Qt::black);

    QPoint formulaPos(2, 2);

    // Производим отрисовку всех формул, которые удалось найти
    for (const FormulaItem& item : m_items)
    {
        formulaPos = item.draw(formulaPos, p);
    }
}

В результате работы данного кода будет получено приложение, представленное на скриншоте в начале статьи.

Также прилагаю код проекта. Скачать

Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.
Підтримайте автора Donate

Коментарі

Only authorized users can post comments.
Please, Log in or Sign up
Timeweb

Дозвольте порекомендувати вам чудовий хостинг, на якому розташований EVILEG.

Протягом багатьох років Timeweb доводив свою стабільність.

Для проектів на Django рекомендую VDS хостинг

Переглянути хостинг
СП

C++ - Тест 001. Первая программа и типы данных

  • Результат:93бали,
  • Рейтинг балів8
VS

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:30бали,
  • Рейтинг балів-10
J

C++ - Тест 001. Первая программа и типы данных

  • Результат:93бали,
  • Рейтинг балів8
Останні коментарі

Qt/C++ - Урок 074. Генерація псевдовипадкових чисел, використання STD бібліотеки random

А использование функции global() не решает ли эти проблемы? value = QRandomGenerator::global()->bounded(15, 43); Получаемая последовательность каждый раз новая.

Qt/C++ - Урок 074. Генерація псевдовипадкових чисел, використання STD бібліотеки random

А использование функции global() не решает ли эти проблемы? value = QRandomGenerator::global()->bounded(15, 43); Получаемая последовательность каждый раз новая.
S

QML - Урок 026. Intents с Qt для Android, часть 1

Есть ли возможность приведения java типа у QAndroidJniObject? Интересует конкретно class to
ВК

Qt / C ++ - Урок 015. QTableWidget або Як зробити таблицю з чекбоксами

Кто-нибудь знает, как сделать так, чтобы в QTableWidget состоящей из чекбоксов в строке таблицы можно было выбрать только один checkbox ?

Qt/C++ - Урок 006. QSqlQueryModel - Таблицы в Qt с помощью SQL-запросов

QSqlTableModel выполняет ряд стандартных операций для одной таблицы из базы данных. Поэтому там и реализован функционал по удалению и редактированию. QSqlQueryModel позволяет выполнить запр…
Тепер обговоріть на форумі
  • Nomad
  • 01 жовтня 2020 р. 05:22

MyForm(forms.Form): - непонятка

понятно спасибо
S

QWebView android

На android не запускается, иначе я бы не создавал этот пост. Собственно, вопрос я решил сам, там ещё понадобилось setDomStorageEnabled(true) вызвать.

не могу передать стринг с QLineEdit

QLineEdit *myLineEdit = new QLineEdit("line edit name", this); QString str = myLineEdit->text();

Siganal slot в ui

Добрый день, Не совсем понял, какой код должен находиться в слоте, но можно подключиться через лямбда функцию. connect(timer, &QTimer::timeout, this, [](){ ui->scrollArea-&g;…
VB

Лишние строки при выборке из базы данных SQLite

Ну вообще совсем без знания документации трудно что-либо написать. Я просмотрел видеокурс по моделям в Qt, несколько видеоуроков. Немного почитал документацию. Понятно, что где-то не понятно как…
Про
Послуги
© EVILEG 2015-2020
Рекомендуємо хостинг TIMEWEB