File indexing completed on 2024-05-12 16:36:08

0001 /* This file is part of the KDE project
0002    Copyright 1999-2006 The KSpread Team <calligra-devel@kde.org>
0003 
0004    This library is free software; you can redistribute it and/or
0005    modify it under the terms of the GNU Library General Public
0006    License as published by the Free Software Foundation; either
0007    version 2 of the License, or (at your option) any later version.
0008 
0009    This library is distributed in the hope that it will be useful,
0010    but WITHOUT ANY WARRANTY; without even the implied warranty of
0011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0012    Library General Public License for more details.
0013 
0014    You should have received a copy of the GNU Library General Public License
0015    along with this library; see the file COPYING.LIB.  If not, write to
0016    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0017    Boston, MA 02110-1301, USA.
0018 */
0019 
0020 #include "FormulaEditorHighlighter.h"
0021 
0022 // Sheets
0023 #include "Formula.h"
0024 #include "Selection.h"
0025 #include "Sheet.h"
0026 
0027 // Qt
0028 #include <QApplication>
0029 #include <QTextEdit>
0030 
0031 using namespace Calligra::Sheets;
0032 
0033 class FormulaEditorHighlighter::Private
0034 {
0035 public:
0036     Private() {
0037         selection = 0;
0038         tokens = Tokens();
0039         rangeCount = 0;
0040         rangeChanged = false;
0041     }
0042 
0043     // source for cell reference checking
0044     Selection* selection;
0045     Tokens tokens;
0046     uint rangeCount;
0047     bool rangeChanged;
0048 };
0049 
0050 
0051 FormulaEditorHighlighter::FormulaEditorHighlighter(QTextEdit* textEdit, Selection* selection)
0052         : QSyntaxHighlighter(textEdit)
0053         , d(new Private)
0054 {
0055     d->selection = selection;
0056 }
0057 
0058 FormulaEditorHighlighter::~FormulaEditorHighlighter()
0059 {
0060     delete d;
0061 }
0062 
0063 const Tokens& FormulaEditorHighlighter::formulaTokens() const
0064 {
0065     return d->tokens;
0066 }
0067 
0068 void FormulaEditorHighlighter::highlightBlock(const QString& text)
0069 {
0070     // reset syntax highlighting
0071     setFormat(0, text.length(), QApplication::palette().text().color());
0072 
0073     // save the old ones to identify range changes
0074     Tokens oldTokens = d->tokens;
0075 
0076     // interpret the text as formula
0077     // we accept invalid/incomplete formulas
0078     Formula f;
0079     d->tokens = f.scan(text);
0080 
0081     QFont editorFont = document()->defaultFont();
0082     QFont font;
0083 
0084     uint oldRangeCount = d->rangeCount;
0085 
0086     d->rangeCount = 0;
0087     QList<QColor> colors = d->selection->colors();
0088     QList<QString> alreadyFoundRanges;
0089 
0090     Sheet *const originSheet = d->selection->originSheet();
0091     Map *const map = originSheet->map();
0092 
0093     for (int i = 0; i < d->tokens.count(); ++i) {
0094         Token token = d->tokens[i];
0095         Token::Type type = token.type();
0096 
0097         switch (type) {
0098         case Token::Cell:
0099         case Token::Range: {
0100             // don't compare, if we have already found a change
0101             if (!d->rangeChanged && i < oldTokens.count() && token.text() != oldTokens[i].text()) {
0102                 d->rangeChanged = true;
0103             }
0104 
0105             const Region newRange(token.text(), map, originSheet);
0106             if (!newRange.isValid()) {
0107                 continue;
0108             }
0109 
0110             int index = alreadyFoundRanges.indexOf(newRange.name());
0111             if (index == -1) { /* not found */
0112                 alreadyFoundRanges.append(newRange.name());
0113                 index = alreadyFoundRanges.count() - 1;
0114             }
0115             const QColor color(colors[index % colors.size()]);
0116             setFormat(token.pos() + 1, token.text().length(), color);
0117             ++d->rangeCount;
0118         }
0119         break;
0120         case Token::Boolean:     // True, False (also i18n-ized)
0121             /*        font = QFont(editorFont);
0122                     font.setBold(true);
0123                     setFormat(token.pos() + 1, token.text().length(), font);*/
0124             break;
0125         case Token::Identifier:   // function name or named area*/
0126             /*        font = QFont(editorFont);
0127                     font.setBold(true);
0128                     setFormat(token.pos() + 1, token.text().length(), font);*/
0129             break;
0130 
0131         case Token::Unknown:
0132         case Token::Integer:     // 14, 3, 1977
0133         case Token::Float:       // 3.141592, 1e10, 5.9e-7
0134         case Token::String:      // "Calligra", "The quick brown fox..."
0135         case Token::Error:
0136             break;
0137         case Token::Operator: {  // +, *, /, -
0138             switch (token.asOperator()) {
0139             case Token::LeftPar:
0140             case Token::RightPar:
0141                 //Check where this brace is in relation to the cursor and highlight it if necessary.
0142                 handleBrace(i);
0143                 break;
0144             default:
0145                 break;
0146             }
0147         }
0148         break;
0149         }
0150     }
0151 
0152     if (oldRangeCount != d->rangeCount)
0153         d->rangeChanged = true;
0154 }
0155 
0156 void FormulaEditorHighlighter::handleBrace(uint index)
0157 {
0158     const Token& token = d->tokens.at(index);
0159 
0160     QTextEdit* textEdit = qobject_cast<QTextEdit*>(parent());
0161     Q_ASSERT(textEdit);
0162     int cursorPos = textEdit->textCursor().position();
0163     int distance = cursorPos - token.pos();
0164     int opType = token.asOperator();
0165     bool highlightBrace = false;
0166 
0167     //Check where the cursor is in relation to this left or right parenthesis token.
0168     //Only one pair of braces should be highlighted at a time, and if the cursor
0169     //is between two braces, the inner-most pair should be highlighted.
0170 
0171     if (opType == Token::LeftPar) {
0172         //If cursor is directly to the left of this left brace, highlight it
0173         if (distance == 1)
0174             highlightBrace = true;
0175         else
0176             //Cursor is directly to the right of this left brace, highlight it unless
0177             //there is another left brace to the right (in which case that should be highlighted instead as it
0178             //is the inner-most brace)
0179             if (distance == 2)
0180                 if ((index == (uint)d->tokens.count() - 1) || (d->tokens.at(index + 1).asOperator() != Token::LeftPar))
0181                     highlightBrace = true;
0182 
0183     } else {
0184         //If cursor is directly to the right of this right brace, highlight it
0185         if (distance == 2)
0186             highlightBrace = true;
0187         else
0188             //Cursor is directly to the left of this right brace, so highlight it unless
0189             //there is another right brace to the left (in which case that should be highlighted instead as it
0190             //is the inner-most brace)
0191             if (distance == 1)
0192                 if ((index == 0) || (d->tokens.at(index - 1).asOperator() != Token::RightPar))
0193                     highlightBrace = true;
0194     }
0195 
0196     if (highlightBrace) {
0197         QFont font = QFont(document()->defaultFont());
0198         font.setBold(true);
0199         setFormat(token.pos() + 1, token.text().length(), font);
0200 
0201         int matching = findMatchingBrace(index);
0202 
0203         if (matching != -1) {
0204             Token matchingBrace = d->tokens.at(matching);
0205             setFormat(matchingBrace.pos() + 1 , matchingBrace.text().length() , font);
0206         }
0207     }
0208 }
0209 
0210 int FormulaEditorHighlighter::findMatchingBrace(int pos)
0211 {
0212     int depth = 0;
0213     int step = 0;
0214 
0215     Tokens tokens = d->tokens;
0216 
0217     //If this is a left brace we need to step forwards through the text to find the matching right brace,
0218     //otherwise, it is a right brace so we need to step backwards through the text to find the matching left
0219     //brace.
0220     if (tokens.at(pos).asOperator() == Token::LeftPar)
0221         step = 1;
0222     else
0223         step = -1;
0224 
0225     for (int index = pos ; (index >= 0) && (index < (int) tokens.count()) ; index += step) {
0226         if (tokens.at(index).asOperator() == Token::LeftPar)
0227             depth++;
0228         if (tokens.at(index).asOperator() == Token::RightPar)
0229             depth--;
0230 
0231         if (depth == 0) {
0232             return index;
0233         }
0234     }
0235 
0236     return -1;
0237 }
0238 
0239 uint FormulaEditorHighlighter::rangeCount() const
0240 {
0241     return d->rangeCount;
0242 }
0243 
0244 bool FormulaEditorHighlighter::rangeChanged() const
0245 {
0246     return d->rangeChanged;
0247 }
0248 
0249 void FormulaEditorHighlighter::resetRangeChanged()
0250 {
0251     d->rangeChanged = false;
0252 }