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
АФ

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

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

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

  • Результат:80бали,
  • Рейтинг балів4
d

C++ - Тест 005. Структуры и Классы

  • Результат:50бали,
  • Рейтинг балів-4
Останні коментарі
k
kmssr09 лютого 2024 р. 02:43
Qt Linux - Урок 001. Автозапуск програми Qt під Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
АК
Анатолий Кононенко05 лютого 2024 р. 09:50
Qt WinAPI - Урок 007. Робота з ICMP Ping в Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
EVA
EVA25 грудня 2023 р. 18:30
Boost - статичне зв&#39;язування в проекті CMake під Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
J
JonnyJo25 грудня 2023 р. 16:38
Boost - статичне зв&#39;язування в проекті CMake під Windows Сделал всё по-как у вас, но выдаёт ошибку [build] LINK : fatal error LNK1104: не удается открыть файл "libboost_locale-vc142-mt-gd-x64-1_74.lib" Хоть убей, не могу понять в чём дел…
G
Gvozdik19 грудня 2023 р. 05:01
Qt/C++ - Урок 056. Підключення бібліотеки Boost в Qt для компіляторів MinGW і MSVC Для решения твой проблемы добавь в файл .pro строчку "LIBS += -lws2_32" она решит проблему , лично мне помогло.
Тепер обговоріть на форумі
P
Pisych27 лютого 2023 р. 12:04
Как получить в массив значения из связанной модели? Спасибо, разобрался:))
AC
Alexandru Codreanu19 січня 2024 р. 19:57
QML Обнулить значения SpinBox Доброго времени суток, не могу разобраться с обнулением значение SpinBox находящего в делегате. import QtQuickimport QtQuick.ControlsWindow { width: 640 height: 480 visible: tr…
BlinCT
BlinCT27 грудня 2023 р. 16:57
Растягивать Image на парент по высоте Ну и само собою дял включения scrollbar надо чтобы был Flickable. Так что выходит как то так Flickable{ id: root anchors.fill: parent clip: true property url linkFile p…
Дмитрий
Дмитрий10 січня 2024 р. 12:18
Qt Creator загружает всю оперативную память Проблема решена. Удалось разобраться с помощью утилиты strace. Запустил ее: strace ./qtcreator Начал выводиться весь лог работы креатора. В один момент он начал считывать фай…
Evgenii Legotckoi
Evgenii Legotckoi12 грудня 2023 р. 14:48
Побуквенное сравнение двух строк Добрый день. Там случайно не высылается этот сигнал textChanged ещё и при форматировани текста? Если решиать в лоб, то можно просто отключать сигнал/слотовое соединение внутри слота и …

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