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 }