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
OI
  • Ora Iro
  • 24 грудня 2024 р. 06:38

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

  • Результат:40бали,
  • Рейтинг балів-8
AD

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

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

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

  • Результат:80бали,
  • Рейтинг балів4
Останні коментарі
ИМ
Игорь Максимов22 листопада 2024 р. 11:51
Django - Підручник 017. Налаштуйте сторінку входу до Django Добрый вечер Евгений! Я сделал себе авторизацию аналогичную вашей, все работает, кроме возврата к предидущей странице. Редеректит всегда на главную, хотя в логах сервера вижу запросы на правильн…
Evgenii Legotckoi
Evgenii Legotckoi31 жовтня 2024 р. 14:37
Django - Урок 064. Як написати розширення для Python Markdown Добрый день. Да, можно. Либо через такие же плагины, либо с постобработкой через python библиотеку Beautiful Soup
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 аналогично. Могу предположить, что из-за более новой верси…
Тепер обговоріть на форумі
Donald Randolph
Donald Randolph30 грудня 2024 р. 02:59
Personal Injury lawyer Santa Monica As an experienced Santa Monica personal injury lawyer, Donald C. Randolph has recovered over $100 Million in verdicts and settlements for our clients. In severe injury cases, this compensation i…
Nirvana Yoga School
Nirvana Yoga School30 грудня 2024 р. 05:13
OAuth2.0 через VK, получение email Nirvana Yoga School is one of the most trusted and reputed traditional Rishikesh yoga courses , India certified by Yoga Alliance, USA. We aim to spread traditional yoga teachings so t…
s
sripark30 грудня 2024 р. 04:47
Mobile app development company in Chennai A Mobile app development company in Chennai focuses on creating personalized mobile applications to meet various business requirements. These companies offer a full range of services,…
a
amit8830 грудня 2024 р. 04:45
Excel in Exams with PSLE Maths Tuition Singapore Preparing for the PSLE can be challenging, but the right guidance makes all the difference. PSLE Maths tuition Singapore offers personalized coaching to help students master key concepts, improv…
a
awinash6230 грудня 2024 р. 04:23
Unlock Your Potential with the Certified Public Accountant Credential" Becoming a Certified Public Accountant (CPA) is a career milestone that opens doors to unparalleled opportunities in the world of accounting and finance. This globally recognized qualification s…

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