diff options
author | Michele Calgaro <[email protected]> | 2021-05-23 20:48:35 +0900 |
---|---|---|
committer | Michele Calgaro <[email protected]> | 2021-05-29 15:16:28 +0900 |
commit | 8b78a8791bc539bcffe7159f9d9714d577cb3d7d (patch) | |
tree | 1328291f966f19a22d7b13657d3f01a588eb1083 /kspread/kspread_editors.cpp | |
parent | 95834e2bdc5e01ae1bd21ac0dfa4fa1d2417fae9 (diff) | |
download | koffice-8b78a8791bc539bcffe7159f9d9714d577cb3d7d.tar.gz koffice-8b78a8791bc539bcffe7159f9d9714d577cb3d7d.zip |
Renaming of files in preparation for code style tools.
Signed-off-by: Michele Calgaro <[email protected]>
Diffstat (limited to 'kspread/kspread_editors.cpp')
-rw-r--r-- | kspread/kspread_editors.cpp | 1589 |
1 files changed, 1589 insertions, 0 deletions
diff --git a/kspread/kspread_editors.cpp b/kspread/kspread_editors.cpp new file mode 100644 index 00000000..4af76159 --- /dev/null +++ b/kspread/kspread_editors.cpp @@ -0,0 +1,1589 @@ +/* This file is part of the KDE project + + Copyright 1999-2006 The KSpread Team <[email protected]> + + 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 <tdelistbox.h> + +#include <tqapplication.h> +#include <tqlistbox.h> +#include <tqtimer.h> +#include <tqlabel.h> +#include <tqvbox.h> +#include <tqvaluelist.h> +#include <tqrichtext_p.h> + +//#include <klineedit.h> +#include <ktextedit.h> +#include <tqapplication.h> +#include <tqbutton.h> +#include <tqfont.h> +#include <tqfontmetrics.h> +#include <tqregexp.h> +#include <kdebug.h> + +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<TQColor> colors = d->canvas->choice()->colors(); + TQValueList<Range> 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; + TDEListBox *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 TDEListBox( 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("</qt>").prepend("<qt>"); + 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; i<fnames.count(); i++ ) + if( fnames[i].startsWith( id, false ) ) + choices.append( fnames[i] ); + choices.sort(); + + // 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[0].lower() == id.lower() ) + return; + + // present the user with completion choices + d->functionCompletion->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<Region> 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<Reference>::Iterator it; + TQValueList<Reference> 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; i<count(); i++ ) + { + if ( text(i)==_name ) + { + removeItem( i ); + break; + } + } + m_locationWidget->removeCompletionItem( _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<<endl; + if ( completionList.items().contains( _item) == 0 ) + { + completionList.addItem( _item ); + kdDebug()<<" _utem :"<<_item<<endl; + kdDebug()<<" completionList.items().count()"<<completionList.items().count()<<endl; + } +} + +void LocationEditWidget::removeCompletionItem( const TQString &_item ) +{ + completionList.removeItem( _item ); +} + +void LocationEditWidget::slotActivateItem() +{ + activateItem(); +} + +bool LocationEditWidget::activateItem() +{ + TQString ltext = text(); + TQString tmp = ltext.lower(); + TQValueList<Reference>::Iterator it; + TQValueList<Reference> 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" |