void Editor::autoComplete( const QString& item ) { if( !d->autoCompleteEnabled || item.isEmpty() ) return; int para = 0, curPos = 0; getCursorPosition( ¶, &curPos ); QString subtext = text().left( curPos ); Tokens tokens = Evaluator::scan( subtext ); if( !tokens.valid() || tokens.count() < 1 ) return; Token lastToken = tokens[ tokens.count()-1 ]; if( !lastToken.isIdentifier() ) return; QStringList str = QStringList::split( ':', item ); blockSignals( true ); setSelection( 0, lastToken.pos(), 0, lastToken.pos()+lastToken.text().length() ); insert( str[0] ); blockSignals( false ); }
void Editor::doMatchingRight() { // Tokenize the expression. const int currentPosition = textCursor().position(); // Check for left par. QString subtext = text().right(text().length() - currentPosition); Tokens tokens = m_evaluator->scan(subtext); if (!tokens.valid() || tokens.count() < 1) return; Token firstToken = tokens.at(0); // Left par? if (firstToken.type() == Token::stxOpenPar && firstToken.pos() == 0) { // Find the matching right par. unsigned par = 1; int k = 0; Token matchToken; int matchPosition = -1; for (k = 1; k < tokens.count() && par > 0; ++k) { const Token matchToken = tokens.at(k); switch (matchToken.type()) { case Token::stxOpenPar : ++par; break; case Token::stxClosePar: --par; break; default:; } matchPosition = matchToken.pos(); } if (par == 0) { QTextEdit::ExtraSelection hilite1; hilite1.cursor = textCursor(); hilite1.cursor.setPosition(currentPosition+matchPosition); hilite1.cursor.setPosition(currentPosition+matchPosition + 1, QTextCursor::KeepAnchor); hilite1.format.setBackground(m_highlighter->colorForRole(SyntaxHighlighter::Matched)); QTextEdit::ExtraSelection hilite2; hilite2.cursor = textCursor(); hilite2.cursor.setPosition(currentPosition+firstToken.pos()); hilite2.cursor.setPosition(currentPosition+firstToken.pos() + 1, QTextCursor::KeepAnchor); hilite2.format.setBackground(m_highlighter->colorForRole(SyntaxHighlighter::Matched)); QList<QTextEdit::ExtraSelection> extras; extras << hilite1; extras << hilite2; setExtraSelections(extras); } } }
void Editor::doMatchingRight() { if( !d->syntaxHighlightEnabled ) return; // tokenize the expression int para = 0, curPos = 0; getCursorPosition( ¶, &curPos ); // check for left par QString subtext = text().right( text().length() - curPos ); Tokens tokens = Evaluator::scan( subtext ); if( !tokens.valid() ) return; if( tokens.count()<1 ) return; Token firstToken = tokens[ 0 ]; // left par ? if( firstToken.isOperator() ) if( firstToken.asOperator() == Token::LeftPar ) if( firstToken.pos() == 0 ) { // find the matching right par unsigned par = 1; unsigned int k = 0; Token matchToken; int matchPos = -1; for( k = 1; k < tokens.count(); k++ ) { if( par < 1 ) break; Token matchToken = tokens[k]; if( matchToken.isOperator() ) { if( matchToken.asOperator() == Token::LeftPar ) par++; if( matchToken.asOperator() == Token::RightPar ) par--; if( par == 0 ) matchPos = matchToken.pos(); } } if( matchPos >= 0 ) { setSelection( 0, curPos+matchPos, 0, curPos+matchPos+1, 2 ); setSelection( 0, curPos+firstToken.pos(), 0, curPos+firstToken.pos()+1, 1 ); setCursorPosition( para, curPos ); } } }
void Editor::doMatchingLeft() { // Tokenize the expression. const int currentPosition = textCursor().position(); // Check for right par. QString subtext = text().left(currentPosition); Tokens tokens = m_evaluator->scan(subtext, Evaluator::NoAutoFix); if (!tokens.valid() || tokens.count() < 1) return; Token lastToken = tokens.at(tokens.count() - 1); // Right par? if (lastToken.type() == Token::stxClosePar && lastToken.pos() == currentPosition - 1) { // Find the matching left par. unsigned par = 1; int matchPosition = -1; for (int i = tokens.count() - 2; i >= 0 && par > 0; --i) { Token matchToken = tokens.at(i); switch (matchToken.type()) { case Token::stxOpenPar : --par; break; case Token::stxClosePar: ++par; break; default:; } matchPosition = matchToken.pos(); } if (par == 0) { QTextEdit::ExtraSelection hilite1; hilite1.cursor = textCursor(); hilite1.cursor.setPosition(matchPosition); hilite1.cursor.setPosition(matchPosition + 1, QTextCursor::KeepAnchor); hilite1.format.setBackground(m_highlighter->colorForRole(SyntaxHighlighter::Matched)); QTextEdit::ExtraSelection hilite2; hilite2.cursor = textCursor(); hilite2.cursor.setPosition(lastToken.pos()); hilite2.cursor.setPosition(lastToken.pos() + 1, QTextCursor::KeepAnchor); hilite2.format.setBackground(m_highlighter->colorForRole(SyntaxHighlighter::Matched)); QList<QTextEdit::ExtraSelection> extras; extras << hilite1; extras << hilite2; setExtraSelections(extras); } } }
// Called by the cell tool when setting the active element with a cell location. void CellEditor::setActiveSubRegion(int index) { index = qBound(0, index, (int)d->highlighter->rangeCount()); int counter = 0; bool subRegion = false; const Tokens tokens = d->highlighter->formulaTokens(); for (int i = 0; i < tokens.count(); ++i) { const Token token = tokens[i]; switch (token.type()) { case Token::Cell: case Token::Range: if (!subRegion) { d->currentToken = i; subRegion = true; } if (counter == index) { setCursorPosition(token.pos() + token.text().length() + 1); return; } ++counter; continue; case Token::Operator: if (token.asOperator() == Token::Semicolon) { if (subRegion) { continue; } } subRegion = false; continue; default: subRegion = false; continue; } } }
int FormulaEditorHighlighter::findMatchingBrace(int pos) { int depth = 0; int step = 0; Tokens tokens = d->tokens; //If this is a left brace we need to step forwards through the text to find the matching right brace, //otherwise, it is a right brace so we need to step backwards through the text to find the matching left //brace. if (tokens.at(pos).asOperator() == Token::LeftPar) step = 1; else step = -1; for (int index = pos ; (index >= 0) && (index < (int) tokens.count()) ; index += step) { if (tokens.at(index).asOperator() == Token::LeftPar) depth++; if (tokens.at(index).asOperator() == Token::RightPar) depth--; if (depth == 0) { return index; } } return -1; }
void SyntaxHighlighter::highlightBlock(const QString& text) { if (!Settings::instance()->syntaxHighlighting) { setFormat(0, text.length(), colorForRole(Number)); return; } if (text.startsWith(QLatin1String("="))) { setFormat(0, 1, colorForRole(Operator)); setFormat(1, text.length(), colorForRole(Result)); groupDigits(text, 1, text.length() - 1); return; } Tokens tokens = Evaluator::instance()->scan(text); for (int i = 0; i < tokens.count(); ++i) { const Token& token = tokens.at(i); const QString tokenText = token.text().toLower(); QStringList functionNames = FunctionRepo::instance()->getIdentifiers(); QColor color; switch (token.type()) { case Token::stxNumber: case Token::stxUnknown: color = colorForRole(Number); break; case Token::stxOperator: color = colorForRole(Operator); break; case Token::stxSep: color = colorForRole(Separator); break; case Token::stxOpenPar: case Token::stxClosePar: color = colorForRole(Parens); break; case Token::stxIdentifier: color = colorForRole(Variable); for (int i = 0; i < functionNames.count(); ++i) if (functionNames.at(i).toLower() == tokenText) color = colorForRole(Function); break; default: break; }; if (token.pos() >= 0) { setFormat(token.pos(), token.text().length(), color); if (token.type() == Token::stxNumber) groupDigits(text, token.pos(), token.text().length()); } } }
void Editor::autoComplete(const QString& item) { if (!m_isAutoCompletionEnabled || item.isEmpty()) return; const int currentPosition = textCursor().position(); const QString subtext = text().left(currentPosition); const Tokens tokens = m_evaluator->scan(subtext); if (!tokens.valid() || tokens.count() < 1) return; const Token lastToken = tokens.at(tokens.count() - 1); if (!lastToken.isIdentifier()) return; const QStringList str = item.split(':'); blockSignals(true); QTextCursor cursor = textCursor(); cursor.setPosition(lastToken.pos()); cursor.setPosition(lastToken.pos() + lastToken.text().length(), QTextCursor::KeepAnchor); setTextCursor(cursor); insert(str.at(0)); blockSignals(false); cursor = textCursor(); bool hasParensAlready; if ((hasParensAlready = cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor))) { QString nextChar = cursor.selectedText(); hasParensAlready = (nextChar == "("); } bool shouldAutoInsertParens = (FunctionRepo::instance()->find(str.at(0)) || m_evaluator->hasUserFunction(str.at(0))) && !hasParensAlready; if (shouldAutoInsertParens) { insert(QString::fromLatin1("()")); cursor = textCursor(); cursor.movePosition(QTextCursor::PreviousCharacter); setTextCursor(cursor); } }
void CellEditor::Private::rebuildSelection() { // Do not react on selection changes, that update the formula's expression, // because the selection gets already build based on the current formula. selectionChangedLocked = true; Sheet *const originSheet = selection->originSheet(); Map *const map = originSheet->map(); // Rebuild the reference selection by using the formula tokens. Tokens tokens = highlighter->formulaTokens(); selection->update(); // set the old cursor dirty; updates the editors selection->clear(); //A list of regions which have already been highlighted on the spreadsheet. //This is so that we don't end up highlighting the same region twice in two different //colors. QSet<QString> alreadyUsedRegions; int counter = 0; for (int i = 0; i < tokens.count(); ++i) { const Token token = tokens[i]; const Token::Type type = token.type(); if (type == Token::Cell || type == Token::Range) { const Region region(token.text(), map, originSheet); if (!region.isValid() || region.isEmpty()) { continue; } if (alreadyUsedRegions.contains(region.name())) { continue; } alreadyUsedRegions.insert(region.name()); const QRect range = region.firstRange(); Sheet *const sheet = region.firstSheet(); selection->initialize(range, sheet); // Always append the next range by pointing behind the last item. selection->setActiveSubRegion(++counter, 0); } } // Set the active sub-region. // Needs up-to-date tokens; QSyntaxHighlighter::rehighlight() gets called // automatically on text changes, which does the update. updateActiveSubRegion(highlighter->formulaTokens()); selectionChangedLocked = false; }
void Editor::autoCalcSelection() { if (!m_isAutoCalcEnabled) return; const QString str = m_evaluator->autoFix(textCursor().selectedText()); if (str.isEmpty()) return; // Very short (just one token) and still no calculation, then skip. if (!m_isAnsAvailable) { const Tokens tokens = m_evaluator->scan(text()); if (tokens.count() < 2) return; } // Too short even after autofix? Don't bother either. const Tokens tokens = m_evaluator->scan(str); if (tokens.count() < 2) return; // Same reason as above, do not update "ans". m_evaluator->setExpression(str); const HNumber num = m_evaluator->evalNoAssign(); if (m_evaluator->error().isEmpty()) { if (num.isNan() && m_evaluator->isUserFunctionAssign()) { // Result is not always available when assigning a user function. const QString message = tr("Selection result: n/a"); emit autoCalcEnabled(message); } else { const QString message = tr("Selection result: <b>%1</b>").arg(NumberFormatter::format(num)); emit autoCalcEnabled(message); } } else emit autoCalcEnabled(m_evaluator->error()); }
int EditorHighlighter::highlightParagraph ( const QString & text, int ) { if( !editor->isSyntaxHighlightEnabled() ) { setFormat( 0, text.length(), editor->colorGroup().text() ); return 0; } QStringList fnames = FunctionManager::instance()->functionList(FunctionManager::All); fnames.sort(); // Sort list so we can bin search it. Tokens tokens = Evaluator::scan( text ); for( unsigned i = 0; i < tokens.count(); i++ ) { Token& token = tokens[i]; QString text = token.text().lower(); QFont font = editor->font(); QColor color = Qt::black; switch( token.type() ) { case Token::Number: color = editor->highlightColor( Editor::Number ); break; case Token::Identifier: { color = editor->highlightColor( Editor::Variable ); // XXX: QT 4: Replace this with qBinaryFind(). if( fnames.contains( text ) ) { color = editor->highlightColor( Editor::FunctionName ); } } break; case Token::Operator: break; default: break; } if( token.pos() >= 0 ) { setFormat( token.pos(), token.text().length(), font, color ); } } return 0; }
static QString tokenizeFormula(const QString& formula) { Formula f; QString expr = formula; expr.prepend( '=' ); f.setExpression( expr ); Tokens tokens = f.tokens(); QString resultCodes; if( tokens.valid() ) for( int i = 0; i < tokens.count(); i++ ) resultCodes.append( encodeTokenType( tokens[i] ) ); return resultCodes; }
int ClangFunctionHintModel::activeArgument(const QString &prefix) const { int argnr = 0; int parcount = 0; SimpleLexer tokenize; Tokens tokens = tokenize(prefix); for (int i = 0; i < tokens.count(); ++i) { const CPlusPlus::Token &tk = tokens.at(i); if (tk.is(T_LPAREN)) ++parcount; else if (tk.is(T_RPAREN)) --parcount; else if (! parcount && tk.is(T_COMMA)) ++argnr; } if (parcount < 0) return -1; if (argnr != m_currentArg) m_currentArg = argnr; return argnr; }
void FormulaEditorHighlighter::highlightBlock(const QString& text) { // reset syntax highlighting setFormat(0, text.length(), QApplication::palette().text().color()); // save the old ones to identify range changes Tokens oldTokens = d->tokens; // interpret the text as formula // we accept invalid/incomplete formulas Formula f; d->tokens = f.scan(text); QFont editorFont = document()->defaultFont(); QFont font; uint oldRangeCount = d->rangeCount; d->rangeCount = 0; QList<QColor> colors = d->selection->colors(); QList<QString> alreadyFoundRanges; Sheet *const originSheet = d->selection->originSheet(); Map *const map = originSheet->map(); for (int i = 0; i < d->tokens.count(); ++i) { Token token = d->tokens[i]; Token::Type type = token.type(); switch (type) { case Token::Cell: case Token::Range: { // don't compare, if we have already found a change if (!d->rangeChanged && i < oldTokens.count() && token.text() != oldTokens[i].text()) { d->rangeChanged = true; } const Region newRange(token.text(), map, originSheet); if (!newRange.isValid()) { continue; } int index = alreadyFoundRanges.indexOf(newRange.name()); if (index == -1) { /* not found */ alreadyFoundRanges.append(newRange.name()); index = alreadyFoundRanges.count() - 1; } const QColor color(colors[index % colors.size()]); setFormat(token.pos() + 1, token.text().length(), color); ++d->rangeCount; } break; case Token::Boolean: // True, False (also i18n-ized) /* font = QFont(editorFont); font.setBold(true); setFormat(token.pos() + 1, token.text().length(), font);*/ break; case Token::Identifier: // function name or named area*/ /* font = QFont(editorFont); font.setBold(true); setFormat(token.pos() + 1, token.text().length(), font);*/ break; case Token::Unknown: case Token::Integer: // 14, 3, 1977 case Token::Float: // 3.141592, 1e10, 5.9e-7 case Token::String: // "Calligra", "The quick brown fox..." case Token::Error: break; case Token::Operator: { // +, *, /, - switch (token.asOperator()) { case Token::LeftPar: case Token::RightPar: //Check where this brace is in relation to the cursor and highlight it if necessary. handleBrace(i); break; default: break; } } break; } } if (oldRangeCount != d->rangeCount) d->rangeChanged = true; }
// Called on selection (and sheet) changes. void CellEditor::selectionChanged() { if (d->selectionChangedLocked) { return; } Selection* choice = selection(); if (choice->isEmpty()) return; const QString text = toPlainText(); const int textLength = text.length(); // Find the start text cursor position for the active sub-region within // the formula's expression and determine the length of the sub-region. Tokens tokens = d->highlighter->formulaTokens(); uint start = 1; uint length = 0; if (!tokens.empty()) { if (d->currentToken < tokens.count()) { Token token = tokens[d->currentToken]; Token::Type type = token.type(); if (type == Token::Cell || type == Token::Range) { start = token.pos() + 1; // don't forget the '='! length = token.text().length(); // Iterate to the end of the sub-region. for (int i = d->currentToken + 1; i < tokens.count(); ++i) { token = tokens[i]; type = token.type(); switch (type) { case Token::Cell: case Token::Range: length += token.text().length(); continue; case Token::Operator: if (token.asOperator() == Token::Semicolon) { ++length; continue; } default: break; } break; } } else { start = token.pos() + 1; // don't forget the '='! length = token.text().length(); } } else { // sanitize d->currentToken = tokens.count(); start = textLength; } } // Replace the formula's active sub-region with the selection's one. const QString address = choice->activeSubRegionName(); const QString newExpression = QString(text).replace(start, length, address); // The expression highlighting gets updated automatically by the next call, // even though signals are blocked (must be connected to QTextDocument). blockSignals(true); setText(newExpression, start + address.length()); blockSignals(false); // Ranges have changed. // Reset the flag, that indicates range changes after text changes. d->highlighter->resetRangeChanged(); // Mirror the behaviour of slotCursorPositionChanged(), but here the tokens // are already up-to-date. d->globalCursorPos = mapToGlobal(cursorRect().bottomLeft()); // Set the active sub-region. // Needs up-to-date tokens; QSyntaxHighlighter::rehighlight() gets called // automatically on text changes, which does the update. d->updateActiveSubRegion(d->highlighter->formulaTokens()); // Always emit, because this editor may be hidden or does not have focus, // but the external one needs an update. emit textChanged(toPlainText()); }
bool Conditions::isTrueFormula(const Cell &cell, const QString &formula, const QString &baseCellAddress) const { Map* const map = cell.sheet()->map(); ValueCalc *const calc = map->calc(); Formula f(cell.sheet(), cell); f.setExpression('=' + formula); Region r(baseCellAddress, map, cell.sheet()); if (r.isValid() && r.isSingular()) { QPoint basePoint = static_cast<Region::Point*>(*r.constBegin())->pos(); QString newFormula('='); const Tokens tokens = f.tokens(); for (int t = 0; t < tokens.count(); ++t) { const Token token = tokens[t]; if (token.type() == Token::Cell || token.type() == Token::Range) { if (map->namedAreaManager()->contains(token.text())) { newFormula.append(token.text()); continue; } const Region region(token.text(), map, cell.sheet()); if (!region.isValid() || !region.isContiguous()) { newFormula.append(token.text()); continue; } if (region.firstSheet() != r.firstSheet()) { newFormula.append(token.text()); continue; } Region::Element* element = *region.constBegin(); if (element->type() == Region::Element::Point) { Region::Point* point = static_cast<Region::Point*>(element); QPoint pos = point->pos(); if (!point->isRowFixed()) { int delta = pos.y() - basePoint.y(); pos.setY(cell.row() + delta); } if (!point->isColumnFixed()) { int delta = pos.x() - basePoint.x(); pos.setX(cell.column() + delta); } newFormula.append(Region(pos, cell.sheet()).name()); } else { Region::Range* range = static_cast<Region::Range*>(element); QRect r = range->rect(); if (!range->isTopFixed()) { int delta = r.top() - basePoint.y(); r.setTop(cell.row() + delta); } if (!range->isBottomFixed()) { int delta = r.bottom() - basePoint.y(); r.setBottom(cell.row() + delta); } if (!range->isLeftFixed()) { int delta = r.left() - basePoint.x(); r.setLeft(cell.column() + delta); } if (!range->isRightFixed()) { int delta = r.right() - basePoint.x(); r.setRight(cell.column() + delta); } newFormula.append(Region(r, cell.sheet()).name()); } } else { newFormula.append(token.text()); } } f.setExpression(newFormula); } Value val = f.eval(); return calc->conv()->asBoolean(val).asBoolean(); }
QString Calligra::Sheets::Odf::encodeFormula(const QString& expr, const KLocale* locale) { // use locale settings const QString decimal = locale ? locale->decimalSymbol() : "."; QString result('='); Formula formula; Tokens tokens = formula.scan(expr, locale); if (!tokens.valid() || tokens.count() == 0) return expr; // no altering on error for (int i = 0; i < tokens.count(); ++i) { const QString tokenText = tokens[i].text(); const Token::Type type = tokens[i].type(); switch (type) { case Token::Cell: case Token::Range: { result.append('['); // FIXME Stefan: Hack to get the apostrophes right. Fix and remove! const int pos = tokenText.lastIndexOf('!'); if (pos != -1 && tokenText.left(pos).contains(' ')) result.append(Region::saveOdf('\'' + tokenText.left(pos) + '\'' + tokenText.mid(pos))); else result.append(Region::saveOdf(tokenText)); result.append(']'); break; } case Token::Float: { QString tmp(tokenText); result.append(tmp.replace(decimal, ".")); break; } case Token::Operator: { if (tokens[i].asOperator() == Token::Equal) result.append('='); else result.append(tokenText); break; } case Token::Identifier: { if (tokenText == "ERRORTYPE") { // need to replace this result.append("ERROR.TYPE"); } else if (tokenText == "LEGACYNORMSDIST") { result.append("LEGACY.NORMSDIST"); } else if (tokenText == "LEGACYNORMSINV") { result.append("LEGACY.NORMSINV"); } else { // dump it out unchanged result.append(tokenText); } break; } case Token::Boolean: case Token::Integer: case Token::String: default: result.append(tokenText); break; } } return result; }
void Editor::autoCalc() { if( !d->autoCalcEnabled ) return; QString str = Evaluator::autoFix( text() ); if( str.isEmpty() ) return; // too short? do not bother... Tokens tokens = Evaluator::scan( str ); if( tokens.count() < 2 ) return; // If we're using set for a function don't try. QRegExp setFn("\\s*set.*\\(.*="); if( str.find(setFn) != -1 ) return; // strip off assignment operator, e.g. "x=1+2" becomes "1+2" only // the reason is that we want only to evaluate (on the fly) the expression, // not to update (put the result in) the variable if( tokens.count() > 2 && tokens[0].isIdentifier() && tokens[1].asOperator() == Token::Equal ) { Tokens::const_iterator it = tokens.begin(); ++it; ++it; // Skip first two tokens. // Reconstruct string to evaluate using the tokens. str = ""; while(it != tokens.end()) { str += (*it).text(); str += ' '; ++it; } } Abakus::number_t result = parseString(str.latin1()); if( Result::lastResult()->type() == Result::Value ) { QString ss = i18n("Result: <b>%2</b>", result.toString()); d->autoCalcLabel->setText( ss ); d->autoCalcLabel->adjustSize(); // reposition nicely QPoint pos = mapToGlobal( QPoint( 0, 0 ) ); pos.setY( pos.y() - d->autoCalcLabel->height() - 1 ); d->autoCalcLabel->move( pos ); d->autoCalcLabel->show(); d->autoCalcLabel->raise(); // do not show it forever QTimer::singleShot( 5000, d->autoCalcLabel, SLOT( hide()) ); } else { // invalid expression d->autoCalcLabel->hide(); } }
void CellEditor::Private::updateActiveSubRegion(const Tokens &tokens) { // Index of the token, at which the text cursor is positioned. // For sub-regions it is the start range. currentToken = 0; if (tokens.isEmpty()) { selection->setActiveSubRegion(0, 0); // also set the active element return; } const int cursorPosition = textEdit->textCursor().position() - 1; // without '=' kDebug() << "cursorPosition:" << cursorPosition << "textLength:" << textEdit->toPlainText().length() - 1; uint rangeCounter = 0; // counts the ranges in the sub-region uint currentRange = 0; // range index denoting the current range int regionStart = 0; // range index denoting the sub-region start uint regionEnd = 0; // range index denoting the sub-region end enum { Anywhere, InRegion, BeyondCursor } state = Anywhere; Token token; Token::Type type; // Search the current range the text cursor is positioned to. // Determine the subregion start and end, in which the range is located. for (int i = 0; i < tokens.count(); ++i) { token = tokens[i]; type = token.type(); // If not in a subregion, we may already quit the loop here. if (state == Anywhere) { // Already beyond the cursor position? if (token.pos() > cursorPosition) { state = BeyondCursor; break; // for loop } } else if (state == InRegion) { // Loop to the end of the subregion. if (type == Token::Cell || type == Token::Range) { regionEnd = rangeCounter++; continue; // keep going until the referenced region ends } if (type == Token::Operator) { if (tokens[i].asOperator() == Token::Semicolon) { continue; // keep going until the referenced region ends } } state = Anywhere; continue; } // Can the token be replaced by a reference? switch (type) { case Token::Cell: case Token::Range: if (state == Anywhere) { currentToken = i; regionStart = rangeCounter; state = InRegion; } regionEnd = rangeCounter; // length = 1 currentRange = ++rangeCounter; // point behind the last continue; case Token::Unknown: case Token::Boolean: case Token::Integer: case Token::Float: case Token::String: case Token::Error: // Set the active sub-region start to the next range but // with a length of 0, which results in inserting a new range // to the selection on calling Selection::initialize() or // Selection::update(). currentToken = i; regionStart = rangeCounter; // position of the next range regionEnd = rangeCounter - 1; // length = 0 currentRange = rangeCounter; continue; case Token::Operator: case Token::Identifier: continue; } } // Cursor not reached? I.e. the cursor is placed at the last token's end. if (state == Anywhere) { token = tokens.last(); type = token.type(); // Check the last token. // It was processed, but maybe a reference can be placed behind it. // Check, if the token can be replaced by a reference. switch (type) { case Token::Operator: // Possible to place a reference behind the operator? switch (token.asOperator()) { case Token::Plus: case Token::Minus: case Token::Asterisk: case Token::Slash: case Token::Caret: case Token::LeftPar: case Token::Semicolon: case Token::Equal: case Token::NotEqual: case Token::Less: case Token::Greater: case Token::LessEqual: case Token::GreaterEqual: case Token::Intersect: case Token::Union: // Append new references by pointing behind the last. currentToken = tokens.count(); regionStart = rangeCounter; regionEnd = rangeCounter - 1; // length = 0 currentRange = rangeCounter; break; case Token::InvalidOp: case Token::RightPar: case Token::Comma: case Token::Ampersand: case Token::Percent: case Token::CurlyBra: case Token::CurlyKet: case Token::Pipe: // reference cannot be placed behind break; } break; case Token::Unknown: case Token::Boolean: case Token::Integer: case Token::Float: case Token::String: case Token::Identifier: case Token::Error: // currentToken = tokens.count() - 1; // already set // Set the active sub-region start to the end of the selection // with a length of 0, which results in appending a new range // to the selection on calling Selection::initialize() or // Selection::update(). regionStart = rangeCounter; regionEnd = rangeCounter - 1; // length = 0 currentRange = rangeCounter; break; case Token::Cell: case Token::Range: // currentToken = tokens.count() - 1; // already set // Set the last range as active one. It is not a sub-region, // otherwise the state would have been InRegion. regionStart = rangeCounter - 1; regionEnd = rangeCounter - 1; // length = 1 currentRange = rangeCounter; // point behind the last break; } } const int regionLength = regionEnd - regionStart + 1; kDebug() << "currentRange:" << currentRange << "regionStart:" << regionStart << "regionEnd:" << regionEnd << "regionLength:" << regionLength; selection->setActiveSubRegion(regionStart, regionLength, currentRange); }
void Editor::triggerAutoComplete() { if( !d->autoCompleteEnabled ) return; // tokenize the expression (don't worry, this is very fast) // faster now that it uses flex. ;) int para = 0, curPos = 0; getCursorPosition( ¶, &curPos ); QString subtext = text().left( curPos ); Tokens tokens = Evaluator::scan( subtext ); if(!tokens.valid()) { kdWarning() << "invalid tokens.\n"; return; } if(tokens.isEmpty() || subtext.endsWith(" ")) return; Token lastToken = tokens[ tokens.count()-1 ]; // last token must be an identifier if( !lastToken.isIdentifier() ) return; QString id = lastToken.text(); if( id.isEmpty() ) return; // find matches in function names QStringList fnames = FunctionManager::instance()->functionList(FunctionManager::All); QStringList choices; for( unsigned i=0; i<fnames.count(); i++ ) if( fnames[i].startsWith( id, false ) ) { QString str = fnames[i]; ::Function* f = FunctionManager::instance()->function( str ); if( f && !f->description.isEmpty() ) str.append( ':' ).append( f->description ); choices.append( str ); } choices.sort(); // find matches in variables names QStringList vchoices; QStringList values = NumeralModel::instance()->valueNames(); for(QStringList::ConstIterator it = values.begin(); it != values.end(); ++it) if( (*it).startsWith( id, false ) ) { QString choice = NumeralModel::description(*it); if(choice.isEmpty()) choice = NumeralModel::instance()->value(*it).toString(); vchoices.append( QString("%1:%2").arg( *it, choice ) ); } vchoices.sort(); choices += vchoices; // no match, don't bother with completion if( !choices.count() ) return; // one match, complete it for the user if( choices.count()==1 ) { QString str = QStringList::split( ':', choices[0] )[0]; // single perfect match, no need to give choices. if(str == id.lower()) return; str = str.remove( 0, id.length() ); int para = 0, curPos = 0; getCursorPosition( ¶, &curPos ); blockSignals( true ); insert( str ); setSelection( 0, curPos, 0, curPos+str.length() ); blockSignals( false ); return; } // present the user with completion choices d->completion->showCompletion( choices ); }
void CellEditor::permuteFixation() { // Nothing to do, if not in reference selection mode. if (!d->selection->referenceSelection()) { return; } // Search for the last range before or the range at the cursor. int index = -1; const int cursorPosition = textCursor().position() - 1; // - '=' const Tokens tokens = d->highlighter->formulaTokens(); for (int i = 0; i < tokens.count(); ++i) { const Token token = tokens[i]; if (token.pos() > cursorPosition) { break; // for loop } if (token.type() == Token::Cell || token.type() == Token::Range) { index = i; } } // Quit, if no range was found. if (index == -1) { return; } const Token token = tokens[index]; Map *const map = d->selection->activeSheet()->map(); QString regionName = token.text(); // Filter sheet; truncates regionName; range without sheet name resides. Sheet *const sheet = Region(QString(), map).filterSheetName(regionName); const Region region(regionName, map, 0); // TODO Stefan: Skip named areas. if (!region.isValid()) { return; } // FIXME Stefan: need access to fixation, thus to Region::Range; must use iterator Region::Element *range = (*region.constBegin()); QString result(sheet ? (sheet->sheetName() + '!') : QString()); // Permute fixation. if (region.isSingular()) { char fixation = 0x00; if (range->isRowFixed()) { fixation += 0x01; } if (range->isColumnFixed()) { fixation += 0x02; } fixation += 0x01; int i = 0; result += permuteLocationFixation(regionName, i, fixation & 0x02, fixation & 0x01); } else { char fixation = 0x00; if (range->isBottomFixed()) { fixation += 0x01; } if (range->isRightFixed()) { fixation += 0x02; } if (range->isTopFixed()) { fixation += 0x04; } if (range->isLeftFixed()) { fixation += 0x08; } fixation += 0x01; int i = 0; result += permuteLocationFixation(regionName, i, fixation & 0x08, fixation & 0x04); Q_ASSERT(regionName[i] == ':'); ++i; result += ':'; result += permuteLocationFixation(regionName, i, fixation & 0x02, fixation & 0x01); } // Replace the range in the formula's expression. QString text = toPlainText(); const int start = token.pos() + 1; // + '=' const int length = token.text().length(); setPlainText(text.replace(start, length, result)); // Set the cursor to the end of the range. QTextCursor textCursor = this->textCursor(); textCursor.setPosition(start + result.length()); setTextCursor(textCursor); }
void Editor::triggerAutoComplete() { if (!m_isAutoCompletionEnabled) return; // Tokenize the expression (this is very fast). const int currentPosition = textCursor().position(); QString subtext = text().left(currentPosition); const Tokens tokens = m_evaluator->scan(subtext, Evaluator::NoAutoFix); if (!tokens.valid() || tokens.count() < 1) return; Token lastToken = tokens.at(tokens.count()-1); // Last token must be an identifier. if (!lastToken.isIdentifier()) return; const QString id = lastToken.text(); if (id.length() < 1) return; // No space after identifier. if (lastToken.pos() + id.length() < subtext.length()) return; // Find matches in function names. const QStringList fnames = FunctionRepo::instance()->getIdentifiers(); QStringList choices; for (int i = 0; i < fnames.count(); ++i) { if (fnames.at(i).startsWith(id, Qt::CaseInsensitive)) { QString str = fnames.at(i); Function* f = FunctionRepo::instance()->find(str); if (f) str.append(':').append(f->name()); choices.append(str); } } choices.sort(); // Find matches in variables names. QStringList vchoices; QList<Evaluator::Variable> variables = m_evaluator->getVariables(); for (int i = 0; i < variables.count(); ++i) if (variables.at(i).name.startsWith(id, Qt::CaseInsensitive)) vchoices.append(QString("%1:%2").arg(variables.at(i).name) .arg(NumberFormatter::format(variables.at(i).value))); vchoices.sort(); choices += vchoices; // Find matches in user functions. QStringList ufchoices; QList<Evaluator::UserFunctionDescr> userFunctions = m_evaluator->getUserFunctions(); for (int i = 0; i < userFunctions.count(); ++i) if (userFunctions.at(i).name.startsWith(id, Qt::CaseInsensitive)) ufchoices.append(QString("%1:User function").arg(userFunctions.at(i).name)); ufchoices.sort(); choices += ufchoices; // TODO: if we are assigning a user function, find matches in its arguments names and // replace variables names that collide. But we cannot know if we are assigning // a user function without evaluating the expression (a token scan will not be enough). // No match, don't bother with completion. if (!choices.count()) return; // Single perfect match, no need to give choices. if (choices.count() == 1) if (choices.at(0).toLower() == id.toLower()) return; // Present the user with completion choices. m_completion->showCompletion(choices); }