Evgenii Legotckoi
Evgenii Legotckoi21 апреля 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\((? \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 хостинг.

Вам это нравится? Поделитесь в социальных сетях!

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь
e
  • ehot
  • 1 апреля 2024 г. 0:29

C++ - Тест 003. Условия и циклы

  • Результат:78баллов,
  • Очки рейтинга2
B

C++ - Тест 002. Константы

  • Результат:16баллов,
  • Очки рейтинга-10
B

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

  • Результат:46баллов,
  • Очки рейтинга-6
Последние комментарии
k
kmssr9 февраля 2024 г. 5:43
Qt Linux - Урок 001. Автозапуск Qt приложения под Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
АК
Анатолий Кононенко5 февраля 2024 г. 12:50
Qt WinAPI - Урок 007. Работаем с ICMP Ping в Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
EVA
EVA25 декабря 2023 г. 21:30
Boost - статическая линковка в CMake проекте под Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
J
JonnyJo25 декабря 2023 г. 19:38
Boost - статическая линковка в CMake проекте под Windows Сделал всё по-как у вас, но выдаёт ошибку [build] LINK : fatal error LNK1104: не удается открыть файл "libboost_locale-vc142-mt-gd-x64-1_74.lib" Хоть убей, не могу понять в чём дел…
G
Gvozdik19 декабря 2023 г. 8:01
Qt/C++ - Урок 056. Подключение библиотеки Boost в Qt для компиляторов MinGW и MSVC Для решения твой проблемы добавь в файл .pro строчку "LIBS += -lws2_32" она решит проблему , лично мне помогло.
Сейчас обсуждают на форуме
DA
Dr Gangil Academics20 апреля 2024 г. 17:45
Unlock Your Aesthetic Potential: Explore MSC in Facial Aesthetics and Cosmetology in India Embark on a transformative journey with an msc in facial aesthetics and cosmetology in india . Delve into the intricate world of beauty and rejuvenation, guided by expert faculty and …
a
a_vlasov14 апреля 2024 г. 16:41
Мобильное приложение на C++Qt и бэкенд к нему на Django Rest Framework Евгений, добрый день! Такой вопрос. Верно ли следующее утверждение: Любое Android-приложение, написанное на Java/Kotlin чисто теоретически (пусть и с большими трудностями) можно написать и на C+…
Павел Дорофеев
Павел Дорофеев14 апреля 2024 г. 12:35
QTableWidget с 2 заголовками Вот тут есть кастомный QTableView с многорядностью проект поддерживается, обращайтесь
f
fastrex4 апреля 2024 г. 14:47
Вернуть старое поведение QComboBox, не менять индекс при resetModel Добрый день! У нас много проектов в которых используется QComboBox, в версии 5.5.1, когда модель испускает сигнал resetModel, currentIndex не менялся. В версии 5.15 при resetModel происходит try…
P
Pisych27 февраля 2023 г. 15:04
Как получить в массив значения из связанной модели? Спасибо, разобрался:))

Следите за нами в социальных сетях