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 не буду, оскільки все створюється за замовчуванням.

віджет.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.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 хостинг.

Вам це подобається? Поділіться в соціальних мережах!

Коментарі

Only authorized users can post comments.
Please, Log in or Sign up
d
  • dsfs
  • 26 квітня 2024 р. 14:56

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

  • Результат:80бали,
  • Рейтинг балів4
d
  • dsfs
  • 26 квітня 2024 р. 14:45

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

  • Результат:50бали,
  • Рейтинг балів-4
d
  • dsfs
  • 26 квітня 2024 р. 14:35

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

  • Результат:73бали,
  • Рейтинг балів1
Останні коментарі
k
kmssr09 лютого 2024 р. 05:43
Qt Linux - Урок 001. Автозапуск програми Qt під Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
АК
Анатолий Кононенко05 лютого 2024 р. 12:50
Qt WinAPI - Урок 007. Робота з ICMP Ping в Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
EVA
EVA25 грудня 2023 р. 21:30
Boost - статичне зв&#39;язування в проекті CMake під Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
J
JonnyJo25 грудня 2023 р. 19:38
Boost - статичне зв&#39;язування в проекті CMake під Windows Сделал всё по-как у вас, но выдаёт ошибку [build] LINK : fatal error LNK1104: не удается открыть файл "libboost_locale-vc142-mt-gd-x64-1_74.lib" Хоть убей, не могу понять в чём дел…
G
Gvozdik19 грудня 2023 р. 08:01
Qt/C++ - Урок 056. Підключення бібліотеки Boost в Qt для компіляторів MinGW і MSVC Для решения твой проблемы добавь в файл .pro строчку "LIBS += -lws2_32" она решит проблему , лично мне помогло.
Тепер обговоріть на форумі
G
Gar22 квітня 2024 р. 15:46
Clipboard Как скопировать окно целиком в clipb?
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
fastrex04 квітня 2024 р. 14:47
Вернуть старое поведение QComboBox, не менять индекс при resetModel Добрый день! У нас много проектов в которых используется QComboBox, в версии 5.5.1, когда модель испускает сигнал resetModel, currentIndex не менялся. В версии 5.15 при resetModel происходит try…

Слідкуйте за нами в соціальних мережах