File indexing completed on 2024-05-05 03:48:22

0001 /*
0002     File             : EquationHighlighter.h
0003     Project          : LabPlot
0004     Description      : syntax highligher for mathematical equations
0005     --------------------------------------------------------------------
0006     SPDX-FileCopyrightText: 2014 Alexander Semke <alexander.semke@web.de>
0007     SPDX-FileCopyrightText: 2006 David Saxton <david@bluehaze.org>
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 
0011 #include "EquationHighlighter.h"
0012 #include "backend/gsl/ExpressionParser.h"
0013 
0014 #include <KTextEdit>
0015 #include <QPalette>
0016 
0017 EquationHighlighter::EquationHighlighter(KTextEdit* parent)
0018     : QSyntaxHighlighter(parent)
0019     , m_parent(parent)
0020 //  m_errorPosition = -1
0021 {
0022 }
0023 
0024 void EquationHighlighter::setVariables(const QStringList& variables) {
0025     m_variables = variables;
0026     rehighlight();
0027 }
0028 
0029 void EquationHighlighter::highlightBlock(const QString& text) {
0030     // TODO: m_parent->checkTextValidity();
0031 
0032     if (text.isEmpty())
0033         return;
0034 
0035     QTextCharFormat number;
0036     QTextCharFormat function;
0037     QTextCharFormat variable;
0038     QTextCharFormat constant;
0039     QTextCharFormat matchedParenthesis;
0040 
0041     QPalette palette;
0042     if (qGray(palette.color(QPalette::Base).rgb()) > 160) { // light
0043         number.setForeground(QColor(0, 0, 127));
0044         function.setForeground(QColor(85, 0, 0));
0045         function.setFontWeight(QFont::Bold);
0046         variable.setForeground(QColor(0, 85, 0));
0047         constant.setForeground(QColor(85, 0, 0));
0048         matchedParenthesis.setBackground(QColor(255, 255, 183));
0049     } else { // dark
0050         number.setForeground(QColor(160, 160, 255));
0051         function.setForeground(QColor(255, 160, 160));
0052         function.setFontWeight(QFont::Bold);
0053         variable.setForeground(QColor(160, 255, 160));
0054         constant.setForeground(QColor(255, 160, 160));
0055         matchedParenthesis.setBackground(QColor(85, 85, 0));
0056     }
0057 
0058     QTextCharFormat other;
0059 
0060     static const QStringList& functions = ExpressionParser::getInstance()->functions();
0061     static const QStringList& constants = ExpressionParser::getInstance()->constants();
0062 
0063     for (int i = 0; i < text.length(); ++i) {
0064         QString remaining = text.right(text.length() - i);
0065         bool found = false;
0066 
0067         // variables
0068         for (const QString& var : m_variables) {
0069             if (remaining.startsWith(var)) {
0070                 QString nextChar = remaining.mid(var.length(), 1);
0071                 if (nextChar == QLatin1Char(' ') || nextChar == QLatin1Char(')') || nextChar == QLatin1Char('+') || nextChar == QLatin1Char('-')
0072                     || nextChar == QLatin1Char('*') || nextChar == QLatin1Char('/') || nextChar == QLatin1Char('^')) {
0073                     setFormat(i, var.length(), variable);
0074                     i += var.length() - 1;
0075                     found = true;
0076                     break;
0077                 }
0078             }
0079         }
0080         if (found)
0081             continue;
0082 
0083         // functions
0084         for (const QString& f : functions) {
0085             if (remaining.startsWith(f)) {
0086                 setFormat(i, f.length(), function);
0087                 i += f.length() - 1;
0088                 found = true;
0089                 break;
0090             }
0091         }
0092         if (found)
0093             continue;
0094 
0095         // constants
0096         for (const QString& f : constants) {
0097             if (remaining.startsWith(f)) {
0098                 setFormat(i, f.length(), function);
0099                 i += f.length() - 1;
0100                 found = true;
0101                 break;
0102             }
0103         }
0104         if (found)
0105             continue;
0106 
0107         // TODO
0108         /*
0109         ushort u = text[i].unicode();
0110         bool isFraction = (u >= 0xbc && u <= 0xbe) || (u >= 0x2153 && u <= 0x215e);
0111         bool isPower = (u >= 0xb2 && u <= 0xb3) || (u == 0x2070) || (u >= 0x2074 && u <= 0x2079);
0112         bool isDigit = text[i].isDigit();
0113         bool isDecimalPoint = text[i] == QLocale().decimalPoint();
0114 
0115         if (isFraction || isPower || isDigit || isDecimalPoint)
0116             setFormat(i, 1, number);
0117         else
0118             setFormat(i, 1, other);
0119         */
0120     }
0121 
0122     // highlight matched brackets
0123     int cursorPos = m_parent->textCursor().position();
0124     if (cursorPos < 0)
0125         cursorPos = 0;
0126 
0127     // Adjust cursorpos to allow for a bracket before the cursor position
0128     if (cursorPos >= text.size())
0129         cursorPos = text.size() - 1;
0130     else if (cursorPos > 0 && (text[cursorPos - 1] == QLatin1Char('(') || text[cursorPos - 1] == QLatin1Char(')')))
0131         cursorPos--;
0132 
0133     bool haveOpen = text[cursorPos] == QLatin1Char('(');
0134     bool haveClose = text[cursorPos] == QLatin1Char(')');
0135 
0136     if ((haveOpen || haveClose) && m_parent->hasFocus()) {
0137         // Search for the other bracket
0138 
0139         int inc = haveOpen ? 1 : -1; // which direction to search in
0140 
0141         int level = 0;
0142         for (int i = cursorPos; i >= 0 && i < text.size(); i += inc) {
0143             if (text[i] == QLatin1Char(')'))
0144                 level--;
0145             else if (text[i] == QLatin1Char('('))
0146                 level++;
0147 
0148             if (level == 0) {
0149                 // Matched!
0150                 setFormat(cursorPos, 1, matchedParenthesis);
0151                 setFormat(i, 1, matchedParenthesis);
0152                 break;
0153             }
0154         }
0155     }
0156 
0157     // TODO: highlight the position of the error
0158     //  if (m_errorPosition != -1) {
0159     //      QTextCharFormat error;
0160     //      error.setForeground(Qt::red);
0161     //
0162     //      setFormat(m_errorPosition, 1, error);
0163     //  }
0164 }
0165 
0166 void EquationHighlighter::rehighlight() {
0167     setDocument(nullptr);
0168     setDocument(m_parent->document());
0169 }
0170 
0171 /**
0172  * This is used to indicate the position where the error occurred.
0173  * If \p position is negative, then no error will be shown.
0174  */
0175 // void EquationHighlighter::setErrorPosition(int position) {
0176 //  m_errorPosition = position;
0177 // }