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

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 "CellEditor.h"
0021 
0022 // Sheets
0023 #include "ApplicationSettings.h"
0024 #include "CellToolBase.h"
0025 #include "Formula.h"
0026 #include "FormulaEditorHighlighter.h"
0027 #include "Function.h"
0028 #include "FunctionRepository.h"
0029 #include "FunctionCompletion.h"
0030 #include "Selection.h"
0031 #include "Sheet.h"
0032 #include "Style.h"
0033 #include "Cell.h"
0034 #include "ValueConverter.h"
0035 #include "RectStorage.h"
0036 #include "Map.h"
0037 #include "Value.h"
0038 #include "CellStorage.h"
0039 #include "SheetsDebug.h"
0040 
0041 // Calligra
0042 #include <KoDpi.h>
0043 #include <KoUnit.h>
0044 #include <KoViewConverter.h>
0045 // KF5
0046 #include <ktextedit.h>
0047 
0048 // Qt
0049 #include <QFocusEvent>
0050 #include <QKeyEvent>
0051 #include <QCompleter>
0052 #include <QAbstractItemView>
0053 #include <QStringListModel>
0054 #include <QScrollBar>
0055 #include <QList>
0056 
0057 using namespace Calligra::Sheets;
0058 
0059 class CellEditor::Private
0060 {
0061 public:
0062     CellToolBase*             cellTool;
0063     Selection*                selection;
0064     KTextEdit*                textEdit;
0065     FormulaEditorHighlighter* highlighter;
0066     FunctionCompletion*       functionCompletion;
0067     QTimer*                   functionCompletionTimer;
0068     QHash<int, QString>       *wordCollection;
0069     QPoint globalCursorPos;
0070     QCompleter                *complete;
0071     bool captureAllKeyEvents : 1;
0072     bool selectionChangedLocked     : 1;
0073     int currentToken;
0074 
0075 public:
0076     void updateActiveSubRegion(const Tokens &tokens);
0077     void rebuildSelection();
0078 };
0079 
0080 void CellEditor::Private::updateActiveSubRegion(const Tokens &tokens)
0081 {
0082     // Index of the token, at which the text cursor is positioned.
0083     // For sub-regions it is the start range.
0084     currentToken = 0;
0085 
0086     if (tokens.isEmpty()) {
0087         selection->setActiveSubRegion(0, 0); // also set the active element
0088         return;
0089     }
0090 
0091     const int cursorPosition = textEdit->textCursor().position() - 1; // without '='
0092     debugSheets << "cursorPosition:" << cursorPosition << "textLength:" << textEdit->toPlainText().length() - 1;
0093 
0094     uint rangeCounter = 0; // counts the ranges in the sub-region
0095     uint currentRange = 0; // range index denoting the current range
0096     int regionStart = 0; // range index denoting the sub-region start
0097     uint regionEnd = 0; // range index denoting the sub-region end
0098     enum { Anywhere, InRegion, BeyondCursor } state = Anywhere;
0099 
0100     Token token;
0101     Token::Type type;
0102     // Search the current range the text cursor is positioned to.
0103     // Determine the subregion start and end, in which the range is located.
0104     for (int i = 0; i < tokens.count(); ++i) {
0105         token = tokens[i];
0106         type = token.type();
0107 
0108         // If not in a subregion, we may already quit the loop here.
0109         if (state == Anywhere) {
0110             // Already beyond the cursor position?
0111             if (token.pos() > cursorPosition) {
0112                 state = BeyondCursor;
0113                 break; // for loop
0114             }
0115         } else if (state == InRegion) {
0116             // Loop to the end of the subregion.
0117             if (type == Token::Cell || type == Token::Range) {
0118                 regionEnd = rangeCounter++;
0119                 continue; // keep going until the referenced region ends
0120             }
0121             if (type == Token::Operator) {
0122                 if (tokens[i].asOperator() == Token::Semicolon) {
0123                     continue; // keep going until the referenced region ends
0124                 }
0125             }
0126             state = Anywhere;
0127             continue;
0128         }
0129 
0130         // Can the token be replaced by a reference?
0131         switch (type) {
0132         case Token::Cell:
0133         case Token::Range:
0134             if (state == Anywhere) {
0135                 currentToken = i;
0136                 regionStart = rangeCounter;
0137                 state = InRegion;
0138             }
0139             regionEnd = rangeCounter; // length = 1
0140             currentRange = ++rangeCounter; // point behind the last
0141             continue;
0142         case Token::Unknown:
0143         case Token::Boolean:
0144         case Token::Integer:
0145         case Token::Float:
0146         case Token::String:
0147         case Token::Error:
0148             // Set the active sub-region start to the next range but
0149             // with a length of 0, which results in inserting a new range
0150             // to the selection on calling Selection::initialize() or
0151             // Selection::update().
0152             currentToken = i;
0153             regionStart = rangeCounter; // position of the next range
0154             regionEnd = rangeCounter - 1; // length = 0
0155             currentRange = rangeCounter;
0156             continue;
0157         case Token::Operator:
0158         case Token::Identifier:
0159             continue;
0160         }
0161     }
0162 
0163     // Cursor not reached? I.e. the cursor is placed at the last token's end.
0164     if (state == Anywhere) {
0165         token = tokens.last();
0166         type = token.type();
0167         // Check the last token.
0168         // It was processed, but maybe a reference can be placed behind it.
0169         // Check, if the token can be replaced by a reference.
0170         switch (type) {
0171         case Token::Operator:
0172             // Possible to place a reference behind the operator?
0173             switch (token.asOperator()) {
0174             case Token::Plus:
0175             case Token::Minus:
0176             case Token::Asterisk:
0177             case Token::Slash:
0178             case Token::Caret:
0179             case Token::LeftPar:
0180             case Token::Semicolon:
0181             case Token::Equal:
0182             case Token::NotEqual:
0183             case Token::Less:
0184             case Token::Greater:
0185             case Token::LessEqual:
0186             case Token::GreaterEqual:
0187             case Token::Intersect:
0188             case Token::Union:
0189                 // Append new references by pointing behind the last.
0190                 currentToken = tokens.count();
0191                 regionStart = rangeCounter;
0192                 regionEnd = rangeCounter - 1; // length = 0
0193                 currentRange = rangeCounter;
0194                 break;
0195             case Token::InvalidOp:
0196             case Token::RightPar:
0197             case Token::Comma:
0198             case Token::Ampersand:
0199             case Token::Percent:
0200             case Token::CurlyBra:
0201             case Token::CurlyKet:
0202             case Token::Pipe:
0203                 // reference cannot be placed behind
0204                 break;
0205             }
0206             break;
0207         case Token::Unknown:
0208         case Token::Boolean:
0209         case Token::Integer:
0210         case Token::Float:
0211         case Token::String:
0212         case Token::Identifier:
0213         case Token::Error:
0214             // currentToken = tokens.count() - 1; // already set
0215             // Set the active sub-region start to the end of the selection
0216             // with a length of 0, which results in appending a new range
0217             // to the selection on calling Selection::initialize() or
0218             // Selection::update().
0219             regionStart = rangeCounter;
0220             regionEnd = rangeCounter - 1; // length = 0
0221             currentRange = rangeCounter;
0222             break;
0223         case Token::Cell:
0224         case Token::Range:
0225             // currentToken = tokens.count() - 1; // already set
0226             // Set the last range as active one. It is not a sub-region,
0227             // otherwise the state would have been InRegion.
0228             regionStart = rangeCounter - 1;
0229             regionEnd = rangeCounter - 1; // length = 1
0230             currentRange = rangeCounter; // point behind the last
0231             break;
0232         }
0233     }
0234 
0235     const int regionLength = regionEnd - regionStart + 1;
0236     debugSheets << "currentRange:" << currentRange << "regionStart:" << regionStart
0237     << "regionEnd:" << regionEnd << "regionLength:" << regionLength;
0238 
0239     selection->setActiveSubRegion(regionStart, regionLength, currentRange);
0240 }
0241 
0242 
0243 CellEditor::CellEditor(CellToolBase *cellTool,QHash<int,QString> &wordList, QWidget* parent)
0244         : KTextEdit(parent)
0245         , d(new Private)
0246 {
0247     d->cellTool = cellTool;
0248     d->selection = cellTool->selection();
0249     d->textEdit = this;
0250     d->globalCursorPos = QPoint();
0251     d->captureAllKeyEvents = false;
0252     d->selectionChangedLocked = false;
0253     d->currentToken = 0;
0254     d->wordCollection = &wordList;
0255 
0256     setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
0257     setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
0258     setFrameStyle(QFrame::NoFrame);
0259     setLineWidth(0);
0260     document()->setDocumentMargin(0);
0261     // setMinimumHeight(fontMetrics().height());
0262 
0263     d->highlighter = new FormulaEditorHighlighter(this, d->selection);
0264 
0265     d->functionCompletion = new FunctionCompletion(this);
0266     d->functionCompletionTimer = new QTimer(this);
0267 
0268     const Cell cell(d->selection->activeSheet(), d->selection->marker());
0269     const bool wrapText = cell.style().wrapText();
0270     d->textEdit->setWordWrapMode(wrapText ? QTextOption::WordWrap : QTextOption::NoWrap);
0271 
0272 #if 0 // FIXME Implement a completion aware KTextEdit.
0273     setCompletionMode(selection()->view()->doc()->completionMode());
0274     setCompletionObject(&selection()->view()->doc()->map()->stringCompletion(), true);
0275 #endif
0276     
0277     //populateWordCollection();
0278     
0279     //AutoCompletion Code
0280     d->complete = new QCompleter(this);
0281     d->complete->setModel(model());
0282     //completer->setModelSorting(QCompleter::CaseInsensitivelySortedModel);
0283     d->complete->setCaseSensitivity(Qt::CaseInsensitive);
0284     d->complete->setWrapAround(false);
0285     this->setCompleter(d->complete);
0286     
0287     connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(slotCursorPositionChanged()));
0288     connect(this, SIGNAL(textChanged()), this, SLOT(slotTextChanged()));
0289 }
0290 
0291 CellEditor::~CellEditor()
0292 {
0293     if (selection()){
0294         selection()->endReferenceSelection();
0295     }
0296     delete d;
0297 }
0298 
0299 Selection* CellEditor::selection() const
0300 {
0301     return d->selection;
0302 }
0303 
0304 QPoint CellEditor::globalCursorPosition() const
0305 {
0306     return d->globalCursorPos;
0307 }
0308 
0309 bool CellEditor::captureArrowKeys() const
0310 {
0311     return d->captureAllKeyEvents;
0312 }
0313 
0314 void CellEditor::setCaptureArrowKeys(bool capture)
0315 {
0316     d->captureAllKeyEvents = capture;
0317 }
0318 
0319 //AutoCompletion Functions
0320 
0321 QAbstractItemModel *CellEditor::model()
0322 {
0323   //ValueConverter *conv;
0324   QList<QString> words;
0325   QList<QString> wordlist;
0326   const Cell cell(d->selection->activeSheet(), d->selection->marker());
0327   int col = cell.column();
0328   words = d->wordCollection->values(col);
0329   for (int i = 0; i < 3 && (!words.isEmpty()) ; i++) {
0330     wordlist.push_back(words.front());
0331     words.pop_front();
0332   }
0333     return new QStringListModel(wordlist, d->complete);
0334 }
0335 
0336 void CellEditor::setCompleter(QCompleter *completer)
0337 {
0338      if (d->complete)
0339          QObject::disconnect(d->complete, 0, this, 0);
0340 
0341      d->complete = completer;
0342 
0343      if (!d->complete)
0344          return;
0345 
0346      d->complete->setWidget(this);
0347      d->complete->setCompletionMode(QCompleter::PopupCompletion);
0348      d->complete->setCaseSensitivity(Qt::CaseInsensitive);
0349      QObject::connect(d->complete, SIGNAL(activated(QString)),
0350                       this, SLOT(insertCompletion(QString)));
0351 }
0352 
0353 QCompleter *CellEditor::completer() const
0354 {
0355      return d->complete;
0356 }
0357 
0358  void CellEditor::insertCompletion(const QString& completion)
0359 {
0360      if (d->complete->widget() != this)
0361          return;
0362      QTextCursor tc = textCursor();
0363      int extra = completion.length() - d->complete->completionPrefix().length();
0364      tc.movePosition(QTextCursor::Left);
0365      tc.movePosition(QTextCursor::EndOfWord);
0366      tc.insertText(completion.right(extra));
0367      setTextCursor(tc);
0368 }
0369 
0370 QString CellEditor::textUnderCursor() const
0371 {
0372      QTextCursor tc = textCursor();
0373      tc.select(QTextCursor::WordUnderCursor);
0374      return tc.selectedText();
0375 }
0376 
0377 void CellEditor::slotCursorPositionChanged()
0378 {
0379     // Nothing to do, if not in reference selection mode.
0380     if (!selection()->referenceSelection()) {
0381         return;
0382     }
0383     // NOTE On text changes TextEdit::cursorPositionChanged() is triggered
0384     // before TextEdit::textChanged(). The text is already up-to-date.
0385 
0386     // Save the global position for the function auto-completion popup.
0387     d->globalCursorPos = mapToGlobal(cursorRect().bottomLeft());
0388 
0389     // Needs up-to-date tokens; QSyntaxHighlighter::rehighlight() gets called
0390     // automatically on text changes, which does the update.
0391     d->updateActiveSubRegion(d->highlighter->formulaTokens());
0392 }
0393 
0394 void CellEditor::Private::rebuildSelection()
0395 {
0396     // Do not react on selection changes, that update the formula's expression,
0397     // because the selection gets already build based on the current formula.
0398     selectionChangedLocked = true;
0399 
0400     Sheet *const originSheet = selection->originSheet();
0401     Map *const map = originSheet->map();
0402 
0403     // Rebuild the reference selection by using the formula tokens.
0404     Tokens tokens = highlighter->formulaTokens();
0405     selection->update(); // set the old cursor dirty; updates the editors
0406     selection->clear();
0407 
0408     //A list of regions which have already been highlighted on the spreadsheet.
0409     //This is so that we don't end up highlighting the same region twice in two different
0410     //colors.
0411     QSet<QString> alreadyUsedRegions;
0412 
0413     int counter = 0;
0414     for (int i = 0; i < tokens.count(); ++i) {
0415         const Token token = tokens[i];
0416         const Token::Type type = token.type();
0417 
0418         if (type == Token::Cell || type == Token::Range) {
0419             const Region region(token.text(), map, originSheet);
0420 
0421             if (!region.isValid() || region.isEmpty()) {
0422                 continue;
0423             }
0424             if (alreadyUsedRegions.contains(region.name())) {
0425                 continue;
0426             }
0427             alreadyUsedRegions.insert(region.name());
0428 
0429             const QRect range = region.firstRange();
0430             Sheet *const sheet = region.firstSheet();
0431 
0432             selection->initialize(range, sheet);
0433             // Always append the next range by pointing behind the last item.
0434             selection->setActiveSubRegion(++counter, 0);
0435         }
0436     }
0437 
0438     // Set the active sub-region.
0439     // Needs up-to-date tokens; QSyntaxHighlighter::rehighlight() gets called
0440     // automatically on text changes, which does the update.
0441     updateActiveSubRegion(highlighter->formulaTokens());
0442 
0443     selectionChangedLocked = false;
0444 }
0445 
0446 void CellEditor::setEditorFont(QFont const & font, bool updateSize, const KoViewConverter *viewConverter)
0447 {
0448     const qreal scaleY = POINT_TO_INCH(static_cast<qreal>((KoDpi::dpiY())));
0449     setFont(QFont(font.family(), viewConverter->documentToViewY(font.pointSizeF()) / scaleY));
0450 
0451     if (updateSize) {
0452         QFontMetrics fontMetrics(this->font());
0453         int width = fontMetrics.width(toPlainText()) + fontMetrics.averageCharWidth();
0454         // don't make it smaller: then we would have to repaint the obscured cells
0455         if (width < this->width())
0456             width = this->width();
0457         int height = fontMetrics.height();
0458         if (height < this->height())
0459             height = this->height();
0460         setGeometry(x(), y(), width, height);
0461     }
0462 }
0463 
0464 void CellEditor::slotCompletionModeChanged(KCompletion::CompletionMode _completion)
0465 {
0466     selection()->activeSheet()->map()->settings()->setCompletionMode(_completion);
0467 }
0468 
0469 void CellEditor::slotTextChanged()
0470 {
0471     // NOTE On text changes TextEdit::cursorPositionChanged() is triggered
0472     // before TextEdit::textChanged().
0473 
0474     // Fix the position.
0475     verticalScrollBar()->setValue(1);
0476 
0477     const QString text = toPlainText();
0478 
0479     const QFontMetricsF fontMetrics(font());
0480     // TODO Adjust size depending on which cells can be obscured (see CellView)?
0481     // The following line would result in an unchanged width for cells with
0482     // enabled word wrapping, but after the user input got applied it may
0483     // obscure cells horizontally.
0484     // Passing no flags will only change the height, if a manual line break is
0485     // entered (Shift + Return).
0486     // const int flags = wordWrapMode() == QTextOption::NoWrap ? 0 : Qt::TextWordWrap;
0487     const QRectF rect = fontMetrics.boundingRect(this->rect(), 0, text);
0488     const int requiredWidth = rect.width();
0489     const int requiredHeight = rect.height() - 1; // -1 to fit into a default cell
0490     if (text.isRightToLeft()) {
0491         setGeometry(x() - requiredWidth + width(), y(), requiredWidth, requiredHeight);
0492     } else {
0493         setGeometry(x(), y(), requiredWidth, requiredHeight);
0494     }
0495 
0496     // FIXME Stefan: Is this really wanted? The percent char does not get
0497     // removed on applying the user input. If the style changes afterwards,
0498     // the user input is still indicating a percent value. If the digit gets
0499     // deleted while editing the percent char also stays. Disabling for now.
0500 #if 0 // CALLIGRA_SHEETS_WIP_EDITOR_OVERHAUL
0501     const Cell cell(d->selection->activeSheet(), d->selection->marker());
0502     if ((cell.style().formatType()) == Format::Percentage) {
0503         if ((text.length() == 1) && text[0].isDigit()) {
0504             setPlainText(text + " %");
0505             setCursorPosition(1);
0506             return;
0507         }
0508     }
0509 #endif // CALLIGRA_SHEETS_WIP_EDITOR_OVERHAUL
0510 
0511     // update the external editor, but only if we have focus
0512     if (hasFocus()) {
0513         emit textChanged(text);
0514     }
0515 
0516     // Enable/disable the reference selection.
0517     if (!text.isEmpty() && text[0] == '=') {
0518         selection()->startReferenceSelection();
0519     } else {
0520         selection()->endReferenceSelection();
0521         return;
0522     }
0523 
0524     // The expression highlighting got updated automatically.
0525     // If a reference has changed since the last update ...
0526     if (d->highlighter->rangeChanged()) {
0527         // Reset the flag, that indicates range changes after text changes.
0528         d->highlighter->resetRangeChanged();
0529         // Rebuild the reference selection by using the formula tokens.
0530         d->rebuildSelection();
0531     }
0532 }
0533 
0534 // Called on selection (and sheet) changes.
0535 void CellEditor::selectionChanged()
0536 {
0537     if (d->selectionChangedLocked) {
0538         return;
0539     }
0540 
0541     Selection* choice = selection();
0542 
0543     if (choice->isEmpty())
0544         return;
0545 
0546     const QString text = toPlainText();
0547     const int textLength = text.length();
0548 
0549     // Find the start text cursor position for the active sub-region within
0550     // the formula's expression and determine the length of the sub-region.
0551     Tokens tokens = d->highlighter->formulaTokens();
0552     uint start = 1;
0553     uint length = 0;
0554     if (!tokens.empty()) {
0555         if (d->currentToken < tokens.count()) {
0556             Token token = tokens[d->currentToken];
0557             Token::Type type = token.type();
0558             if (type == Token::Cell || type == Token::Range) {
0559                 start = token.pos() + 1; // don't forget the '='!
0560                 length = token.text().length();
0561                 // Iterate to the end of the sub-region.
0562                 for (int i = d->currentToken + 1; i < tokens.count(); ++i) {
0563                     token = tokens[i];
0564                     type = token.type();
0565                     switch (type) {
0566                     case Token::Cell:
0567                     case Token::Range:
0568                         length += token.text().length();
0569                         continue;
0570                     case Token::Operator:
0571                         if (token.asOperator() == Token::Semicolon) {
0572                             ++length;
0573                             continue;
0574                         }
0575                     default:
0576                         break;
0577                     }
0578                     break;
0579                 }
0580             } else {
0581                 start = token.pos() + 1; // don't forget the '='!
0582                 length = token.text().length();
0583             }
0584         } else {
0585             // sanitize
0586             d->currentToken = tokens.count();
0587             start = textLength;
0588         }
0589     }
0590 
0591     // Replace the formula's active sub-region with the selection's one.
0592     const QString address = choice->activeSubRegionName();
0593     const QString newExpression = QString(text).replace(start, length, address);
0594     // The expression highlighting gets updated automatically by the next call,
0595     // even though signals are blocked (must be connected to QTextDocument).
0596     blockSignals(true);
0597     setText(newExpression, start + address.length());
0598     blockSignals(false);
0599 
0600     // Ranges have changed.
0601     // Reset the flag, that indicates range changes after text changes.
0602     d->highlighter->resetRangeChanged();
0603     // Mirror the behaviour of slotCursorPositionChanged(), but here the tokens
0604     // are already up-to-date.
0605     d->globalCursorPos = mapToGlobal(cursorRect().bottomLeft());
0606     // Set the active sub-region.
0607     // Needs up-to-date tokens; QSyntaxHighlighter::rehighlight() gets called
0608     // automatically on text changes, which does the update.
0609     d->updateActiveSubRegion(d->highlighter->formulaTokens());
0610 
0611     // Always emit, because this editor may be hidden or does not have focus,
0612     // but the external one needs an update.
0613     emit textChanged(toPlainText());
0614 }
0615 
0616 void CellEditor::keyPressEvent(QKeyEvent *event)
0617  { 
0618     const Cell cell_temp(d->selection->activeSheet(), d->selection->marker()); 
0619    
0620     switch (event->key()) {
0621     case Qt::Key_Left:
0622     case Qt::Key_Right:
0623     case Qt::Key_Up:
0624     case Qt::Key_Down:
0625     case Qt::Key_PageDown:
0626     case Qt::Key_PageUp:
0627         // Forward left/right arrows to parent, so that pressing left/right
0628         // in this editor leaves editing mode, unless this editor has been
0629         // set to capture arrow key events.
0630         if (d->captureAllKeyEvents) {
0631             break; // pass to TextEdit
0632         }
0633         event->ignore(); // pass to parent
0634         return;
0635     case Qt::Key_Tab:
0636     case Qt::Key_Backtab:
0637         // Always forward tab/backtab to parent, so that pressing them leaves
0638         // editing mode. To insert literal tabs you can always use the external
0639         // editor.
0640 
0641         if (!textUnderCursor().isEmpty() && !d->wordCollection->values(cell_temp.column()).contains(textUnderCursor())) {
0642       d->wordCollection->insertMulti(cell_temp.column(), textUnderCursor());
0643     }
0644     event->ignore();
0645     return;
0646     case Qt::Key_Return:
0647     case Qt::Key_Enter:
0648         // Shift + Return: manual line wrap
0649         if (event->modifiers() & Qt::ShiftModifier) {
0650             break; // pass to TextEdit
0651         }
0652         if (!textUnderCursor().isEmpty() && !d->wordCollection->values(cell_temp.column()).contains(textUnderCursor())) {
0653       d->wordCollection->insertMulti(cell_temp.column(), textUnderCursor());
0654     }
0655     event->ignore(); // pass to parent
0656         return;
0657     }
0658     
0659     if (d->complete && d->complete->popup()->isVisible()) {
0660          // The following keys are forwarded by the completer to the widget
0661         switch (event->key()) {
0662         case Qt::Key_Enter:
0663         case Qt::Key_Return:
0664         case Qt::Key_Escape:
0665         case Qt::Key_Tab:
0666         case Qt::Key_Backtab:
0667              event->ignore();
0668              return; // let the completer do default behavior
0669         default:
0670             break;
0671         }
0672      }
0673 
0674 
0675      bool isShortcut = ((event->modifiers() & Qt::ControlModifier) && event->key() == Qt::Key_E); // CTRL+E
0676      if (!d->complete || !isShortcut) // do not process the shortcut when we have a completer
0677          QTextEdit::keyPressEvent(event);
0678 
0679      const bool ctrlOrShift = event->modifiers() & (Qt::ControlModifier | Qt::ShiftModifier);
0680      if (!d->complete || (ctrlOrShift && event->text().isEmpty()))
0681          return;
0682 
0683      static QString eow("~!@#$%^&*()_+{}|:\"<>?,./;'[]\\-="); // end of word
0684      bool hasModifier = (event->modifiers() != Qt::NoModifier) && !ctrlOrShift;
0685      QString completionPrefix = textUnderCursor();
0686 
0687      if (!isShortcut && (hasModifier || event->text().isEmpty()|| completionPrefix.length() < 3||eow.contains(event->text().right(1)))) {
0688          d->complete->popup()->hide();
0689          return;
0690      }
0691      if (completionPrefix != d->complete->completionPrefix()) {
0692          d->complete->setCompletionPrefix(completionPrefix);
0693          d->complete->popup()->setCurrentIndex(d->complete->completionModel()->index(0, 0));
0694     }
0695      QRect cr = cursorRect();
0696      cr.setWidth(d->complete->popup()->sizeHintForColumn(0) + d->complete->popup()->verticalScrollBar()->sizeHint().width());
0697      d->complete->complete(); // pop it up! 
0698     
0699 }
0700 
0701 void CellEditor::focusInEvent(QFocusEvent *event)
0702 {
0703     if (d->complete)
0704          d->complete->setWidget(this);
0705      KTextEdit::focusInEvent(event);
0706     // If the focussing is user induced.
0707     if (event->reason() != Qt::OtherFocusReason) {
0708         debugSheets << "induced by user";
0709         d->cellTool->setLastEditorWithFocus(CellToolBase::EmbeddedEditor);
0710     }
0711     KTextEdit::focusInEvent(event);
0712 }
0713 
0714 void CellEditor::focusOutEvent(QFocusEvent *event)
0715 {
0716     KTextEdit::focusOutEvent(event);
0717 }
0718 
0719 void CellEditor::setText(const QString& text, int cursorPos)
0720 {
0721     if (text == toPlainText()) return;
0722 
0723     setPlainText(text);
0724 
0725     // Only update the cursor position, if a non-negative value was set.
0726     // The default parameter value is -1, i.e. the cursor does not get touched.
0727     if (cursorPos >= 0) {
0728         if (cursorPos > text.length()) {
0729             // Usability: It is usually more convenient, if the cursor is
0730             // positioned at the end of the text so it can be quickly deleted
0731             // using the backspace key.
0732             cursorPos = text.length();
0733         }
0734         setCursorPosition(cursorPos);
0735     }
0736 }
0737 
0738 // helper for CellEditor::permuteFixation()
0739 QString permuteLocationFixation(const QString &regionName, int &i,
0740                                 bool columnFixed, bool rowFixed)
0741 {
0742     QString result;
0743     if (columnFixed) {
0744         result += '$';
0745     }
0746     // copy the column letter(s)
0747     while (i < regionName.count()) {
0748         if (!regionName[i].isLetter()) {
0749             if (regionName[i] == '$') {
0750                 // swallow the old fixation
0751                 ++i;
0752                 continue;
0753             }
0754             // stop, if not a column letter
0755             break;
0756         }
0757         result += regionName[i++];
0758     }
0759     if (rowFixed) {
0760         result += '$';
0761     }
0762     // copy the row number(s)
0763     while (i < regionName.count()) {
0764         if (!regionName[i].isNumber()) {
0765             if (regionName[i] == '$') {
0766                 // swallow the old fixation
0767                 ++i;
0768                 continue;
0769             }
0770             // stop, if not a row number
0771             break;
0772         }
0773         result += regionName[i++];
0774     }
0775     return result;
0776 }
0777 
0778 void CellEditor::permuteFixation()
0779 {
0780     // Nothing to do, if not in reference selection mode.
0781     if (!d->selection->referenceSelection()) {
0782         return;
0783     }
0784 
0785     // Search for the last range before or the range at the cursor.
0786     int index = -1;
0787     const int cursorPosition = textCursor().position() - 1; // - '='
0788     const Tokens tokens = d->highlighter->formulaTokens();
0789     for (int i = 0; i < tokens.count(); ++i) {
0790         const Token token = tokens[i];
0791         if (token.pos() > cursorPosition) {
0792             break; // for loop
0793         }
0794         if (token.type() == Token::Cell || token.type() == Token::Range) {
0795             index = i;
0796         }
0797     }
0798     // Quit, if no range was found.
0799     if (index == -1) {
0800         return;
0801     }
0802 
0803     const Token token = tokens[index];
0804     Map *const map = d->selection->activeSheet()->map();
0805     QString regionName = token.text();
0806     // Filter sheet; truncates regionName; range without sheet name resides.
0807     Sheet *const sheet = Region(QString(), map).filterSheetName(regionName);
0808     const Region region(regionName, map, 0);
0809     // TODO Stefan: Skip named areas.
0810     if (!region.isValid()) {
0811         return;
0812     }
0813     // FIXME Stefan: need access to fixation, thus to Region::Range; must use iterator
0814     Region::Element *range = (*region.constBegin());
0815     QString result(sheet ? (sheet->sheetName() + '!') : QString());
0816     // Permute fixation.
0817     if (region.isSingular()) {
0818         char fixation = 0x00;
0819         if (range->isRowFixed()) {
0820             fixation += 0x01;
0821         }
0822         if (range->isColumnFixed()) {
0823             fixation += 0x02;
0824         }
0825         fixation += 0x01;
0826 
0827         int i = 0;
0828         result += permuteLocationFixation(regionName, i, fixation & 0x02, fixation & 0x01);
0829     } else {
0830         char fixation = 0x00;
0831         if (range->isBottomFixed()) {
0832             fixation += 0x01;
0833         }
0834         if (range->isRightFixed()) {
0835             fixation += 0x02;
0836         }
0837         if (range->isTopFixed()) {
0838             fixation += 0x04;
0839         }
0840         if (range->isLeftFixed()) {
0841             fixation += 0x08;
0842         }
0843         fixation += 0x01;
0844 
0845         int i = 0;
0846         result += permuteLocationFixation(regionName, i, fixation & 0x08, fixation & 0x04);
0847         Q_ASSERT(regionName[i] == ':');
0848         ++i;
0849         result += ':';
0850         result += permuteLocationFixation(regionName, i, fixation & 0x02, fixation & 0x01);
0851     }
0852     // Replace the range in the formula's expression.
0853     QString text = toPlainText();
0854     const int start = token.pos() + 1; // + '='
0855     const int length = token.text().length();
0856     setPlainText(text.replace(start, length, result));
0857     // Set the cursor to the end of the range.
0858     QTextCursor textCursor = this->textCursor();
0859     textCursor.setPosition(start + result.length());
0860     setTextCursor(textCursor);
0861 }
0862 
0863 int CellEditor::cursorPosition() const
0864 {
0865     return textCursor().position();
0866 }
0867 
0868 void CellEditor::setCursorPosition(int pos)
0869 {
0870     QTextCursor textCursor(this->textCursor());
0871     textCursor.setPosition(pos);
0872     setTextCursor(textCursor);
0873 }
0874 
0875 // Called by the cell tool when setting the active element with a cell location.
0876 void CellEditor::setActiveSubRegion(int index)
0877 {
0878     index = qBound(0, index, (int)d->highlighter->rangeCount());
0879     int counter = 0;
0880     bool subRegion = false;
0881     const Tokens tokens = d->highlighter->formulaTokens();
0882     for (int i = 0; i < tokens.count(); ++i) {
0883         const Token token = tokens[i];
0884         switch (token.type()) {
0885         case Token::Cell:
0886         case Token::Range:
0887             if (!subRegion) {
0888                 d->currentToken = i;
0889                 subRegion = true;
0890             }
0891             if (counter == index) {
0892                 setCursorPosition(token.pos() + token.text().length() + 1);
0893                 return;
0894             }
0895             ++counter;
0896             continue;
0897         case Token::Operator:
0898             if (token.asOperator() == Token::Semicolon) {
0899                 if (subRegion) {
0900                     continue;
0901                 }
0902             }
0903             subRegion = false;
0904             continue;
0905         default:
0906             subRegion = false;
0907             continue;
0908         }
0909     }
0910 }