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
m
  • molni99
  • 26 жовтня 2024 р. 01:37

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

  • Результат:80бали,
  • Рейтинг балів4
m
  • molni99
  • 26 жовтня 2024 р. 01:29

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

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

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

  • Результат:42бали,
  • Рейтинг балів-8
Останні коментарі
A
ALO1ZE19 жовтня 2024 р. 08:19
Читалка файлів fb3 на Qt Creator Подскажите как это запустить? Я не шарю в программировании и кодинге. Скачал и установаил Qt, но куча ошибок выдается и не запустить. А очень надо fb3 переконвертировать в html
ИМ
Игорь Максимов05 жовтня 2024 р. 07:51
Django - Урок 064. Як написати розширення для Python Markdown Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
d
dblas505 липня 2024 р. 11:02
QML - Урок 016. База даних SQLite та робота з нею в QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
k
kmssr08 лютого 2024 р. 18:43
Qt Linux - Урок 001. Автозапуск програми Qt під Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
АК
Анатолий Кононенко05 лютого 2024 р. 01:50
Qt WinAPI - Урок 007. Робота з ICMP Ping в Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
Тепер обговоріть на форумі
jd
jasmine disouza28 жовтня 2024 р. 04:58
GeForce Now India: Unlocking the Future of Cloud Gaming GeForce Now India has a major impact on the gaming scene by introducing NVIDIA's cloud gaming service to Indian gamers. GeForce Now India lets you stream top-notch PC games on any device, from b…
9
9Anonim25 жовтня 2024 р. 09:10
Машина тьюринга // Начальное состояние 0 0, ,<,1 // Переход в состояние 1 при пустом символе 0,0,>,0 // Остаемся в состоянии 0, двигаясь вправо при встрече 0 0,1,>…
J
JacobFib17 жовтня 2024 р. 03:27
добавить qlineseries в функции Пользователь может получить любые разъяснения по интересующим вопросам, касающимся обработки его персональных данных, обратившись к Оператору с помощью электронной почты https://topdecorpro.ru…
ИМ
Игорь Максимов03 жовтня 2024 р. 04:05
Реализация навигации по разделам Спасибо Евгений!
JW
Jhon Wick01 жовтня 2024 р. 15:52
Indian Food Restaurant In Columbus OH| Layla’s Kitchen Indian Restaurant If you're looking for a truly authentic https://www.laylaskitchenrestaurantohio.com/ , Layla’s Kitchen Indian Restaurant is your go-to destination. Located at 6152 Cleveland Ave, Colu…

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