/* This file is part of the KDE project Copyright 1999-2006 The KSpread Team This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kspread_editors.h" #include "kspread_canvas.h" #include "kspread_cell.h" #include "kspread_doc.h" #include "selection.h" #include "kspread_sheet.h" #include "kspread_view.h" #include "kspread_util.h" #include "formula.h" #include "functions.h" #include #include #include #include #include #include #include #include //#include #include #include #include #include #include #include #include using namespace KSpread; /***************************************************************************** * * FormulaEditorHighlighter * ****************************************************************************/ namespace KSpread { class FormulaEditorHighlighter::Private { public: Private() { canvas = 0; tokens = Tokens(); rangeCount = 0; rangeChanged = false; } // source for cell reference checking Canvas* canvas; Tokens tokens; uint rangeCount; bool rangeChanged; }; FormulaEditorHighlighter::FormulaEditorHighlighter(TQTextEdit* textEdit, Canvas* canvas) : TQSyntaxHighlighter(textEdit) { d = new Private(); d->canvas = canvas; } FormulaEditorHighlighter::~FormulaEditorHighlighter() { delete d; } const Tokens& FormulaEditorHighlighter::formulaTokens() const { return d->tokens; } int FormulaEditorHighlighter::highlightParagraph(const TQString& text, int /* endStateOfLastPara */) { // reset syntax highlighting setFormat(0, text.length(), TQt::black); // 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); TQFont editorFont = textEdit()->currentFont(); TQFont font; uint oldRangeCount = d->rangeCount; d->rangeCount = 0; TQValueList colors = d->canvas->choice()->colors(); TQValueList alreadyFoundRanges; for (uint 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; } Range newRange( token.text() ); if (!alreadyFoundRanges.contains(newRange)) { alreadyFoundRanges.append(newRange); d->rangeCount++; } setFormat(token.pos() + 1, token.text().length(), colors[ alreadyFoundRanges.findIndex(newRange) % colors.size()] ); } break; case Token::Boolean: // True, False (also i18n-ized) /* font = TQFont(editorFont); font.setBold(true); setFormat(token.pos() + 1, token.text().length(), font);*/ break; case Token::Identifier: // function name or named area*/ /* font = TQFont(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: // "KOffice", "The quick brown fox..." 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; return 0; } void FormulaEditorHighlighter::handleBrace( uint index ) { int cursorParagraph; int cursorPos; const Token& token = d->tokens.at( index ); textEdit()->getCursorPosition( &cursorParagraph , &cursorPos ); int distance = cursorPos-token.pos(); int opType = token.asOperator(); bool highlightBrace=false; //Check where the cursor is in relation to this left or right parenthesis token. //Only one pair of braces should be highlighted at a time, and if the cursor //is between two braces, the inner-most pair should be highlighted. if ( opType == Token::LeftPar ) { //If cursor is directly to the left of this left brace, highlight it if ( distance == 1 ) highlightBrace=true; else //Cursor is directly to the right of this left brace, highlight it unless //there is another left brace to the right (in which case that should be highlighted instead as it //is the inner-most brace) if (distance==2) if ( (index == d->tokens.count()-1) || ( d->tokens.at(index+1).asOperator() != Token::LeftPar) ) highlightBrace=true; } else { //If cursor is directly to the right of this right brace, highlight it if ( distance == 2 ) highlightBrace=true; else //Cursor is directly to the left of this right brace, so highlight it unless //there is another right brace to the left (in which case that should be highlighted instead as it //is the inner-most brace) if ( distance == 1 ) if ( (index == 0) || (d->tokens.at(index-1).asOperator() != Token::RightPar) ) highlightBrace=true; } if (highlightBrace) { TQFont font = TQFont( textEdit()->currentFont() ); font.setBold(true); setFormat(token.pos() + 1, token.text().length(), font); int matching = findMatchingBrace( index ); if (matching != -1) { Token matchingBrace = d->tokens.at(matching); setFormat( matchingBrace.pos() + 1 , matchingBrace.text().length() , font); } } } 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; } uint FormulaEditorHighlighter::rangeCount() const { return d->rangeCount; } bool FormulaEditorHighlighter::rangeChanged() const { return d->rangeChanged; } void FormulaEditorHighlighter::resetRangeChanged() { d->rangeChanged=false; } } // namespace KSpread /***************************************************************************** * * FunctionCompletion * ****************************************************************************/ class FunctionCompletion::Private { public: CellEditor* editor; TQVBox *completionPopup; KListBox *completionListBox; TQLabel* hintLabel; }; FunctionCompletion::FunctionCompletion( CellEditor* editor ): TQObject( editor ) { d = new Private; d->editor = editor; d->hintLabel = 0; d->completionPopup = new TQVBox( editor->topLevelWidget(), 0, WType_Popup ); d->completionPopup->setFrameStyle( TQFrame::Box | TQFrame::Plain ); d->completionPopup->setLineWidth( 1 ); d->completionPopup->installEventFilter( this ); d->completionPopup->setSizePolicy( TQSizePolicy::Expanding, TQSizePolicy::Minimum); d->completionListBox = new KListBox( d->completionPopup ); d->completionPopup->setFocusProxy( d->completionListBox ); d->completionListBox->setFrameStyle( TQFrame::NoFrame ); d->completionListBox->setVariableWidth( true ); d->completionListBox->installEventFilter( this ); connect( d->completionListBox, TQT_SIGNAL(selected(const TQString&)), this, TQT_SLOT(itemSelected(const TQString&)) ); connect( d->completionListBox, TQT_SIGNAL(highlighted(const TQString&)), this, TQT_SLOT(itemSelected(const TQString&)) ); d->hintLabel = new TQLabel( 0, "autocalc", TQt::WStyle_StaysOnTop | TQt::WStyle_Customize | TQt::WStyle_NoBorder | TQt::WStyle_Tool | TQt::WX11BypassWM ); d->hintLabel->setFrameStyle( TQFrame::Plain | TQFrame::Box ); d->hintLabel->setPalette( TQToolTip::palette() ); d->hintLabel->hide(); } FunctionCompletion::~FunctionCompletion() { delete d->hintLabel; delete d; } void FunctionCompletion::itemSelected( const TQString& item ) { KSpread::FunctionDescription* desc; desc = KSpread::FunctionRepository::self()->functionInfo(item); if(!desc) { d->hintLabel->hide(); return; } TQString helpText = desc->helpText()[0]; if( helpText.isEmpty() ) { d->hintLabel->hide(); return; } helpText.append("").prepend(""); d->hintLabel->setText( helpText ); d->hintLabel->adjustSize(); // reposition nicely TQPoint pos = d->editor->mapToGlobal( TQPoint( d->editor->width(), 0 ) ); pos.setY( pos.y() - d->hintLabel->height() - 1 ); d->hintLabel->move( pos ); d->hintLabel->show(); d->hintLabel->raise(); // do not show it forever //TQTimer::singleShot( 5000, d->hintLabel, TQT_SLOT( hide()) ); } bool FunctionCompletion::eventFilter( TQObject *obj, TQEvent *ev ) { if ( TQT_BASE_OBJECT(obj) == TQT_BASE_OBJECT(d->completionPopup) || TQT_BASE_OBJECT(obj) == TQT_BASE_OBJECT(d->completionListBox) ) { if ( ev->type() == TQEvent::KeyPress ) { TQKeyEvent *ke = (TQKeyEvent*)ev; if ( ke->key() == Key_Enter || ke->key() == Key_Return ) { doneCompletion(); return true; } else if ( ke->key() == Key_Left || ke->key() == Key_Right || ke->key() == Key_Up || ke->key() == Key_Down || ke->key() == Key_Home || ke->key() == Key_End || ke->key() == Key_Prior || ke->key() == Key_Next ) return false; d->hintLabel->hide(); d->completionPopup->close(); d->editor->setFocus(); TQApplication::sendEvent( d->editor, ev ); return true; } if ( ev->type() == TQEvent::MouseButtonDblClick ) { doneCompletion(); return true; } } return false; } void FunctionCompletion::doneCompletion() { d->hintLabel->hide(); d->completionPopup->close(); d->editor->setFocus(); emit selectedCompletion( d->completionListBox->currentText() ); } void FunctionCompletion::showCompletion( const TQStringList &choices ) { if( !choices.count() ) return; d->completionListBox->clear(); for( unsigned i = 0; i < choices.count(); i++ ) new TQListBoxText( (TQListBox*)d->completionListBox, choices[i] ); d->completionListBox->setCurrentItem( 0 ); // size of the pop-up d->completionPopup->setMaximumHeight( 100 ); d->completionPopup->resize( d->completionListBox->sizeHint() + TQSize( d->completionListBox->verticalScrollBar()->width() + 4, d->completionListBox->horizontalScrollBar()->height() + 4 ) ); int h = d->completionListBox->height(); int w = d->completionListBox->width(); TQPoint pos = d->editor->globalCursorPosition(); // if popup is partially invisible, move to other position // FIXME check it if it works in Xinerama multihead int screen_num = TQApplication::desktop()->screenNumber( d->completionPopup ); TQRect screen = TQApplication::desktop()->screenGeometry( screen_num ); if( pos.y() + h > screen.y()+screen.height() ) pos.setY( pos.y() - h - d->editor->height() ); if( pos.x() + w > screen.x()+screen.width() ) pos.setX( screen.x()+screen.width() - w ); d->completionPopup->move( pos ); d->completionListBox->setFocus(); d->completionPopup->show(); } /**************************************************************************** * * CellEditor * ****************************************************************************/ class CellEditor::Private { public: Cell* cell; Canvas* canvas; KTextEdit* textEdit; FormulaEditorHighlighter* highlighter; FunctionCompletion* functionCompletion; TQTimer* functionCompletionTimer; TQPoint globalCursorPos; bool captureAllKeyEvents : 1; bool checkChoice : 1; bool updateChoice : 1; bool updatingChoice : 1; uint length; uint fontLength; uint length_namecell; uint length_text; uint currentToken; uint rangeCount; }; CellEditor::CellEditor( Cell* _cell, Canvas* _parent, bool captureAllKeyEvents, const char* _name ) : TQWidget( _parent, _name ) { d = new Private(); d->cell = _cell; d->canvas = _parent; d->textEdit = new KTextEdit(this); d->globalCursorPos = TQPoint(); d->captureAllKeyEvents = captureAllKeyEvents; d->checkChoice = true; d->updateChoice = true; d->updatingChoice = false; d->length = 0; d->fontLength = 0; d->length_namecell = 0; d->length_text = 0; d->currentToken = 0; d->rangeCount = 0; //TODO - Get rid of TQTextEdit margins, this doesn't seem easily possible in TQt 3.3, so a job for TQt 4 porting. d->textEdit->setHScrollBarMode(TQScrollView::AlwaysOff); d->textEdit->setVScrollBarMode(TQScrollView::AlwaysOff); d->textEdit->setFrameStyle(TQFrame::NoFrame); d->textEdit->setLineWidth(0); d->textEdit->installEventFilter( this ); d->highlighter = new FormulaEditorHighlighter(d->textEdit, _parent); d->functionCompletion = new FunctionCompletion( this ); d->functionCompletionTimer = new TQTimer( this ); connect( d->functionCompletion, TQT_SIGNAL( selectedCompletion( const TQString& ) ), TQT_SLOT( functionAutoComplete( const TQString& ) ) ); connect( d->textEdit, TQT_SIGNAL( textChanged() ), TQT_SLOT( checkFunctionAutoComplete() ) ); connect( d->functionCompletionTimer, TQT_SIGNAL( timeout() ), TQT_SLOT( triggerFunctionAutoComplete() ) ); if (!cell()->format()->multiRow(cell()->column(),cell()->row())) d->textEdit->setWordWrap(TQTextEdit::NoWrap); else d->textEdit->setWrapPolicy(TQTextEdit::AtWordOrDocumentBoundary); //TODO - Custom KTextEdit class which supports text completion /* d->textEdit->setFrame( false ); d->textEdit->setCompletionMode((TDEGlobalSettings::Completion)canvas()->view()->doc()->completionMode() ); d->textEdit->setCompletionObject( &canvas()->view()->doc()->completion(),true ); */ setFocusProxy( d->textEdit ); connect( d->textEdit, TQT_SIGNAL( cursorPositionChanged(int,int) ), this, TQT_SLOT (slotCursorPositionChanged(int,int))); connect( d->textEdit, TQT_SIGNAL( cursorPositionChanged(TQTextCursor*) ), this, TQT_SLOT (slotTextCursorChanged(TQTextCursor*))); connect( d->textEdit, TQT_SIGNAL( textChanged() ), this, TQT_SLOT( slotTextChanged() ) ); // connect( d->textEdit, TQT_SIGNAL(completionModeChanged( TDEGlobalSettings::Completion )),this,TQT_SLOT (slotCompletionModeChanged(TDEGlobalSettings::Completion))); // A choose should always start at the edited cell // canvas()->setChooseMarkerRow( canvas()->markerRow() ); // canvas()->setChooseMarkerColumn( canvas()->markerColumn() ); // set font size according to zoom factor TQFont font( _cell->format()->font() ); font.setPointSizeFloat( 0.01 * _parent->doc()->zoom() * font.pointSizeFloat() ); d->textEdit->setFont( font ); if (d->fontLength == 0) { TQFontMetrics fm( d->textEdit->font() ); d->fontLength = fm.width('x'); } } CellEditor::~CellEditor() { canvas()->endChoose(); delete d->highlighter; delete d->functionCompletion; delete d->functionCompletionTimer; delete d; } Cell* CellEditor::cell() const { return d->cell; } Canvas* CellEditor::canvas() const { return d->canvas; } TQPoint CellEditor::globalCursorPosition() const { return d->globalCursorPos; } void CellEditor::checkFunctionAutoComplete() { d->functionCompletionTimer->stop(); d->functionCompletionTimer->start( 2000, true ); } void CellEditor::triggerFunctionAutoComplete() { // tokenize the expression (don't worry, this is very fast) int para = 0, curPos = 0; d->textEdit->getCursorPosition( ¶, &curPos ); TQString subtext = d->textEdit->text().left( curPos ); KSpread::Formula f; KSpread::Tokens tokens = f.scan( subtext ); if( !tokens.valid() ) return; if( tokens.count()<1 ) return; KSpread::Token lastToken = tokens[ tokens.count()-1 ]; // last token must be an identifier if( !lastToken.isIdentifier() ) return; TQString id = lastToken.text(); if( id.length() < 1 ) return; // find matches in function names TQStringList fnames = KSpread::FunctionRepository::self()->functionNames(); TQStringList choices; for( unsigned i=0; ifunctionCompletion->showCompletion( choices ); } void CellEditor::functionAutoComplete( const TQString& item ) { if( item.isEmpty() ) return; int para = 0, curPos = 0; d->textEdit->getCursorPosition( ¶, &curPos ); TQString subtext = text().left( curPos ); KSpread::Formula f; KSpread::Tokens tokens = f.scan( subtext ); if( !tokens.valid() ) return; if( tokens.count()<1 ) return; KSpread::Token lastToken = tokens[ tokens.count()-1 ]; if( !lastToken.isIdentifier() ) return; d->textEdit->blockSignals( true ); d->textEdit->setSelection( 0, lastToken.pos()+1, 0, lastToken.pos()+lastToken.text().length()+1 ); d->textEdit->insert( item ); d->textEdit->blockSignals( false ); } void CellEditor::slotCursorPositionChanged(int /* para */, int pos) { // kdDebug() << k_funcinfo << endl; // TODO Stefan: optimize this function! // turn choose mode on/off if (!checkChoice()) return; d->highlighter->rehighlight(); Tokens tokens = d->highlighter->formulaTokens(); uint rangeCounter = 0; uint currentRange = 0; uint regionStart = 0; uint regionEnd = 0; bool lastWasASemicolon = false; d->currentToken = 0; uint rangeCount = d->highlighter->rangeCount(); d->rangeCount = rangeCount; Token token; Token::Type type; // search the current token // determine the subregion number, btw for (uint i = 0; i < tokens.count(); ++i) { if (tokens[i].pos() >= pos - 1) // without '=' { /* kdDebug() << "token.pos >= cursor.pos" << endl;*/ type = tokens[i].type(); if (type == Token::Cell || type == Token::Range) { if (lastWasASemicolon) { regionEnd = rangeCounter++; lastWasASemicolon = false; continue; } } if (type == Token::Operator && tokens[i].asOperator() == Token::Semicolon) { lastWasASemicolon = true; continue; } lastWasASemicolon = false; break; } token = tokens[i]; d->currentToken = i; type = token.type(); if (type == Token::Cell || type == Token::Range) { if (!lastWasASemicolon) { regionStart = rangeCounter; } regionEnd = rangeCounter; currentRange = rangeCounter++; } // semicolons are use as deliminiters in regions if (type == Token::Operator) { if (token.asOperator() == Token::Semicolon) { lastWasASemicolon = true; } else { lastWasASemicolon = false; // set the region start to the next element regionStart = currentRange + 1; regionEnd = regionStart - 1; // len = 0 } } } // kdDebug() << "regionStart = " << regionStart/* << endl*/ // << ", regionEnd = " << regionEnd/* << endl*/ // << ", currentRange = " << currentRange << endl; d->canvas->choice()->setActiveElement(currentRange); d->canvas->choice()->setActiveSubRegion(regionStart, regionEnd-regionStart+1); // triggered by keyboard action? if (!d->updatingChoice) { if (d->highlighter->rangeChanged()) { d->highlighter->resetRangeChanged(); disconnect( d->canvas->choice(), TQT_SIGNAL(changed(const Region&)), d->canvas->view(), TQT_SLOT(slotScrollChoice(const Region&)) ); d->canvas->doc()->emitBeginOperation(); setUpdateChoice(false); Tokens tokens = d->highlighter->formulaTokens(); d->canvas->choice()->update(); // set the old one dirty d->canvas->choice()->clear(); Region tmpRegion; Region::ConstIterator it; //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 //colours. TQValueList alreadyUsedRegions; for (uint i = 0; i < tokens.count(); ++i) { Token token = tokens[i]; Token::Type type = token.type(); if (type == Token::Cell || type == Token::Range) { Region region(d->canvas->view(), token.text()); it = region.constBegin(); if (!alreadyUsedRegions.contains(region)) { TQRect r=(*it)->rect(); if (d->canvas->choice()->isEmpty()) d->canvas->choice()->initialize((*it)->rect(), (*it)->sheet()); else d->canvas->choice()->extend((*it)->rect(), (*it)->sheet()); alreadyUsedRegions.append(region); } } } setUpdateChoice(true); d->canvas->doc()->emitEndOperation(*d->canvas->choice()); connect( d->canvas->choice(), TQT_SIGNAL(changed(const Region&)), d->canvas->view(), TQT_SLOT(slotScrollChoice(const Region&)) ); } } } void CellEditor::slotTextCursorChanged(TQTextCursor* cursor) { TQTextStringChar *chr = cursor->paragraph()->at( cursor->index() ); int h = cursor->paragraph()->lineHeightOfChar( cursor->index() ); int x = cursor->paragraph()->rect().x() + chr->x; int y, dummy; cursor->paragraph()->lineHeightOfChar( cursor->index(), &dummy, &y ); y += cursor->paragraph()->rect().y(); d->globalCursorPos = d->textEdit->mapToGlobal( d->textEdit-> contentsToViewport( TQPoint( x, y + h ) ) ); } void CellEditor::cut() { d->textEdit->cut(); } void CellEditor::paste() { d->textEdit->paste(); } void CellEditor::copy() { d->textEdit->copy(); } void CellEditor::setEditorFont(TQFont const & font, bool updateSize) { TQFont tmpFont( font ); tmpFont.setPointSizeFloat( 0.01 * canvas()->doc()->zoom() * tmpFont.pointSizeFloat() ); d->textEdit->setFont( tmpFont ); if (updateSize) { TQFontMetrics fm( d->textEdit->font() ); d->fontLength = fm.width('x'); int mw = fm.width( d->textEdit->text() ) + d->fontLength; // don't make it smaller: then we would have to repaint the obscured cells if (mw < width()) mw = width(); int mh = fm.height(); if (mh < height()) mh = height(); setGeometry(x(), y(), mw, mh); } } void CellEditor::slotCompletionModeChanged(TDEGlobalSettings::Completion _completion) { canvas()->view()->doc()->setCompletionMode( _completion ); } void CellEditor::slotTextChanged() { // kdDebug() << k_funcinfo << endl; //FIXME - text() may return richtext? TQString t = text(); if (t.length() > d->length) { d->length = t.length(); TQFontMetrics fm(d->textEdit->font()); // - requiredWidth = width of text plus some spacer characters int requiredWidth = fm.width(t) + (2*fm.width('x')); //For normal single-row cells, the text editor must be expanded horizontally to //allow the text to fit if the new text is too wide //For multi-row (word-wrap enabled) cells, the text editor must expand vertically to //allow for new rows of text & the width of the text editor is not affected if (d->textEdit->wordWrap() == TQTextEdit::NoWrap) { if (requiredWidth > width()) { if (t.isRightToLeft()) { setGeometry(x() - requiredWidth + width(), y(), requiredWidth,height()); } else { setGeometry(x(), y(), requiredWidth,height()); } } } else { int requiredHeight = d->textEdit->heightForWidth(width()); if (requiredHeight > height()) { setGeometry(x(), y(), width(), requiredHeight); } } /* // allocate more space than needed. Otherwise it might be too slow d->length = t.length(); // Too slow for long texts // TQFontMetrics fm( d->textEdit->font() ); // int mw = fm.width( t ) + fm.width('x'); int mw = d->fontLength * d->length; if (mw < width()) mw = width(); if (t.isRightToLeft()) setGeometry(x() - mw + width(), y(), mw, height()); else setGeometry(x(), y(), mw, height()); d->length -= 2; */ } if ( (cell()->formatType()) == Percentage_format ) { if ( (t.length() == 1) && t[0].isDigit() ) { TQString tmp = t + " %"; d->textEdit->setText(tmp); d->textEdit->setCursorPosition(0,1); return; } } canvas()->view()->editWidget()->setText( t ); // canvas()->view()->editWidget()->setCursorPosition( d->textEdit->cursorPosition() ); } void CellEditor::setCheckChoice(bool state) { d->checkChoice = state; } bool CellEditor::checkChoice() { if (!d->checkChoice) return false; // // prevent recursion // d->checkChoice = false; // TODO nescessary? d->length_namecell = 0; d->currentToken = 0; TQString text = d->textEdit->text(); if ( text[0] != '=' ) { canvas()->setChooseMode(false); } else { int para, cur; d->textEdit->getCursorPosition(¶, &cur); Tokens tokens = d->highlighter->formulaTokens(); // empty formula? if (tokens.count() < 1) { canvas()->startChoose(); } else { Token token; for (uint i = 0; i < tokens.count(); ++i) { if (tokens[i].pos() >= cur - 1) // without '=' { break; } token = tokens[i]; d->currentToken = i; } Token::Type type = token.type(); if (type == Token::Operator && token.asOperator() != Token::RightPar) { canvas()->setChooseMode(true); } else if (type == Token::Cell || type == Token::Range) { d->length_namecell = token.text().length(); canvas()->setChooseMode(true); } else { canvas()->setChooseMode(false); } } } // d->checkChoice = true; return true; } void CellEditor::setUpdateChoice(bool state) { d->updateChoice = state; } void CellEditor::updateChoice() { // kdDebug() << k_funcinfo << endl; if (!d->updateChoice) return; // // prevent recursion // d->updateChoice = false; // TODO nescessary? d->updatingChoice = true; Selection* choice = d->canvas->choice(); if (choice->isEmpty()) return; if (!choice->activeElement()) return; // only one element TODO if (++choice->constBegin() == choice->constEnd()) { } TQString name_cell = choice->activeSubRegionName(); Tokens tokens = d->highlighter->formulaTokens(); uint start = 1; uint length = 0; if (!tokens.empty()) { 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(); } else { start = token.pos() + token.text().length() + 1; } } d->length_namecell = name_cell.length(); d->length_text = text().length(); //kdDebug(36001) << "updateChooseMarker2 len=" << d->length_namecell << endl; TQString oldText = text(); TQString newText = oldText.left(start) + name_cell + oldText.right(d->length_text - start - length); setCheckChoice( false ); setText( newText ); setCheckChoice( true ); setCursorPosition( start + d->length_namecell ); d->canvas->view()->editWidget()->setText( newText ); //kdDebug(36001) << "old=" << old << " len=" << d->length_namecell << " pos=" << pos << endl; // d->updateChoice = false; d->updatingChoice = false; } void CellEditor::resizeEvent( TQResizeEvent* ) { d->textEdit->setGeometry( 0, 0, width(), height() ); } void CellEditor::handleKeyPressEvent( TQKeyEvent * _ev ) { if (_ev->key() == TQt::Key_F4) { if (d->textEdit == 0) { TQApplication::sendEvent( d->textEdit, _ev ); return; } TQRegExp exp("(\\$?)([a-zA-Z]+)(\\$?)([0-9]+)$"); int para,cur; d->textEdit->getCursorPosition(¶,&cur); // int cur = d->textEdit->cursorPosition(); TQString tmp, tmp2; int n = -1; // this is ugly, and sort of hack // FIXME rewrite to use the real Tokenizer unsigned i; for( i = 0; i < 10; i++ ) { tmp = d->textEdit->text().left( cur+i ); tmp2 = d->textEdit->text().right( d->textEdit->text().length() - cur - i ); n = exp.search(tmp); if( n >= 0 ) break; } if (n == -1) return; TQString newPart; if ((exp.cap(1) == "$") && (exp.cap(3) == "$")) newPart = "$" + exp.cap(2) + exp.cap(4); else if ((exp.cap(1) != "$") && (exp.cap(3) != "$")) newPart = "$" + exp.cap(2) + "$" + exp.cap(4); else if ((exp.cap(1) == "$") && (exp.cap(3) != "$")) newPart = exp.cap(2) + "$" + exp.cap(4); else if ((exp.cap(1) != "$") && (exp.cap(3) == "$")) newPart = exp.cap(2) + exp.cap(4); TQString newString = tmp.left(n); newString += newPart; cur = newString.length() - i; newString += tmp2; d->textEdit->setText(newString); d->textEdit->setCursorPosition( 0, cur ); _ev->accept(); return; } // Send the key event to the KLineEdit TQApplication::sendEvent( d->textEdit, _ev ); } void CellEditor::handleIMEvent( TQIMEvent * _ev ) { // send the IM event to the KLineEdit TQApplication::sendEvent( d->textEdit, _ev ); } TQString CellEditor::text() const { return d->textEdit->text(); } void CellEditor::setText(TQString text) { d->textEdit->setText(text); //Usability : It is usually more convenient if the cursor is positioned at the end of the text so it can //be quickly deleted using the backspace key //This also ensures that the caret is sized correctly for the text d->textEdit->setCursorPosition(0,text.length()); if (d->fontLength == 0) { TQFontMetrics fm( d->textEdit->font() ); d->fontLength = fm.width('x'); } } int CellEditor::cursorPosition() const { int para,cur; d->textEdit->getCursorPosition(¶,&cur); return cur; // return d->textEdit->cursorPosition(); } void CellEditor::setCursorPosition( int pos ) { d->textEdit->setCursorPosition(0,pos); canvas()->view()->editWidget()->setCursorPosition( pos ); } bool CellEditor::eventFilter( TQObject* o, TQEvent* e ) { // Only interested in KTextEdit if ( TQT_BASE_OBJECT(o) != TQT_BASE_OBJECT(d->textEdit) ) return false; if ( e->type() == TQEvent::FocusOut ) { canvas()->setLastEditorWithFocus( Canvas::CellEditor ); return false; } if ( e->type() == TQEvent::KeyPress || e->type() == TQEvent::KeyRelease ) { TQKeyEvent* k = (TQKeyEvent*)e; if (!(k->state() & TQt::ShiftButton) || canvas()->chooseMode()) { //If the user presses the return key to finish editing this cell, choose mode must be turned off first //otherwise it will merely select a different cell if (k->key() == Key_Return || k->key() == Key_Enter) { kdDebug() << "CellEditor::eventFilter: canvas()->endChoose();" << endl; canvas()->endChoose(); } //NB - Added check for Key_Return when migrating text edit from KLineEdit to KTextEdit, since //normal behaviour for KTextEdit is to swallow return key presses if ( k->key() == Key_Up || k->key() == Key_Down || k->key() == Key_Next || k->key() == Key_Prior || k->key() == Key_Escape || k->key() == Key_Tab || k->key() == Key_Return || k->key() == Key_Enter) { // Send directly to canvas TQApplication::sendEvent( parent(), e ); return true; } } else if ( k->state() & TQt::ShiftButton && ( k->key() == Key_Return || k->key() == Key_Enter ) ) { // enable content wrapping d->cell->format()->setMultiRow( true ); } // End choosing. May be restarted by CellEditor::slotTextChanged if ( e->type() == TQEvent::KeyPress && !k->text().isEmpty() ) { canvas()->setChooseMode(false); } // forward Left/Right keys - so that pressing left/right in this // editor leaves editing mode ... otherwise editing is annoying // left/right arrows still work with the F2-editor. // Forward left & right arrows to parent, unless this editor has been set to capture arrow key events // Changed to this behaviour for consistancy with OO Calc & MS Office. if ( ((k->key() == TQt::Key_Left) || (k->key() == TQt::Key_Right)) && (!d->captureAllKeyEvents)) { TQApplication::sendEvent (parent(), e); return true; } } return false; } void CellEditor::setCursorToRange(uint pos) { // kdDebug() << k_funcinfo << endl; d->updatingChoice = true; uint counter = 0; Tokens tokens = d->highlighter->formulaTokens(); for (uint i = 0; i < tokens.count(); ++i) { Token token = tokens[i]; Token::Type type = token.type(); if (type == Token::Cell || type == Token::Range) { if (counter == pos) { setCursorPosition(token.pos() + token.text().length() + 1); } counter++; } } d->updatingChoice = false; } /***************************************************************************** * * ComboboxLocationEditWidget * ****************************************************************************/ ComboboxLocationEditWidget::ComboboxLocationEditWidget( TQWidget * _parent, View * _view ) : KComboBox( _parent, "ComboboxLocationEditWidget" ) { m_locationWidget = new LocationEditWidget( _parent, _view ); setLineEdit( m_locationWidget ); insertItem( "" ); TQValueList::Iterator it; TQValueList area = _view->doc()->listArea(); for ( it = area.begin(); it != area.end(); ++it ) slotAddAreaName( (*it).ref_name); connect( this, TQT_SIGNAL( activated ( const TQString & ) ), m_locationWidget, TQT_SLOT( slotActivateItem() ) ); } void ComboboxLocationEditWidget::slotAddAreaName( const TQString &_name) { insertItem( _name ); m_locationWidget->addCompletionItem( _name ); } void ComboboxLocationEditWidget::slotRemoveAreaName( const TQString &_name ) { for ( int i = 0; iremoveCompletionItem( _name ); } /***************************************************************************** * * LocationEditWidget * ****************************************************************************/ LocationEditWidget::LocationEditWidget( TQWidget * _parent, View * _view ) : KLineEdit( _parent, "LocationEditWidget" ), m_pView(_view) { setCompletionObject( &completionList,true ); setCompletionMode(TDEGlobalSettings::CompletionAuto ); } void LocationEditWidget::addCompletionItem( const TQString &_item ) { kdDebug()<<" LocationEditWidget::addCompletionItem add :"<<_item<::Iterator it; TQValueList area = m_pView->doc()->listArea(); for ( it = area.begin(); it != area.end(); ++it ) { if ((*it).ref_name == tmp) { TQString tmp = (*it).sheet_name; tmp += "!"; tmp += util_rangeName((*it).rect); m_pView->selectionInfo()->initialize( Region(m_pView,tmp) ); return true; } } // Set the cell component to uppercase: // Sheet1!a1 -> Sheet1!A2 int pos = ltext.find('!'); if ( pos !=- 1 ) tmp = ltext.left(pos)+ltext.mid(pos).upper(); else tmp = ltext.upper(); // Selection entered in location widget if ( ltext.contains( ':' ) ) m_pView->selectionInfo()->initialize( Region(m_pView,tmp) ); // Location entered in location widget else { Region region(m_pView,tmp); bool validName = true; for (unsigned int i = 0; i < ltext.length(); ++i) { if (!ltext[i].isLetter()) { validName = false; break; } } if ( !region.isValid() && validName) { TQRect rect( m_pView->selectionInfo()->selection() ); Sheet * t = m_pView->activeSheet(); // set area name on current selection/cell m_pView->doc()->addAreaName(rect, ltext.lower(), t->sheetName()); } if (!validName) { m_pView->selectionInfo()->initialize(region); } } // Set the focus back on the canvas. m_pView->canvasWidget()->setFocus(); return false; } void LocationEditWidget::keyPressEvent( TQKeyEvent * _ev ) { // Do not handle special keys and accelerators. This is // done by TQLineEdit. if ( _ev->state() & ( TQt::AltButton | TQt::ControlButton ) ) { TQLineEdit::keyPressEvent( _ev ); // Never allow that keys are passed on to the parent. _ev->accept(); return; } // Handle some special keys here. Eve switch( _ev->key() ) { case Key_Return: case Key_Enter: { if ( activateItem() ) return; _ev->accept(); } break; // Escape pressed, restore original value case Key_Escape: // #### Torben says: This is duplicated code. Bad. if ( m_pView->selectionInfo()->isSingular() ) { setText( Cell::columnName( m_pView->canvasWidget()->markerColumn() ) + TQString::number( m_pView->canvasWidget()->markerRow() ) ); } else { setText( Cell::columnName( m_pView->selectionInfo()->lastRange().left() ) + TQString::number( m_pView->selectionInfo()->lastRange().top() ) + ":" + Cell::columnName( m_pView->selectionInfo()->lastRange().right() ) + TQString::number( m_pView->selectionInfo()->lastRange().bottom() ) ); } m_pView->canvasWidget()->setFocus(); _ev->accept(); break; default: TQLineEdit::keyPressEvent( _ev ); // Never allow that keys are passed on to the parent. _ev->accept(); } } /**************************************************************** * * EditWidget * The line-editor that appears above the sheet and allows to * edit the cells content. * ****************************************************************/ EditWidget::EditWidget( TQWidget *_parent, Canvas *_canvas, TQButton *cancelButton, TQButton *okButton ) : TQLineEdit( _parent, "EditWidget" ) { m_pCanvas = _canvas; Q_ASSERT(m_pCanvas != NULL); // Those buttons are created by the caller, so that they are inserted // properly in the layout - but they are then managed here. m_pCancelButton = cancelButton; m_pOkButton = okButton; isArray = false; installEventFilter(m_pCanvas); if ( !m_pCanvas->doc()->isReadWrite() || !m_pCanvas->activeSheet() ) setEnabled( false ); TQObject::connect( m_pCancelButton, TQT_SIGNAL( clicked() ), this, TQT_SLOT( slotAbortEdit() ) ); TQObject::connect( m_pOkButton, TQT_SIGNAL( clicked() ), this, TQT_SLOT( slotDoneEdit() ) ); setEditMode( false ); // disable buttons } void EditWidget::showEditWidget(bool _show) { if (_show) { m_pCancelButton->show(); m_pOkButton->show(); show(); } else { m_pCancelButton->hide(); m_pOkButton->hide(); hide(); } } void EditWidget::slotAbortEdit() { m_pCanvas->deleteEditor( false /*discard changes*/ ); // will take care of the buttons } void EditWidget::slotDoneEdit() { m_pCanvas->deleteEditor( true /*keep changes*/, isArray); isArray = false; // will take care of the buttons } void EditWidget::keyPressEvent ( TQKeyEvent* _ev ) { // Dont handle special keys and accelerators, except Enter ones if (( ( _ev->state() & ( TQt::AltButton | TQt::ControlButton ) ) || ( _ev->state() & TQt::ShiftButton ) || ( _ev->key() == Key_Shift ) || ( _ev->key() == Key_Control ) ) && (_ev->key() != Key_Return) && (_ev->key() != Key_Enter)) { TQLineEdit::keyPressEvent( _ev ); _ev->accept(); return; } if ( !m_pCanvas->doc()->isReadWrite() ) return; if ( !m_pCanvas->editor() ) { // Start editing the current cell m_pCanvas->createEditor( Canvas::CellEditor,false ); } CellEditor * cellEditor = (CellEditor*) m_pCanvas->editor(); switch ( _ev->key() ) { case Key_Down: case Key_Up: case Key_Return: case Key_Enter: cellEditor->setText( text()); // Don't allow to start a chooser when pressing the arrow keys // in this widget, since only up and down would work anyway. // This is why we call slotDoneEdit now, instead of sending // to the canvas. //TQApplication::sendEvent( m_pCanvas, _ev ); isArray = (_ev->state() & TQt::AltButton) && (_ev->state() & TQt::ControlButton); slotDoneEdit(); m_pCanvas->view()->updateEditWidget(); _ev->accept(); break; case Key_F2: cellEditor->setFocus(); cellEditor->setText( text()); cellEditor->setCursorPosition(cursorPosition()); break; default: TQLineEdit::keyPressEvent( _ev ); setFocus(); cellEditor->setCheckChoice( false ); cellEditor->setText( text() ); cellEditor->setCheckChoice( true ); cellEditor->setCursorPosition( cursorPosition() ); } } void EditWidget::setEditMode( bool mode ) { m_pCancelButton->setEnabled(mode); m_pOkButton->setEnabled(mode); } void EditWidget::focusOutEvent( TQFocusEvent* ev ) { //kdDebug(36001) << "EditWidget lost focus" << endl; // See comment about setLastEditorWithFocus m_pCanvas->setLastEditorWithFocus( Canvas::EditWidget ); TQLineEdit::focusOutEvent( ev ); } void EditWidget::setText( const TQString& t ) { if ( t == text() ) // Why this? (David) return; TQLineEdit::setText( t ); } #include "kspread_editors.moc"