diff options
Diffstat (limited to 'kspread/kspread_cell.cpp')
-rw-r--r-- | kspread/kspread_cell.cpp | 7348 |
1 files changed, 7348 insertions, 0 deletions
diff --git a/kspread/kspread_cell.cpp b/kspread/kspread_cell.cpp new file mode 100644 index 00000000..fca76bbe --- /dev/null +++ b/kspread/kspread_cell.cpp @@ -0,0 +1,7348 @@ +/* This file is part of the KDE project + + Copyright 2006 Stefan Nikolaus <[email protected]> + Copyright 2005 Raphael Langerhorst <[email protected]> + Copyright 2004-2005 Tomas Mecir <[email protected]> + Copyright 2004-2006 Inge Wallin <[email protected]> + Copyright 1999-2002,2004,2005 Laurent Montel <[email protected]> + Copyright 2002-2005 Ariya Hidayat <[email protected]> + Copyright 2001-2003 Philipp Mueller <[email protected]> + Copyright 2002-2003 Norbert Andres <[email protected]> + Copyright 2003 Reinhart Geiser <[email protected]> + Copyright 2003-2005 Meni Livne <[email protected]> + Copyright 2003 Peter Simonsson <[email protected]> + Copyright 1999-2002 David Faure <[email protected]> + Copyright 2000-2002 Werner Trobin <[email protected]> + Copyright 1999,2002 Harri Porten <[email protected]> + Copyright 2002 John Dailey <[email protected]> + Copyright 1998-2000 Torben Weis <[email protected]> + Copyright 2000 Bernd Wuebben <[email protected]> + Copyright 2000 Simon Hausmann <[email protected] + Copyright 1999 Stephan Kulow <[email protected]> + Copyright 1999 Michael Reiher <michael.reiher.gmx.de> + Copyright 1999 Boris Wedl <[email protected]> + Copyright 1998-1999 Reginald Stadlbauer <[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 <stdlib.h> +#include <ctype.h> +#include <float.h> +#include <math.h> + +#include <tqapplication.h> +#include <tqpopupmenu.h> +#include <tqregexp.h> + +#include "kspread_canvas.h" +#include "kspread_condition.h" +#include "kspread_doc.h" +#include "kspread_format.h" +#include "kspread_global.h" +#include "kspread_map.h" +#include "kspread_sheet.h" +#include "kspread_sheetprint.h" +#include "kspread_style.h" +#include "kspread_style_manager.h" +#include "kspread_util.h" +#include "ksploadinginfo.h" +#include "kspread_genvalidationstyle.h" +#include "kspread_locale.h" +#include "kspread_value.h" +#include "kspread_view.h" +#include "kspread_value.h" +#include "formula.h" +#include "selection.h" +#include "valueconverter.h" +#include "valueformatter.h" +#include "valueparser.h" + +#include <KoStyleStack.h> +#include <KoRect.h> +#include <KoXmlNS.h> +#include <KoDom.h> +#include <KoXmlWriter.h> +#include <KoOasisStyles.h> + +#include <tdemessagebox.h> + +#include <kdebug.h> + +using namespace KSpread; + +#define BORDER_SPACE 1 + + +/** + * A pointer to the decimal separator + */ + +namespace Cell_LNS +{ + TQChar decimal_point = '\0'; +} + +using namespace Cell_LNS; + + +// Some variables are placed in Cell::Extra because normally they're +// not required in simple case of cell(s). For example, most plain +// text cells don't need to store information about spanned columns +// and rows, as this is only the case with merged cells. +// +// When the cell is getting complex (e.g. merged with other cells, +// contains rich text, has validation criteria, etc), this Cell::Extra +// is allocated by Cell::Private and starts to be +// available. Otherwise, it won't exist at all. + +class Cell::Extra +{ +public: + Extra() {} + + // Not empty when the cell holds a link + TQString link; + + // Number of cells explicitly merged by the user in X and Y directions. + int mergedXCells; + int mergedYCells; + + // Number of additional cells. + int extraXCells; + int extraYCells; + + // If this cell overlaps other cells, then we have the cells width and + // height stored here. These values do not mean anything unless + // extraXCells and/or extraYCells are different from 0. + double extraWidth; + double extraHeight; + + // A list of cells that obscure this one. + // If this list is not empty, then this cell is obscured by another + // enlarged object. This means that we have to call this object in order + // of painting it for example instead of painting 'this'. + // + // FIXME (comment): If the list consists of more than one obscuring + // element, then is there an order between them that + // is important? + TQValueList<Cell*> obscuringCells; + + // If non-NULL, contains a pointer to a condition or a validity test. + Conditions *conditions; + Validity *validity; + + // Store the number of line when multirow is used (default is 0) + int nbLines; + +private: + // Don't allow implicit copy. + Extra& operator=( const Extra& ); +}; + + +class Cell::Private +{ +public: + + Private(); + ~Private(); + +public: + + // This cell's row and column. If either of them is 0, this is the + // default cell and its row/column can not be determined. Note that + // in the isDefault() method, only column is tested. + int row; + int column; + + // Value of the cell, either typed by user or as result of formula + Value value; + + // Holds the user's input. + // + // FIXME: + // Eventually, we'll want to get rid of strText and generate + // user's input on-the-fly. Then, for normal cells, we'll generate + // this string using converter()->asString + // (value()). + // + // Here the problem is, that strText also holds the formula - + // we'll need to provide some method to generate it from the + // parsed version, created in KSpread::Formula. Hence, we won't be + // able to get rid of strText until we switch to the new formula + // parser and until we write some method that re-generates the + // input formula... + // + // Alternately, we can keep using strText for formulas and + // generate it dynamically for static cells... + // + // /Tomas + // + TQString strText; + + // This is the text we want to display. Not necessarily the same + // as strText, e.g. strText="1" and strOutText="1.00" Also holds + // value that we got from calculation, formerly known as + // strFormulaOut + TQString strOutText; + + // the Formula object for the cell + KSpread::Formula *formula; + + // Position and dimension of displayed text. + // FIXME (comment): Which coordinate system? pixels? mm/cm? zoom? + double textX; + double textY; + double textWidth; + double textHeight; + + // result of "fm.ascent()" in makeLayout. used in offsetAlign. + int fmAscent; + + // Pointers to neighboring cells. + // FIXME (comment): Which order? + Cell *nextCell; + Cell *previousCell; + + bool hasExtra() const { return (cellExtra != 0); }; + Extra *extra(); + + Format *format; + TQ_UINT32 flags; + +private: + // "Extra stuff", see explanation for Cell::Extra. + Extra *cellExtra; +}; + + +Cell::Private::Private() +{ + // Some basic data. + row = 0; + column = 0; + value = Value::empty(); + formula = 0; + + // Formatting + textX = 0.0; + textY = 0.0; + textWidth = 0.0; + textHeight = 0.0; + fmAscent = 0; + + nextCell = 0; + previousCell = 0; + + // Default is to not have the "extra" stuff in a cell. + cellExtra = 0; + format = 0; + flags = 0; +} + + +Cell::Private::~Private() +{ + delete cellExtra; + delete formula; +} + + +Cell::Extra* Cell::Private::extra() +{ + if ( !cellExtra ) { + cellExtra = new Extra; + cellExtra->conditions = 0; + cellExtra->validity = 0; + + cellExtra->mergedXCells = 0; + cellExtra->mergedYCells = 0; + cellExtra->extraXCells = 0; + cellExtra->extraYCells = 0; + cellExtra->extraWidth = 0.0; + cellExtra->extraHeight = 0.0; + cellExtra->nbLines = 0; +// cellExtra->highlight = TQColor(0,0,0); + } + + return cellExtra; +} + + +/***************************************************************************** + * + * Cell + * + *****************************************************************************/ + + +Cell::Cell( Sheet * _sheet, int _column, int _row ) +{ + d = new Private; + d->row = _row; + d->column = _column; + d->format = new Format(_sheet, _sheet->doc()->styleManager()->defaultStyle()); + d->format->setCell(this); + clearAllErrors(); +} + + +Cell::Cell( Sheet * _sheet, Style * _style, int _column, int _row ) +{ + d = new Private; + d->row = _row; + d->column = _column; + d->format = new Format( _sheet, _style ); + d->format->setCell(this); + + clearAllErrors(); +} + +Format* Cell::format() const +{ + return d->format; +} + +// Return the sheet that this cell belongs to. +Sheet * Cell::sheet() const +{ + return d->format->sheet(); +} + +// Return true if this is the default cell. +bool Cell::isDefault() const +{ + return ( d->column == 0 ); +} + +// Return the row number of this cell. +int Cell::row() const +{ + // Make sure this isn't called for the default cell. This assert + // can save you (could have saved me!) the hassle of some very + // obscure bugs. + + if ( isDefault() ) + { + kdWarning(36001) << "Error: Calling Cell::row() for default cell" << endl; + return 0; + } + + return d->row; +} + + +// Return the column number of this cell. +// +int Cell::column() const +{ + // Make sure this isn't called for the default cell. This assert + // can save you (could have saved me!) the hassle of some very + // obscure bugs. + if ( isDefault() ) + { + kdWarning(36001) << "Error: Calling Cell::column() for default cell" << endl; + return 0; + } + return d->column; +} + + +// Return the name of this cell, i.e. the string that the user would +// use to reference it. Example: A1, BZ16 +// +TQString Cell::name() const +{ + return name( d->column, d->row ); +} + + +// Return the name of any cell given by (col, row). +// +TQString Cell::name( int col, int row ) +{ + return columnName( col ) + TQString::number( row ); +} + + +// Return the name of this cell, including the sheet name. +// Example: sheet1!A5 +// +TQString Cell::fullName() const +{ + return fullName( sheet(), d->column, d->row ); +} + + +// Return the full name of any cell given a sheet and (col, row). +// +TQString Cell::fullName( const Sheet* s, int col, int row ) +{ + return s->sheetName() + "!" + name( col, row ); +} + + +// Return the symbolic name of the column of this cell. Examples: A, BB. +// +TQString Cell::columnName() const +{ + return columnName( d->column ); +} + +TDELocale* Cell::locale() const +{ + return d->format->sheet()->doc()->locale(); +} + +// Return the symbolic name of any column. +// +TQString Cell::columnName( uint column ) +{ + TQString str; + unsigned digits = 1; + unsigned offset = 0; + + column--; + + if( column > 4058115285U ) return TQString("@@@"); + + for( unsigned limit = 26; column >= limit+offset; limit *= 26, digits++ ) + offset += limit; + + for( unsigned c = column - offset; digits; --digits, c/=26 ) + str.prepend( TQChar( 'A' + (c%26) ) ); + + return str; +} + + +// Return true if this cell is a formula. +// +bool Cell::isFormula() const +{ + return d->strText[0] == '='; +} + + +// Return the input text of this cell. This could, for instance, be a +// formula. +// +// FIXME: These two functions are inconsistently named. It should be +// either text() and outText() or strText() and strOutText(). +// +TQString Cell::text() const +{ + return d->strText; +} + + +// Return the out text, i.e. the text that is visible in the cells +// square when shown. This could, for instance, be the calculated +// result of a formula. +// +TQString Cell::strOutText() const +{ + return d->strOutText; +} + +Formula *Cell::formula () const +{ + return d->formula; +} + + +// Return the value of this cell. +// +const Value Cell::value() const +{ + return d->value; +} + + +// Set the value of this cell. It also clears all errors if the value +// itself is not an error. +// +// In addition to this, it calculates the outstring and sets the dirty +// flags so that a redraw is forced. +// +void Cell::setValue( const Value& v ) +{ + if (v.type() != Value::Error) + clearAllErrors(); + + //If the value has not changed then we don't need to do anything + //(ie. no need to relayout, update dependant cells etc.), + //unless this cell contains a formula, in which case its dependancies might have changed + //even though the value has not. For example, if this cell was previously empty (and its value is + //therefore empty) and a new dependency upon an empty cell has been added. The new value would still + //be empty, but the dependencies need to be updated (via the call to valueChanged() below). + if ( ( d->value == v ) && ( !isFormula() ) ) + return; + + d->value = v; + + setFlag(Flag_LayoutDirty); + setFlag(Flag_TextFormatDirty); + + // Format and set the outText. + setOutputText(); + + // Set the displayed text, if we hold an error value. + if (d->value.type() == Value::Error) + d->strOutText = d->value.errorMessage (); + + // Value of the cell has changed - trigger necessary actions + valueChanged (); + + if ( !format()->sheet()->isLoading() ) + format()->sheet()->setRegionPaintDirty(cellRect()); +} + +void Cell::setCellValue (const Value &v, FormatType fmtType, const TQString &txt) +{ + if ( !txt.isNull() ) + { + d->strText = txt; + if ( isFormula() ) + makeFormula(); + } + else if ( !isFormula() ) + d->strText = sheet()->doc()->converter()->asString (v).asString(); + if (fmtType != No_format) + format()->setFormatType (fmtType); + setValue (v); +} + +// FIXME: Continue commenting and cleaning here (ingwa) + + +Cell* Cell::previousCell() const +{ + return d->previousCell; +} + +Cell* Cell::nextCell() const +{ + return d->nextCell; +} + +void Cell::setPreviousCell( Cell* c ) +{ + d->previousCell = c; +} + +void Cell::setNextCell( Cell* c ) +{ + d->nextCell = c; +} + +Validity* Cell::getValidity( int newStruct ) +{ + if ( (!newStruct) && (!d->hasExtra())) + //we don't have validity struct and we don't want one + return 0; + + if( ( d->extra()->validity == 0 ) && ( newStruct == -1 ) ) + d->extra()->validity = new Validity; + return d->extra()->validity; +} + +void Cell::removeValidity() +{ + if (!d->hasExtra()) + return; + + delete d->extra()->validity; + d->extra()->validity = 0; +} + + +void Cell::copyFormat( const int column , const int row ) +{ + const Cell * cell = format()->sheet()->cellAt( column , row ); + + copyFormat( cell ); +} + +void Cell::copyFormat( const Cell* cell ) +{ + + Q_ASSERT(cell); + + d->value.setFormat(cell->d->value.format()); + format()->copy(*(cell->format())); + + /*format()->setAlign( cell->format()->align( _column, _row ) ); + format()->setAlignY( cell->format()->alignY( _column, _row ) ); + format()->setTextFont( cell->format()->textFont( _column, _row ) ); + format()->setTextColor( cell->format()->textColor( _column, _row ) ); + format()->setBgColor( cell->bgColor( _column, _row) ); + setLeftBorderPen( cell->leftBorderPen( _column, _row ) ); + setTopBorderPen( cell->topBorderPen( _column, _row ) ); + setBottomBorderPen( cell->bottomBorderPen( _column, _row ) ); + setRightBorderPen( cell->rightBorderPen( _column, _row ) ); + format()->setFallDiagonalPen( cell->format()->fallDiagonalPen( _column, _row ) ); + format()->setGoUpDiagonalPen( cell->format()->goUpDiagonalPen( _column, _row ) ); + format()->setBackGroundBrush( cell->backGroundBrush( _column, _row) ); + format()->setPrecision( cell->format()->precision( _column, _row ) ); + format()->setPrefix( cell->format()->prefix( _column, _row ) ); + format()->setPostfix( cell->format()->postfix( _column, _row ) ); + format()->setFloatFormat( cell->format()->floatFormat( _column, _row ) ); + format()->setFloatColor( cell->format()->floatColor( _column, _row ) ); + format()->setMultiRow( cell->format()->multiRow( _column, _row ) ); + format()->setVerticalText( cell->format()->verticalText( _column, _row ) ); + format()->setDontPrintText( cell->format()->getDontprintText(_column, _row ) ); + format()->setNotProtected( cell->format()->notProtected(_column, _row ) ); + format()->setHideAll(cell->format()->isHideAll(_column, _row ) ); + format()->setHideFormula(cell->format()->isHideFormula(_column, _row ) ); + format()->setIndent( cell->format()->getIndent(_column, _row ) ); + format()->setAngle( cell->format()->getAngle(_column, _row) ); + format()->setFormatType( cell->format()->getFormatType(_column, _row) ); + Format::Currency c; + if ( cell->format()->currencyInfo( c ) ) + format()->setCurrency( c );*/ + + TQValueList<Conditional> conditionList = cell->conditionList(); + if (d->hasExtra()) + delete d->extra()->conditions; + if ( cell->d->hasExtra() && cell->d->extra()->conditions ) + setConditionList( conditionList ); + else + if (d->hasExtra()) + d->extra()->conditions = 0; + + /*format()->setComment( cell->format()->comment( _column, _row ) );*/ +} + +void Cell::copyAll( Cell *cell ) +{ + Q_ASSERT( !isDefault() ); // trouble ahead... + copyFormat( cell ); + copyContent( cell ); +} + +void Cell::copyContent( const Cell* cell ) +{ + Q_ASSERT( !isDefault() ); // trouble ahead... + + if (cell->isFormula() && cell->column() > 0 && cell->row() > 0) + { + // change all the references, e.g. from A1 to A3 if copying + // from e.g. B2 to B4 + TQString d = cell->encodeFormula(); + setCellText( cell->decodeFormula( d ) ); + } + else + setCellText( cell->text() ); + +} + +void Cell::defaultStyle() +{ + format()->defaultStyleFormat(); + + if (!d->hasExtra()) + return; + + if ( d->extra()->conditions ) + { + delete d->extra()->conditions; + d->extra()->conditions = 0; + } + + delete d->extra()->validity; + d->extra()->validity = 0L; +} + + +// Merge a number of cells, i.e. make this cell obscure a number of +// other cells. If _x and _y == 0, then the merging is removed. + +void Cell::mergeCells( int _col, int _row, int _x, int _y ) +{ + // Start by unobscuring the cells that we obscure right now + int extraXCells = d->hasExtra() ? d->extra()->extraXCells : 0; + int extraYCells = d->hasExtra() ? d->extra()->extraYCells : 0; + for ( int x = _col; x <= _col + extraXCells; ++x ) { + for ( int y = _row; y <= _row + extraYCells; ++y ) { + if ( x != _col || y != _row ) + format()->sheet()->nonDefaultCell( x, y )->unobscure(this); + } + } + + // If no merging, then remove all traces, and return. + if ( _x == 0 && _y == 0 ) { + clearFlag( Flag_Merged ); + if (d->hasExtra()) { + d->extra()->extraXCells = 0; + d->extra()->extraYCells = 0; + d->extra()->extraWidth = 0.0; + d->extra()->extraHeight = 0.0; + d->extra()->mergedXCells = 0; + d->extra()->mergedYCells = 0; + } + + // Refresh the layout + setFlag( Flag_LayoutDirty ); + return; + } + + // At this point, we know that we will merge some cells. + setFlag(Flag_Merged); + d->extra()->extraXCells = _x; + d->extra()->extraYCells = _y; + d->extra()->mergedXCells = _x; + d->extra()->mergedYCells = _y; + + // Obscure the cells + for ( int x = _col; x <= _col + _x; ++x ) { + for ( int y = _row; y <= _row + _y; ++y ) { + if ( x != _col || y != _row ) + format()->sheet()->nonDefaultCell( x, y )->obscure( this, true ); + } + } + + // Refresh the layout + setFlag( Flag_LayoutDirty ); +} + +void Cell::move( int col, int row ) +{ + setLayoutDirtyFlag(); + setCalcDirtyFlag(); + setDisplayDirtyFlag(); + + //int ex = extraXCells(); + //int ey = d->extra()->extraYCells(); + + if (d->hasExtra()) + d->extra()->obscuringCells.clear(); + + // Unobscure the objects we obscure right now + int extraXCells = d->hasExtra() ? d->extra()->extraXCells : 0; + int extraYCells = d->hasExtra() ? d->extra()->extraYCells : 0; + for( int x = d->column; x <= d->column + extraXCells; ++x ) + for( int y = d->row; y <= d->row + extraYCells; ++y ) + if ( x != d->column || y != d->row ) + { + Cell *cell = format()->sheet()->nonDefaultCell( x, y ); + cell->unobscure(this); + } + + d->column = col; + d->row = row; + + if (d->hasExtra()) + { + // d->extra()->extraXCells = 0; + // d->extra()->extraYCells = 0; + d->extra()->mergedXCells = 0; + d->extra()->mergedYCells = 0; + } + + // Cell value has been changed (because we're another cell now). + valueChanged (); +} + +void Cell::setLayoutDirtyFlag( bool format ) +{ + setFlag( Flag_LayoutDirty ); + if ( format ) + setFlag( Flag_TextFormatDirty ); + + if (!d->hasExtra()) + return; + + TQValueList<Cell*>::iterator it = d->extra()->obscuringCells.begin(); + TQValueList<Cell*>::iterator end = d->extra()->obscuringCells.end(); + for ( ; it != end; ++it ) { + (*it)->setLayoutDirtyFlag( format ); + } +} + +bool Cell::needsPrinting() const +{ + if ( isDefault() ) + return false; + + if ( !d->strText.stripWhiteSpace().isEmpty() ) { + return true; + } + + // Cell borders? + if ( format()->hasProperty( Format::PTopBorder ) + || format()->hasProperty( Format::PLeftBorder ) + || format()->hasProperty( Format::PRightBorder ) + || format()->hasProperty( Format::PBottomBorder ) + || format()->hasProperty( Format::PFallDiagonal ) + || format()->hasProperty( Format::PGoUpDiagonal ) ) { + return true; + } + + // Background color or brush? + if ( format()->hasProperty( Format::PBackgroundBrush ) ) { + + const TQBrush& brush=backGroundBrush(column(),row()); + + //Only brushes that are visible (ie. they have a brush style and are not white) + //need to be drawn + if ( (brush.style() != TQt::NoBrush) && + (brush.color() != TQt::white || brush.pixmap()) ) + return true; + + } + + if ( format()->hasProperty( Format::PBackgroundColor ) ) { + kdDebug() << "needsPrinting: Has background colour" << endl; + TQColor backgroundColor=bgColor(column(),row()); + + //We don't need to print anything if the background is white + if (backgroundColor != TQt::white) + return true; + } + + return false; +} + +bool Cell::isEmpty() const +{ + return isDefault() || d->strText.isEmpty(); +} + + +// Return true if this cell is obscured by some other cell. + +bool Cell::isObscured() const +{ + if (!d->hasExtra()) + return false; + + return !( d->extra()->obscuringCells.isEmpty() ); +} + + +// Return true if this cell is part of a merged cell, but not the +// master cell. + +bool Cell::isPartOfMerged() const +{ + if (!d->hasExtra()) + return false; + + TQValueList<Cell*>::const_iterator it = d->extra()->obscuringCells.begin(); + TQValueList<Cell*>::const_iterator end = d->extra()->obscuringCells.end(); + for ( ; it != end; ++it ) { + Cell *cell = *it; + + if (cell->doesMergeCells()) { + // The cell might merge extra cells, and then overlap even + // beyond that so just knowing that the obscuring cell merges + // extra isn't enough. We have to know that this cell is one of + // the ones it is forcing over. + if (column() <= cell->column() + cell->d->extra()->mergedXCells + && row() <= cell->row() + cell->mergedYCells() ) + return true; + } + } + + return false; +} + + +// Return the cell that obscures this one. If no cell is obscuring, +// then return this. This method is slightly complicated because we +// can have several layers of obscuring. +// +// Update: it seems that if we do an actual merge, then the obscuring +// cell is prepended and if just expanding, then it is appended. This +// means that we should be able to just look at the first one. + +Cell *Cell::ultimateObscuringCell() const +{ + if (!d->hasExtra()) + return (Cell *) this; + + else if (d->extra()->obscuringCells.isEmpty()) + return (Cell *) this; + + else + return d->extra()->obscuringCells.first(); + +#if 0 + TQValueList<Cell*>::const_iterator it = d->extra()->obscuringCells.begin(); + TQValueList<Cell*>::const_iterator end = d->extra()->obscuringCells.end(); + for ( ; it != end; ++it ) { + Cell *cell = *it; + + if (cell->doesMergeCells()) { + // The cell might merge extra cells, and then overlap even + // beyond that so just knowing that the obscuring cell merges + // extra isn't enough. We have to know that this cell is one of + // the ones it is forcing over. + if (column() <= cell->column() + cell->d->extra()->mergedXCells + && row() <= cell->row() + cell->mergedYCells() ) + return true; + } + } + + return false; +#endif +} + + +TQValueList<Cell*> Cell::obscuringCells() const +{ + if (!d->hasExtra()) + { + TQValueList<Cell*> empty; + return empty; + } + return d->extra()->obscuringCells; +} + +void Cell::clearObscuringCells() +{ + if (!d->hasExtra()) + return; + d->extra()->obscuringCells.clear(); +} + +void Cell::obscure( Cell *cell, bool isForcing ) +{ + if (d->hasExtra()) + { + d->extra()->obscuringCells.remove( cell ); // removes *all* occurrences + cell->clearObscuringCells(); + } + if ( isForcing ) + { + d->extra()->obscuringCells.prepend( cell ); + } + else + { + d->extra()->obscuringCells.append( cell ); + } + setFlag(Flag_LayoutDirty); + format()->sheet()->setRegionPaintDirty( cellRect() ); +} + +void Cell::unobscure( Cell * cell ) +{ + if (d->hasExtra()) + d->extra()->obscuringCells.remove( cell ); + setFlag( Flag_LayoutDirty ); + format()->sheet()->setRegionPaintDirty( cellRect() ); +} + +TQString Cell::encodeFormula( bool _era, int _col, int _row ) const +{ + if ( _col == -1 ) + _col = d->column; + if ( _row == -1 ) + _row = d->row; + + TQString erg = ""; + + if(d->strText.isEmpty()) + return d->strText; + + bool fix1 = false; + bool fix2 = false; + bool onNumber = false; + unsigned int pos = 0; + const unsigned int length = d->strText.length(); + + // All this can surely be made 10 times faster, but I just "ported" it to TQString + // without any attempt to optimize things -- this is really brittle (Werner) + while ( pos < length ) + { + if ( d->strText[pos] == '"' ) + { + erg += d->strText[pos++]; + while ( pos < length && d->strText[pos] != '"' ) // till the end of the world^H^H^H "string" + { + erg += d->strText[pos++]; + // Allow escaped double quotes (\") + if ( pos < length && d->strText[pos] == '\\' && d->strText[pos+1] == '"' ) + { + erg += d->strText[pos++]; + erg += d->strText[pos++]; + } + } + if ( pos < length ) // also copy the trailing double quote + erg += d->strText[pos++]; + + onNumber = false; + } + else if ( d->strText[pos].isDigit() ) + { + erg += d->strText[pos++]; + fix1 = fix2 = false; + onNumber = true; + } + else if ( d->strText[pos] != '$' && !d->strText[pos].isLetter() ) + { + erg += d->strText[pos++]; + fix1 = fix2 = false; + onNumber = false; + } + else + { + TQString tmp = ""; + if ( d->strText[pos] == '$' ) + { + tmp = "$"; + pos++; + fix1 = true; + } + if ( d->strText[pos].isLetter() ) + { + TQString buffer; + unsigned int pos2 = 0; + while ( pos < length && d->strText[pos].isLetter() ) + { + tmp += d->strText[pos]; + buffer[pos2++] = d->strText[pos++]; + } + if ( d->strText[pos] == '$' ) + { + tmp += "$"; + pos++; + fix2 = true; + } + if ( d->strText[pos].isDigit() ) + { + const unsigned int oldPos = pos; + while ( pos < length && d->strText[pos].isDigit() ) ++pos; + int row = 0; + if ( pos != oldPos ) + row = d->strText.mid(oldPos, pos-oldPos).toInt(); + // Is it a sheet name || is it a function name like DEC2HEX + /* or if we're parsing a number, this could just be the + exponential part of it (1.23E4) */ + if ( ( d->strText[pos] == '!' ) || + d->strText[pos].isLetter() || + onNumber ) + { + erg += tmp; + fix1 = fix2 = false; + pos = oldPos; + } + else // It must be a cell identifier + { + //now calculate the row as integer value + int col = 0; + col = util_decodeColumnLabelText( buffer ); + if ( fix1 ) + erg += TQString( "$%1" ).arg( col ); + else + if (_era) + erg += TQChar(0xA7) + TQString( "%1" ).arg( col ); + else + erg += TQString( "#%1" ).arg( col - _col ); + + if ( fix2 ) + erg += TQString( "$%1#").arg( row ); + else + if (_era) + erg += TQChar(0xA7) + TQString( "%1#" ).arg( row ); + else + erg += TQString( "#%1#" ).arg( row - _row ); + } + } + else + { + erg += tmp; + fix1 = fix2 = false; + } + } + else + { + erg += tmp; + fix1 = false; + } + onNumber = false; + } + } + + return erg; +} + +TQString Cell::decodeFormula( const TQString &_text, int _col, int _row) const +{ + if ( _col == -1 ) + _col = d->column; + if ( _row == -1 ) + _row = d->row; + + TQString erg = ""; + unsigned int pos = 0; + const unsigned int length = _text.length(); + + if ( _text.isEmpty() ) + return TQString(); + + while ( pos < length ) + { + if ( _text[pos] == '"' ) + { + erg += _text[pos++]; + while ( pos < length && _text[pos] != '"' ) + { + erg += _text[pos++]; + // Allow escaped double quotes (\") + if ( pos < length && _text[pos] == '\\' && _text[pos+1] == '"' ) + { + erg += _text[pos++]; + erg += _text[pos++]; + } + } + if ( pos < length ) + erg += _text[pos++]; + } + else if ( _text[pos] == '#' || _text[pos] == '$' || _text[pos] == TQChar(0xA7)) + { + bool abs1 = false; + bool abs2 = false; + bool era1 = false; // if 1st is relative but encoded absolutely + bool era2 = false; + + TQChar _t = _text[pos++]; + if ( _t == '$' ) + abs1 = true; + else if ( _t == TQChar(0xA7) ) + era1 = true; + + int col = 0; + unsigned int oldPos = pos; + while ( pos < length && ( _text[pos].isDigit() || _text[pos] == '-' ) ) ++pos; + if ( pos != oldPos ) + col = _text.mid(oldPos, pos-oldPos).toInt(); + if ( !abs1 && !era1 ) + col += _col; + // Skip '#' or '$' + + _t = _text[pos++]; + if ( _t == '$' ) + abs2 = true; + else if ( _t == TQChar(0xA7) ) + era2 = true; + + int row = 0; + oldPos = pos; + while ( pos < length && ( _text[pos].isDigit() || _text[pos] == '-' ) ) ++pos; + if ( pos != oldPos ) + row = _text.mid(oldPos, pos-oldPos).toInt(); + if ( !abs2 && !era2) + row += _row; + // Skip '#' or '$' + ++pos; + if ( row < 1 || col < 1 || row > KS_rowMax || col > KS_colMax ) + { + kdDebug(36001) << "Cell::decodeFormula: row or column out of range (col: " << col << " | row: " << row << ")" << endl; + erg = "=\"#### " + i18n("REFERENCE TO COLUMN OR ROW IS OUT OF RANGE") + "\""; + return erg; + } + if ( abs1 ) + erg += "$"; + erg += Cell::columnName(col); //Get column text + + if ( abs2 ) + erg += "$"; + erg += TQString::number( row ); + } + else + erg += _text[pos++]; + } + + return erg; +} + + +void Cell::freeAllObscuredCells() +{ + // + // Free all obscured cells. + // + + if (!d->hasExtra()) + return; + + for ( int x = d->column + d->extra()->mergedXCells; + x <= d->column + d->extra()->extraXCells; ++x ) { + for ( int y = d->row + d->extra()->mergedYCells; + y <= d->row + d->extra()->extraYCells; ++y ) { + if ( x != d->column || y != d->row ) { + Cell *cell = format()->sheet()->cellAt( x, y ); + cell->unobscure(this); + } + } + } + + d->extra()->extraXCells = d->extra()->mergedXCells; + d->extra()->extraYCells = d->extra()->mergedYCells; + +} + + +// ---------------------------------------------------------------- +// Layout + + +// Recalculate the entire layout. This includes the following members: +// +// d->textX, d->textY +// d->textWidth, d->textHeight +// d->fmAscent +// d->extra()->extraXCells, d->extra()->extraYCells +// d->extra()->extraWidth, d->extra()->extraHeight +// d->extra()->nbLines (if multirow) +// +// and, of course, +// +// d->strOutText +// + +void Cell::makeLayout( TQPainter &_painter, int _col, int _row ) +{ + // Are _col and _row really needed ? + // + // Yes they are: they are useful if this is the default layout, in + // which case d->row and d->column are 0 and 0, but _col and _row + // are the real coordinates of the cell. + + // There is no need to remake the layout if it hasn't changed. + if ( !testFlag( Flag_LayoutDirty ) ) + return; + + // Some initializations. + if (d->hasExtra()) + d->extra()->nbLines = 0; + clearFlag( Flag_CellTooShortX ); + clearFlag( Flag_CellTooShortY ); + + // Initiate the cells that this one is obscuring to the ones that + // are actually merged. + freeAllObscuredCells(); + if (d->hasExtra()) + mergeCells( d->column, d->row, + d->extra()->mergedXCells, d->extra()->mergedYCells ); + + // If the column for this cell is hidden or the row is too low, + // there is no use in remaking the layout. + ColumnFormat *cl1 = format()->sheet()->columnFormat( _col ); + RowFormat *rl1 = format()->sheet()->rowFormat( _row ); + if ( cl1->isHide() + || ( rl1->dblHeight() <= format()->sheet()->doc()->unzoomItY( 2 ) ) ) { + clearFlag( Flag_LayoutDirty ); + return; + } + + // Recalculate the output text, d->strOutText. + setOutputText(); + + // Empty text? Reset the outstring and, if this is the default + // cell, return. + if ( d->strOutText.isEmpty() ) { + d->strOutText = TQString(); + + if ( isDefault() ) { + clearFlag( Flag_LayoutDirty ); + return; + } + } + + // Up to here, we have just cared about the contents, not the + // painting of it. Now it is time to see if the contents fits into + // the cell and, if not, maybe rearrange the outtext a bit. + // + // First, Determine the correct font with zoom taken into account, + // and apply it to _painter. Then calculate text dimensions, i.e. + // d->textWidth and d->textHeight. + applyZoomedFont( _painter, _col, _row ); + textSize( _painter ); + + // + // Calculate the size of the cell + // + RowFormat *rl = format()->sheet()->rowFormat( d->row ); + ColumnFormat *cl = format()->sheet()->columnFormat( d->column ); + + double width = cl->dblWidth(); + double height = rl->dblHeight(); + + // Calculate extraWidth and extraHeight if we have a merged cell. + if ( testFlag( Flag_Merged ) ) { + int extraXCells = d->hasExtra() ? d->extra()->extraXCells : 0; + int extraYCells = d->hasExtra() ? d->extra()->extraYCells : 0; + + // FIXME: Introduce double extraWidth/Height here and use them + // instead (see FIXME about this in paintCell()). + + for ( int x = _col + 1; x <= _col + extraXCells; x++ ) + width += format()->sheet()->columnFormat( x )->dblWidth(); + + for ( int y = _row + 1; y <= _row + extraYCells; y++ ) + height += format()->sheet()->rowFormat( y )->dblHeight(); + } + + // Cache the newly calculated extraWidth and extraHeight if we have + // already allocated a struct for it. Otherwise it will be zero, so + // don't bother. + if (d->hasExtra()) { + d->extra()->extraWidth = width; + d->extra()->extraHeight = height; + } + + TQFontMetrics fm = _painter.fontMetrics(); + d->fmAscent = fm.ascent(); + + // Check if we need to break the line into multiple lines and are + // allowed to do so. If so, set `lines' to the number of lines that + // are needed to fit into the total width of the combined cell. + // + // Also recalculate d->textHeight, d->textWidth, d->extra->nbLines + // and d->strOutText. + // + int lines = 1; + if ( d->textWidth > (width - 2 * BORDER_SPACE + - format()->leftBorderWidth( _col, _row ) + - format()->rightBorderWidth( _col, _row ) ) + && format()->multiRow( _col, _row ) ) + { + // Copy of d->strOutText but without the newlines. +// TQString o = d->strOutText.replace( TQChar('\n'), " " ); + + // don't remove the existing LF, these are intended line wraps (whishlist #9881) + TQString o = d->strOutText; + + // Break the line at appropriate places, i.e. spaces, if + // necessary. This means to change the spaces where breaks occur + // into newlines. + if ( o.find(' ') != -1 ) + { + d->strOutText = ""; + + // Make sure that we have a space at the end. + o += ' '; + + int start = 0; // Start of the line we are handling now + int breakpos = 0; // The next candidate pos to break the string + int pos1 = 0; + int availableWidth = (int) ( width - 2 * BORDER_SPACE + - format()->leftBorderWidth( _col, _row ) + - format()->rightBorderWidth( _col, _row ) ); + + do { + + breakpos = o.find( ' ', breakpos ); + int linefeed = o.find( '\n', pos1 ); + +// kdDebug() << "start: " << start << "; breakpos: " << breakpos << "; pos1: " << pos1 << "; linefeed: " << linefeed << endl; + + //don't miss LF as a position to calculate current lineWidth + int work_breakpos = breakpos; + if (pos1 < linefeed && linefeed < breakpos) + work_breakpos = linefeed; + + double lineWidth = format()->sheet()->doc() + ->unzoomItX( fm.width( d->strOutText.mid( start, (pos1 - start) ) + + o.mid( pos1, work_breakpos - pos1 ) ) ); + + //linefeed could be -1 when no linefeed is found! + if (breakpos > linefeed && linefeed > 0) + { +// kdDebug() << "applying linefeed to start;" << endl; + start = linefeed; + lines++; + } + + if ( lineWidth <= availableWidth ) { + // We have room for the rest of the line. End it here. + d->strOutText += o.mid( pos1, breakpos - pos1 ); + pos1 = breakpos; + } + else { + // Still not enough room. Try to split further. + if ( o.at( pos1 ) == ' ' ) + pos1++; + + if ( pos1 != 0 && breakpos != -1 ) { + d->strOutText += "\n" + o.mid( pos1, breakpos - pos1 ); + lines++; + } + else + d->strOutText += o.mid( pos1, breakpos - pos1 ); + + start = pos1; + pos1 = breakpos; + } + + breakpos++; + } while( o.find( ' ', breakpos ) != -1 ); + } + else + { + lines = o.contains('\n'); + } + + d->textHeight *= lines; + if (lines > 1) + d->extra()->nbLines = lines; + + d->textX = 0.0; + + // Calculate the maximum width, taking into account linebreaks, + // and put it in d->textWidth. + TQString t; + int i; + int pos = 0; + d->textWidth = 0.0; + do { + i = d->strOutText.find( "\n", pos ); + + if ( i == -1 ) + t = d->strOutText.mid( pos, d->strOutText.length() - pos ); + else { + t = d->strOutText.mid( pos, i - pos ); + pos = i + 1; + } + + double tw = format()->sheet()->doc()->unzoomItX( fm.width( t ) ); + if ( tw > d->textWidth ) + d->textWidth = tw; + } while ( i != -1 ); + } + + // Calculate d->textX and d->textY + offsetAlign( _col, _row ); + + int a = effAlignX(); + + // Get indentation. This is only used for left aligned text. + double indent = 0.0; + if ( a == Format::Left && !isEmpty() ) + indent = format()->getIndent( _col, _row ); + + // Set Flag_CellTooShortX if the text is vertical or angled, and too + // high for the cell. + if ( format()->verticalText( _col, _row ) || format()->getAngle( _col, _row ) != 0 ) { + //RowFormat *rl = format()->sheet()->rowFormat( _row ); + + if ( d->textHeight >= height/*rl->dblHeight()*/ ) + setFlag( Flag_CellTooShortX ); + } + + // Do we have to occupy additional cells to the right? This is only + // done for cells that have no merged cells in the Y direction. + // + // FIXME: Check if all cells along the merged edge to the right are + // empty and use the extra space? No, probably not. + // + if ( d->textWidth + indent > ( width - 2 * BORDER_SPACE + - format()->leftBorderWidth( _col, _row ) + - format()->rightBorderWidth( _col, _row ) ) + && ( !d->hasExtra() || d->extra()->mergedYCells == 0 ) ) + { + int c = d->column; + + // Find free cells to the right of this one. + int end = 0; + while ( !end ) { + ColumnFormat *cl2 = format()->sheet()->columnFormat( c + 1 ); + Cell *cell = format()->sheet()->visibleCellAt( c + 1, d->row ); + + if ( cell->isEmpty() ) { + width += cl2->dblWidth() - 1; + c++; + + // Enough space? + if ( d->textWidth + indent <= ( width - 2 * BORDER_SPACE + - format()->leftBorderWidth( _col, _row ) + - format()->rightBorderWidth( _col, _row ) ) ) + end = 1; + } + else + // Not enough space, but the next cell is not empty + end = -1; + } + + // Try to use additional space from the neighboring cells that + // were calculated in the last step. This is the place that we + // set d->extra()->extraXCells and d->extra()->extraWidth. + // + // Currently this is only done for left aligned cells. We have to + // check to make sure we haven't already force-merged enough cells + // + // FIXME: Why not right/center aligned text? + // + // FIXME: Shouldn't we check to see if end == -1 here before + // setting Flag_CellTooShortX? + // + if ( format()->align( _col, _row ) == Format::Left + || ( format()->align( _col, _row ) == Format::Undefined + && !value().isNumber() ) ) + { + if ( c - d->column > d->extra()->mergedXCells ) { + d->extra()->extraXCells = c - d->column; + d->extra()->extraWidth = width; + for ( int i = d->column + 1; i <= c; ++i ) { + Cell *cell = format()->sheet()->nonDefaultCell( i, d->row ); + cell->obscure( this ); + } + + // Not enough space + if ( end == -1 ) + setFlag( Flag_CellTooShortX ); + } + else + setFlag( Flag_CellTooShortX ); + } + else + setFlag( Flag_CellTooShortX ); + } + + // Do we have to occupy additional cells at the bottom ? + // + // FIXME: Setting to make the current cell grow. + // + if ( format()->multiRow( _col, _row ) + && d->textHeight > ( height - 2 * BORDER_SPACE + - format()->topBorderWidth( _col, _row ) + - format()->bottomBorderWidth( _col, _row ) ) ) + { + int r = d->row; + int end = 0; + + // Find free cells bottom to this one + while ( !end ) { + RowFormat *rl2 = format()->sheet()->rowFormat( r + 1 ); + Cell *cell = format()->sheet()->visibleCellAt( d->column, r + 1 ); + + if ( cell->isEmpty() ) { + height += rl2->dblHeight() - 1.0; + r++; + + // Enough space ? + if ( d->textHeight <= ( height - 2 * BORDER_SPACE + - format()->topBorderWidth( _col, _row ) + - format()->bottomBorderWidth( _col, _row ) ) ) + end = 1; + } + else + // Not enough space, but the next cell is not empty. + end = -1; + } + + // Check to make sure we haven't already force-merged enough cells. + if ( r - d->row > d->extra()->mergedYCells ) + { + d->extra()->extraYCells = r - d->row; + d->extra()->extraHeight = height; + + for ( int i = d->row + 1; i <= r; ++i ) + { + Cell *cell = format()->sheet()->nonDefaultCell( d->column, i ); + cell->obscure( this ); + } + + // Not enough space? + if ( end == -1 ) + setFlag( Flag_CellTooShortY ); + } + else + setFlag( Flag_CellTooShortY ); + } + + clearFlag( Flag_LayoutDirty ); + + return; +} + + +void Cell::valueChanged () +{ + update(); + + format()->sheet()->valueChanged (this); +} + + +// Recalculate d->strOutText. +// + +void Cell::setOutputText() +{ + if ( isDefault() ) { + d->strOutText = TQString(); + + if ( d->hasExtra() && d->extra()->conditions ) + d->extra()->conditions->checkMatches(); + + return; + } + + // If nothing has changed, we don't need to remake the text layout. + if ( !testFlag(Flag_TextFormatDirty) ) + return; + + // We don't want to remake the layout unnecessarily. + clearFlag( Flag_TextFormatDirty ); + + // Display a formula if warranted. If not, display the value instead; + // this is the most common case. + if ( (!hasError()) && isFormula() && format()->sheet()->getShowFormula() + && !( format()->sheet()->isProtected() && format()->isHideFormula( d->column, d->row ) ) + || isEmpty() ) + d->strOutText = d->strText; + else { + d->strOutText = sheet()->doc()->formatter()->formatText (this, + formatType()); + } + + // Check conditions if needed. + if ( d->hasExtra() && d->extra()->conditions ) + d->extra()->conditions->checkMatches(); +} + + +// Recalculate d->textX and d->textY. +// +// Used in makeLayout() and calculateTextParameters(). +// + +void Cell::offsetAlign( int _col, int _row ) +{ + int a; + Format::AlignY ay; + int tmpAngle; + bool tmpVerticalText; + bool tmpMultiRow; + + if ( d->hasExtra() + && d->extra()->conditions + && d->extra()->conditions->matchedStyle() ) + { + Style *style = d->extra()->conditions->matchedStyle(); + + if ( style->hasFeature( Style::SAlignX, true ) ) + a = style->alignX(); + else + a = format()->align( _col, _row ); + + if ( style->hasFeature( Style::SVerticalText, true ) ) + tmpVerticalText = style->hasProperty( Style::PVerticalText ); + else + tmpVerticalText = format()->verticalText( _col, _row ); + + if ( style->hasFeature( Style::SMultiRow, true ) ) + tmpMultiRow = style->hasProperty( Style::PMultiRow ); + else + tmpMultiRow = format()->multiRow( _col, _row ); + + if ( style->hasFeature( Style::SAlignY, true ) ) + ay = style->alignY(); + else + ay = format()->alignY( _col, _row ); + + if ( style->hasFeature( Style::SAngle, true ) ) + tmpAngle = style->rotateAngle(); + else + tmpAngle = format()->getAngle( _col, _row ); + } + else { + a = format()->align( _col, _row ); + ay = format()->alignY( _col, _row ); + tmpAngle = format()->getAngle( _col, _row ); + tmpVerticalText = format()->verticalText( _col, _row ); + tmpMultiRow = format()->multiRow( _col, _row ); + } + + RowFormat *rl = format()->sheet()->rowFormat( _row ); + ColumnFormat *cl = format()->sheet()->columnFormat( _col ); + + double w = cl->dblWidth(); + double h = rl->dblHeight(); + + if ( d->hasExtra() ) { + if ( d->extra()->extraXCells ) w = d->extra()->extraWidth; + if ( d->extra()->extraYCells ) h = d->extra()->extraHeight; + } + + const double effTop = BORDER_SPACE + 0.5 * effTopBorderPen( _col, _row ).width(); + const double effBottom = h - BORDER_SPACE - 0.5 * effBottomBorderPen( _col, _row ).width(); + + // Calculate d->textY based on the vertical alignment and a few + // other inputs. + switch( ay ) + { + case Format::Top: + { + if ( tmpAngle == 0 ) + { + d->textY = effTop + (double) d->fmAscent / format()->sheet()->doc()->zoomedResolutionY(); + } + else if ( tmpAngle < 0 ) + { + d->textY = effTop; + } + else + { + d->textY = effTop + + (double) d->fmAscent * cos( tmpAngle * M_PI / 180 ) / format()->sheet()->doc()->zoomedResolutionY(); + } + break; + } + case Format::Bottom: + { + if ( !tmpVerticalText && !tmpMultiRow && !tmpAngle ) + { + d->textY = effBottom; + } + else if ( tmpAngle != 0 ) + { + // Is enough place available? + if ( effBottom - effTop - d->textHeight > 0 ) + { + if ( tmpAngle < 0 ) + { + d->textY = effBottom - d->textHeight; + } + else + { + d->textY = effBottom - d->textHeight + + ( (double) d->fmAscent * cos( tmpAngle * M_PI / 180 ) / format()->sheet()->doc()->zoomedResolutionY() ); + } + } + else + { + if ( tmpAngle < 0 ) + { + d->textY = effTop; + } + else + { + d->textY = effTop + + ( (double) d->fmAscent * cos( tmpAngle * M_PI / 180 ) + / format()->sheet()->doc()->zoomedResolutionY() ); + } + } + } + else if ( tmpMultiRow && !tmpVerticalText ) + { + // Is enough place available? + if ( effBottom - effTop - d->textHeight > 0 ) + { + d->textY = effBottom - d->textHeight + + (double)d->fmAscent / format()->sheet()->doc()->zoomedResolutionY(); + } + else + { + d->textY = effTop + + (double) d->fmAscent / format()->sheet()->doc()->zoomedResolutionY(); + } + } + else + { + // Is enough place available? + if ( effBottom - effTop - d->textHeight > 0 ) + { + d->textY = effBottom - d->textHeight + + (double)d->fmAscent / format()->sheet()->doc()->zoomedResolutionY(); + } + else + { + d->textY = effTop + + (double) d->fmAscent / format()->sheet()->doc()->zoomedResolutionY(); + } + } + break; + } + case Format::Middle: + case Format::UndefinedY: + { + if ( !tmpVerticalText && !tmpMultiRow && !tmpAngle ) + { + d->textY = ( h - d->textHeight ) / 2 + + (double) d->fmAscent / format()->sheet()->doc()->zoomedResolutionY(); + } + else if ( tmpAngle != 0 ) + { + // Is enough place available? + if ( effBottom - effTop - d->textHeight > 0 ) + { + if ( tmpAngle < 0 ) + { + d->textY = ( h - d->textHeight ) / 2; + } + else + { + d->textY = ( h - d->textHeight ) / 2 + + (double) d->fmAscent * cos( tmpAngle * M_PI / 180 ) / format()->sheet()->doc()->zoomedResolutionY(); + } + } + else + { + if ( tmpAngle < 0 ) + { + d->textY = effTop; + } + else + { + d->textY = effTop + + ( (double)d->fmAscent * cos( tmpAngle * M_PI / 180 ) / format()->sheet()->doc()->zoomedResolutionY() ); + } + } + } + else if ( tmpMultiRow && !tmpVerticalText ) + { + // Is enough place available? + if ( effBottom - effTop - d->textHeight > 0 ) + { + d->textY = ( h - d->textHeight ) / 2 + + (double) d->fmAscent / format()->sheet()->doc()->zoomedResolutionY(); + } + else + { + d->textY = effTop + + (double) d->fmAscent / format()->sheet()->doc()->zoomedResolutionY(); + } + } + else + { + // Is enough place available? + if ( effBottom - effTop - d->textHeight > 0 ) + { + d->textY = ( h - d->textHeight ) / 2 + + (double) d->fmAscent / format()->sheet()->doc()->zoomedResolutionY(); + } + else + d->textY = effTop + + (double)d->fmAscent / format()->sheet()->doc()->zoomedResolutionY(); + } + break; + } + } + + a = effAlignX(); + if ( format()->sheet()->getShowFormula() && + !( format()->sheet()->isProtected() && format()->isHideFormula( _col, _row ) ) ) + { + a = Format::Left; + } + + // Calculate d->textX based on alignment and textwidth. + switch ( a ) { + case Format::Left: + d->textX = 0.5 * effLeftBorderPen( _col, _row ).width() + BORDER_SPACE; + break; + case Format::Right: + d->textX = w - BORDER_SPACE - d->textWidth + - 0.5 * effRightBorderPen( _col, _row ).width(); + break; + case Format::Center: + d->textX = 0.5 * ( w - BORDER_SPACE - d->textWidth - + 0.5 * effRightBorderPen( _col, _row ).width() ); + break; + } +} + + +// Recalculate the current text dimensions, i.e. d->textWidth and +// d->textHeight. +// +// Used in makeLayout() and calculateTextParameters(). +// +void Cell::textSize( TQPainter &_paint ) +{ + TQFontMetrics fm = _paint.fontMetrics(); + // Horizontal text ? + + int tmpAngle; + int _row = row(); + int _col = column(); + bool tmpVerticalText; + bool fontUnderlined; + Format::AlignY ay; + + // Set tmpAngle, tmpeVerticalText, ay and fontUnderlined according + // to if there is a matching condition or not. + if ( d->hasExtra() + && d->extra()->conditions + && d->extra()->conditions->matchedStyle() ) + { + Style *style = d->extra()->conditions->matchedStyle(); + + if ( style->hasFeature( Style::SAngle, true ) ) + tmpAngle = style->rotateAngle(); + else + tmpAngle = format()->getAngle( _col, _row ); + + if ( style->hasFeature( Style::SVerticalText, true ) ) + tmpVerticalText = style->hasProperty( Style::PVerticalText ); + else + tmpVerticalText = format()->verticalText( _col, _row ); + + if ( style->hasFeature( Style::SAlignY, true ) ) + ay = style->alignY(); + else + ay = format()->alignY( _col, _row ); + + if ( style->hasFeature( Style::SFontFlag, true ) ) + fontUnderlined = ( style->fontFlags() & (uint) Style::FUnderline ); + else + fontUnderlined = format()->textFontUnderline( _col, _row ); + } + else { + // The cell has no condition with a maxed style. + tmpAngle = format()->getAngle( _col, _row ); + tmpVerticalText = format()->verticalText( _col, _row ); + ay = format()->alignY( _col, _row ); + fontUnderlined = format()->textFontUnderline( _col, _row ); + } + + // Set d->textWidth and d->textHeight to correct values according to + // if the text is horizontal, vertical or rotated. + if ( !tmpVerticalText && !tmpAngle ) { + // Horizontal text. + + d->textWidth = format()->sheet()->doc()->unzoomItX( fm.width( d->strOutText ) ); + int offsetFont = 0; + if ( ( ay == Format::Bottom ) && fontUnderlined ) { + offsetFont = fm.underlinePos() + 1; + } + + d->textHeight = format()->sheet()->doc()->unzoomItY( fm.ascent() + fm.descent() + + offsetFont ); + } + else if ( tmpAngle!= 0 ) { + // Rotated text. + + d->textHeight = format()->sheet()->doc() + ->unzoomItY( int( cos( tmpAngle * M_PI / 180 ) + * ( fm.ascent() + fm.descent() ) + + abs( int( ( fm.width( d->strOutText ) + * sin( tmpAngle * M_PI / 180 ) ) ) ) ) ); + + d->textWidth = format()->sheet()->doc() + ->unzoomItX( int( abs( int( ( sin( tmpAngle * M_PI / 180 ) + * ( fm.ascent() + fm.descent() ) ) ) ) + + fm.width( d->strOutText ) + * cos ( tmpAngle * M_PI / 180 ) ) ); + } + else { + // Vertical text. + int width = 0; + for ( unsigned int i = 0; i < d->strOutText.length(); i++ ) + width = TQMAX( width, fm.width( d->strOutText.at( i ) ) ); + + d->textWidth = format()->sheet()->doc()->unzoomItX( width ); + d->textHeight = format()->sheet()->doc()->unzoomItY( ( fm.ascent() + fm.descent() ) + * d->strOutText.length() ); + } +} + + +// Get the effective font to use after the zooming and apply it to `painter'. +// +// Used in makeLayout() and calculateTextParameters(). +// + +void Cell::applyZoomedFont( TQPainter &painter, int _col, int _row ) +{ + TQFont tmpFont( format()->textFont( _col, _row ) ); + + // If there is a matching condition on this cell then set the + // according style parameters. + if ( d->hasExtra() + && d->extra()->conditions + && d->extra()->conditions->matchedStyle() ) { + + Style * s = d->extra()->conditions->matchedStyle(); + + // Other size? + if ( s->hasFeature( Style::SFontSize, true ) ) + tmpFont.setPointSizeFloat( s->fontSize() ); + + // Other attributes? + if ( s->hasFeature( Style::SFontFlag, true ) ) { + uint flags = s->fontFlags(); + + tmpFont.setBold( flags & (uint) Style::FBold ); + tmpFont.setUnderline( flags & (uint) Style::FUnderline ); + tmpFont.setItalic( flags & (uint) Style::FItalic ); + tmpFont.setStrikeOut( flags & (uint) Style::FStrike ); + } + + // Other family? + if ( s->hasFeature( Style::SFontFamily, true ) ) + tmpFont.setFamily( s->fontFamily() ); + } +#if 0 + else + /* + * could somebody please explaint why we check for isProtected or isHideFormula here + */ + if ( d->extra()->conditions + && d->extra()->conditions->currentCondition( condition ) + && !(format()->sheet()->getShowFormula() + && !( format()->sheet()->isProtected() + && format()->isHideFormula( d->column, d->row ) ) ) ) + { + if ( condition.fontcond ) + tmpFont = *(condition.fontcond); + else + tmpFont = condition.style->font(); + } +#endif + + // Scale the font size according to the current zoom. + tmpFont.setPointSizeFloat( 0.01 * format()->sheet()->doc()->zoom() + * tmpFont.pointSizeFloat() ); + + painter.setFont( tmpFont ); +} + + +//used in Sheet::adjustColumnHelper and Sheet::adjustRow +void Cell::calculateTextParameters( TQPainter &_painter, + int _col, int _row ) +{ + // Apply the correct font to _painter. + applyZoomedFont( _painter, _col, _row ); + + // Recalculate d->textWidth and d->textHeight + textSize( _painter ); + + // Recalculate d->textX and d->textY. + offsetAlign( _col, _row ); +} + + +// ---------------------------------------------------------------- +// Formula handling + + +bool Cell::makeFormula() +{ + clearFormula (); + + d->formula = new KSpread::Formula (sheet(), this); + d->formula->setExpression (d->strText); + + if (!d->formula->isValid ()) { + // Did a syntax error occur ? + clearFormula(); + + if (format()->sheet()->doc()->getShowMessageError()) + { + TQString tmp(i18n("Error in cell %1\n\n")); + tmp = tmp.arg( fullName() ); + KMessageBox::error( (TQWidget*)0L, tmp); + } + setFlag(Flag_ParseError); + Value v; + v.setError ( "####" ); + setValue (v); + return false; + } + + // we must recalc + setCalcDirtyFlag (); + + return true; +} + +void Cell::clearFormula() +{ + delete d->formula; + d->formula = 0L; +} + +bool Cell::calc(bool delay) +{ + if ( !isFormula() ) + return true; + + if (d->formula == 0) + { + if ( testFlag( Flag_ParseError ) ) // there was a parse error + return false; + else + { + /* we were probably at a "isLoading() = true" state when we originally + * parsed + */ + makeFormula (); + + if ( d->formula == 0 ) // there was a parse error + return false; + } + } + + if ( !testFlag( Flag_CalcDirty ) ) + return true; + + if ( delay ) + { + if ( format()->sheet()->doc()->delayCalculation() ) + return true; + } + + setFlag(Flag_LayoutDirty); + setFlag(Flag_TextFormatDirty); + clearFlag(Flag_CalcDirty); + + Value result = d->formula->eval (); + setValue (result); + if (result.isNumber()) + checkNumberFormat(); // auto-chooses number or scientific + + clearFlag(Flag_CalcDirty); + setFlag(Flag_LayoutDirty); + + return true; +} + + +// ================================================================ +// Painting + + +// Paint the cell. This is the main function that calls a lot of +// helper functions. +// +// `rect' is the rectangle that we should paint on. If the cell +// does not overlap this rectangle, we can return immediately. +// `coordinate' is the origin (the upper left) of the cell in document +// coordinates. +// + +void Cell::paintCell( const KoRect &rect, TQPainter & painter, + View *view, + const KoPoint &coordinate, + const TQPoint &cellRef, + int paintBorder, + TQPen & rightPen, TQPen & bottomPen, + TQPen & leftPen, TQPen & topPen, + TQValueList<TQPoint> &mergedCellsPainted, + bool drawCursor ) +{ + bool paintBorderRight = paintBorder & Border_Right; + bool paintBorderBottom = paintBorder & Border_Bottom; + bool paintBorderLeft = paintBorder & Border_Left; + bool paintBorderTop = paintBorder & Border_Top; + + // If we are already painting this cell, then return immediately. + // This avoids infinite recursion. + if ( testFlag( Flag_PaintingCell ) ) + return; + + // Indicate that we are painting this cell now. + setFlag( Flag_PaintingCell ); + + // This flag indicates that we are working on drawing the cells that + // another cell is obscuring. The value is the number of levels down we + // are currently working -- i.e. a cell obscured by a cell which is + // obscured by a cell. + static int paintingObscured = 0; + +#if 0 + if (paintingObscured == 0) + kdDebug(36001) << "painting cell " << name() << endl; + else + kdDebug(36001) << " painting obscured cell " << name() << endl; +#endif + + // Sanity check: If we're working on drawing an obscured cell, that + // means this cell should have a cell that obscures it. + Q_ASSERT(!(paintingObscured > 0 && d->extra()->obscuringCells.isEmpty())); + + // The parameter cellref should be *this, unless this is the default cell. + Q_ASSERT(isDefault() + || (((cellRef.x() == d->column) && (cellRef.y() == d->row)))); + + Sheet::LayoutDirection sheetDir = format()->sheet()->layoutDirection(); + + double left = coordinate.x(); + + ColumnFormat * colFormat = format()->sheet()->columnFormat( cellRef.x() ); + RowFormat * rowFormat = format()->sheet()->rowFormat( cellRef.y() ); + + // Set width, height to the total width and height that this cell + // covers, including obscured cells, and width0, height0 to the + // width and height of this cell, maybe merged but never implicitly + // extended. + double width0 = colFormat->dblWidth(); + double height0 = rowFormat->dblHeight(); + double width = width0; + double height = height0; + + // Handle right-to-left layout. + // In an RTL sheet the cells have to be painted at their opposite horizontal + // location on the canvas, meaning that column A will be the rightmost column + // on screen, column B will be to the left of it and so on. Here we change + // the horizontal coordinate at which we start painting the cell in case the + // sheet's direction is RTL. We do this only if paintingObscured is 0, + // otherwise the cell's painting location will flip back and forth in + // consecutive calls to paintCell when painting obscured cells. + if ( sheetDir == Sheet::RightToLeft && paintingObscured == 0 + && view && view->canvasWidget() ) + { + double dwidth = view->doc()->unzoomItX(view->canvasWidget()->width()); + left = dwidth - coordinate.x() - width; + } + + // See if this cell is merged or has overflown into neighbor cells. + // In that case, the width/height is greater than just the cell + // itself. + if (d->hasExtra()) { + if (d->extra()->mergedXCells > 0 || d->extra()->mergedYCells > 0) { + // merged cell extends to the left if sheet is RTL + if ( sheetDir == Sheet::RightToLeft ) { + left -= d->extra()->extraWidth - width; + } + width0 = d->extra()->extraWidth; + height0 = d->extra()->extraHeight; + width = d->extra()->extraWidth; + height = d->extra()->extraHeight; + } + else { +#if 0 + width += d->extra()->extraXCells ? d->extra()->extraWidth : 0; + height += d->extra()->extraYCells ? d->extra()->extraHeight : 0; +#else + // FIXME: Make extraWidth/Height really contain the *extra* width/height. + if ( d->extra()->extraXCells ) + width = d->extra()->extraWidth; + if ( d->extra()->extraYCells ) + height = d->extra()->extraHeight; +#endif + } + } + + // Check if the cell is "selected", i.e. it should be drawn with the + // color that indicates selection (dark blue). If more than one + // square is selected, the last one uses the ordinary colors. In + // that case, "selected" will be set to false even though the cell + // itself really is selected. + bool selected = false; + if ( view != NULL ) { + selected = view->selectionInfo()->contains( cellRef ); + + // But the cell doesn't look selected if this is the marker cell. + Cell *cell = format()->sheet()->cellAt( view->selectionInfo()->marker() ); + TQPoint bottomRight( view->selectionInfo()->marker().x() + cell->extraXCells(), + view->selectionInfo()->marker().y() + cell->extraYCells() ); + TQRect markerArea( view->selectionInfo()->marker(), bottomRight ); + selected = selected && !( markerArea.contains( cellRef ) ); + + // Don't draw any selection at all when printing. + if ( painter.device()->isExtDev() || !drawCursor ) + selected = false; + } + + // Need to make a new layout ? + // + // FIXME: We have already used (at least) extraWidth/Height above, + // and now we are recalculating the layout. This has to be + // moved up above all uses. + // + // FIXME: This needs to be taken out eventually - it is done in + // canvas::paintUpdates(). + if ( testFlag( Flag_LayoutDirty ) ) + makeLayout( painter, cellRef.x(), cellRef.y() ); + + // ---------------- Start the actual painting. ---------------- + + // If the rect of this cell doesn't intersect the rect that should + // be painted, we can skip the rest and return. (Note that we need + // to calculate `left' first before we can do this.) + const KoRect cellRect( left, coordinate.y(), width, height ); + const KoRect cellRect0( left, coordinate.y(), width0, height0 ); + if ( !cellRect.intersects( rect ) ) { + clearFlag( Flag_PaintingCell ); + return; + } + + // Get the background color. + // + // If there is a condition giving the background color for this cell + // (and it matches), use that one, otherwise get the standard + // background. + TQColor backgroundColor; + if ( d->hasExtra() && d->extra()->conditions + && d->extra()->conditions->matchedStyle() + && d->extra()->conditions->matchedStyle()->hasFeature( Style::SBackgroundColor, true ) ) + backgroundColor = d->extra()->conditions->matchedStyle()->bgColor(); + else + backgroundColor = bgColor( cellRef.x(), cellRef.y() ); + + // 1. Paint the background. + if ( !isPartOfMerged() ) + paintBackground( painter, cellRect0, cellRef, selected, backgroundColor ); + + // 2. Paint the default borders if we are on screen or if we are printing + // and the checkbox to do this is checked. + if ( painter.device()->devType() != TQInternal::Printer + || format()->sheet()->print()->printGrid()) + paintDefaultBorders( painter, rect, cellRect, cellRef, + paintBorderRight, paintBorderBottom, + paintBorderLeft, paintBorderTop, + rightPen, bottomPen, leftPen, topPen ); + + // 3. Paint all the cells that this one obscures. They may only be + // partially obscured. + // + // The `paintingObscured' variable is used to avoid infinite + // recursion since cells sometimes paint their obscuring cell as + // well. + paintingObscured++; + + if (d->hasExtra() && (d->extra()->extraXCells > 0 + || d->extra()->extraYCells > 0)) { + //kdDebug(36001) << "painting obscured cells for " << name() << endl; + + paintObscuredCells( rect, painter, view, cellRect, cellRef, + paintBorderRight, paintBorderBottom, + paintBorderLeft, paintBorderTop, + rightPen, bottomPen, leftPen, topPen, + mergedCellsPainted); + + // FIXME: Is this the right place for this? + if ( d->extra()->mergedXCells > 0 || d->extra()->mergedYCells > 0 ) + mergedCellsPainted.prepend( cellRef ); + } + paintingObscured--; + + // 4. Paint the borders of the cell if no other cell is forcing this + // one, i.e. this cell is not part of a merged cell. + // + + // If we print pages, then we disable clipping, otherwise borders are + // cut in the middle at the page borders. + if ( painter.device()->isExtDev() ) + painter.setClipping( false ); + + // Paint the borders if this cell is not part of another merged cell. + if ( !isPartOfMerged() ) { + // if (!testFlag(Flag_Highlight)) + paintCellBorders( painter, rect, cellRect0, + cellRef, + paintBorderRight, paintBorderBottom, + paintBorderLeft, paintBorderTop, + rightPen, bottomPen, leftPen, topPen ); + } + + // Turn clipping back on. + if ( painter.device()->isExtDev() ) + painter.setClipping( true ); + + // 5. Paint diagonal lines and page borders. + paintCellDiagonalLines( painter, cellRect0, cellRef ); + + paintPageBorders( painter, cellRect0, cellRef, + paintBorderRight, paintBorderBottom ); + + + // 6. Now paint the content, if this cell isn't obscured. + if ( !isObscured() ) { + + // 6a. Paint possible comment indicator. + if ( !painter.device()->isExtDev() + || format()->sheet()->print()->printCommentIndicator() ) + paintCommentIndicator( painter, cellRect, cellRef, backgroundColor ); + + // 6b. Paint possible formula indicator. + if ( !painter.device()->isExtDev() + || format()->sheet()->print()->printFormulaIndicator() ) + paintFormulaIndicator( painter, cellRect, backgroundColor ); + + // 6c. Paint possible indicator for clipped text. + paintMoreTextIndicator( painter, cellRect, backgroundColor ); + + //6c. Paint cell highlight +#if 0 + if (highlightBorder != Border_None) + paintCellHighlight ( painter, cellRect, cellRef, highlightBorder, + rightHighlightPen, bottomHighlightPen, + leftHighlightPen, topHighlightPen ); +#endif + + // 6d. Paint the text in the cell unless: + // a) it is empty + // b) something indicates that the text should not be painted + // c) the sheet is protected and the cell is hidden. + if ( !d->strOutText.isEmpty() + && ( !painter.device()->isExtDev() + || !format()->getDontprintText( cellRef.x(), cellRef.y() ) ) + && !( format()->sheet()->isProtected() + && format()->isHideAll( cellRef.x(), cellRef.y() ) ) ) + { + paintText( painter, cellRect, cellRef ); + } + } + + // 7. If this cell is obscured and we are not already painting obscured + // cells, then paint the obscuring cell(s). Otherwise don't do + // anything so that we don't cause an infinite loop. + if ( isObscured() && paintingObscured == 0 && + !( sheetDir == Sheet::RightToLeft && painter.device()->isExtDev() ) ) + { + + //kdDebug(36001) << "painting cells that obscure " << name() << endl; + + // Store the obscuringCells list in a list of TQPoint(column, row) + // This avoids crashes during the iteration through + // obscuringCells, when the cells may get non valid or the list + // itself gets changed during a call of obscuringCell->paintCell + // (this happens e.g. when there is an updateDepend) + if (d->hasExtra()) { + TQValueList<TQPoint> listPoints; + TQValueList<Cell*>::iterator it = d->extra()->obscuringCells.begin(); + TQValueList<Cell*>::iterator end = d->extra()->obscuringCells.end(); + for ( ; it != end; ++it ) { + Cell *obscuringCell = *it; + + listPoints.append( TQPoint( obscuringCell->column(), obscuringCell->row() ) ); + } + + TQValueList<TQPoint>::iterator it1 = listPoints.begin(); + TQValueList<TQPoint>::iterator end1 = listPoints.end(); + for ( ; it1 != end1; ++it1 ) { + TQPoint obscuringCellRef = *it1; + + // Only paint those obscuring cells that haven't been already + // painted yet. + // + // This optimization removes an O(n^4) behaviour where n is + // the number of cells on one edge in a merged cell. + if ( mergedCellsPainted.contains( obscuringCellRef ) ) + continue; + + Cell *obscuringCell = format()->sheet()->cellAt( obscuringCellRef.x(), + obscuringCellRef.y() ); + + if ( obscuringCell != 0 ) { + double x = format()->sheet()->dblColumnPos( obscuringCellRef.x() ); + double y = format()->sheet()->dblRowPos( obscuringCellRef.y() ); + if ( view != 0 ) { + x -= view->canvasWidget()->xOffset(); + y -= view->canvasWidget()->yOffset(); + } + + KoPoint corner( x, y ); + painter.save(); + + // Get the effective pens for the borders. These are + // determined by possible conditions on the cell with + // associated styles. + TQPen rp( obscuringCell->effRightBorderPen( obscuringCellRef.x(), + obscuringCellRef.y() ) ); + TQPen bp( obscuringCell->effBottomBorderPen( obscuringCellRef.x(), + obscuringCellRef.y() ) ); + TQPen lp( obscuringCell->effLeftBorderPen( obscuringCellRef.x(), + obscuringCellRef.y() ) ); + TQPen tp( obscuringCell->effTopBorderPen( obscuringCellRef.x(), + obscuringCellRef.y() ) ); + + + //kdDebug(36001) << " painting obscuring cell " + // << obscuringCell->name() << endl; + // TQPen highlightPen; + + //Note: Painting of highlight isn't quite right. If several + // cells are merged, then the whole merged cell will be + // painted with the colour of the last cell referenced + // which is inside the merged range. + obscuringCell->paintCell( rect, painter, view, + corner, obscuringCellRef, + Border_Left|Border_Top|Border_Right|Border_Bottom, + rp, bp, lp, tp, + mergedCellsPainted); // new pens + painter.restore(); + } + } + } + } + + // We are done with the painting, so remove the flag on the cell. + clearFlag( Flag_PaintingCell ); +} + + + +// The following code was commented out in the above function. I'll +// leave it here in case this functionality is ever re-implemented and +// someone wants some code to start from. +// +#if 0 + + /** + * Modification for drawing the button + */ + if ( d->style == Cell::ST_Button ) { + TQBrush fill( TQt::lightGray ); + TQApplication::style().drawControl( TQStyle::CE_PushButton, &_painter, this, + TQRect( _tx + 1, _ty + 1, w2 - 1, h2 - 1 ), + defaultColorGroup ); //, selected, &fill ); + } + + /** + * Modification for drawing the combo box + */ + else if ( d->style == Cell::ST_Select ) { + TQApplication::style().drawComboButton( &_painter, _tx + 1, _ty + 1, + w2 - 1, h2 - 1, + defaultColorGroup, selected ); + } +#endif + + +#if 0 + void Cell::paintCellHighlight(TQPainter& painter, + const KoRect& cellRect, + const TQPoint& cellRef, + const int highlightBorder, + const TQPen& rightPen, + const TQPen& bottomPen, + const TQPen& leftPen, + const TQPen& topPen + ) +{ + //painter.drawLine(cellRect.left(),cellRect.top(),cellRect.right(),cellRect.bottom()); + //TQPen pen(d->extra()->highlight); + //painter.setPen(highlightPen); + + TQBrush nullBrush; + painter.setBrush(nullBrush); + + TQRect zoomedCellRect = sheet()->doc()->zoomRect( cellRect ); + + //The highlight rect is just inside the main cell rect + //This saves the hassle of repainting nearby cells when the highlight is changed as the highlight areas + //do not overlap + zoomedCellRect.setLeft(zoomedCellRect.left()+1); + //zoomedCellRect.setRight(zoomedCellRect.right()-1); + zoomedCellRect.setTop(zoomedCellRect.top()+1); + //zoomedCellRect.setBottom(zoomedCellRect.bottom()-1); + + if ( cellRef.x() != KS_colMax ) + zoomedCellRect.setWidth( zoomedCellRect.width() - 1 ); + if ( cellRef.y() != KS_rowMax ) + zoomedCellRect.setHeight( zoomedCellRect.height() - 1 ); + + if (highlightBorder & Border_Top) + { + painter.setPen(topPen); + painter.drawLine(zoomedCellRect.left(),zoomedCellRect.top(),zoomedCellRect.right(),zoomedCellRect.top()); + } + if (highlightBorder & Border_Left) + { + painter.setPen(leftPen); + painter.drawLine(zoomedCellRect.left(),zoomedCellRect.top(),zoomedCellRect.left(),zoomedCellRect.bottom()); + } + if (highlightBorder & Border_Right) + { + painter.setPen(rightPen); + painter.drawLine(zoomedCellRect.right(),zoomedCellRect.top(),zoomedCellRect.right(),zoomedCellRect.bottom()); + } + if (highlightBorder & Border_Bottom) + { + painter.setPen(bottomPen); + painter.drawLine(zoomedCellRect.left(),zoomedCellRect.bottom(),zoomedCellRect.right(),zoomedCellRect.bottom()); + } + + if (highlightBorder & Border_SizeGrip) + { + TQBrush brush(rightPen.color()); + painter.setBrush(brush); + painter.setPen(rightPen); + painter.drawRect(zoomedCellRect.right()-3,zoomedCellRect.bottom()-3,4,4); + } + + //painter.drawRect(zoomedCellRect.left(),zoomedCellRect.top(),zoomedCellRect.width(),zoomedCellRect.height()); +} +#endif + + +// Paint all the cells that this cell obscures (helper function to paintCell). +// +void Cell::paintObscuredCells(const KoRect& rect, TQPainter& painter, + View* view, + const KoRect &cellRect, + const TQPoint &cellRef, + bool paintBorderRight, + bool _paintBorderBottom, + bool paintBorderLeft, + bool _paintBorderTop, + TQPen & rightPen, TQPen & _bottomPen, + TQPen & leftPen, TQPen & _topPen, + TQValueList<TQPoint> &mergedCellsPainted) +{ + // If there are no obscured cells, return. + if ( !extraXCells() && !extraYCells() ) + return; + + double ypos = cellRect.y(); + int maxY = extraYCells(); + int maxX = extraXCells(); + + // Loop through the rectangle of squares that we obscure and paint them. + for ( int y = 0; y <= maxY; ++y ) { + double xpos = cellRect.x(); + RowFormat* rl = format()->sheet()->rowFormat( cellRef.y() + y ); + + for( int x = 0; x <= maxX; ++ x ) { + ColumnFormat * cl = format()->sheet()->columnFormat( cellRef.x() + x ); + if ( y != 0 || x != 0 ) { + uint column = cellRef.x() + x; + uint row = cellRef.y() + y; + + TQPen topPen; + TQPen bottomPen; + bool paintBorderTop; + bool paintBorderBottom; + + Cell *cell = format()->sheet()->cellAt( column, row ); + KoPoint corner( xpos, ypos ); + + // Check if the upper and lower borders should be painted, and + // if so which pens we should use. There used to be a nasty + // bug here (#61452). + // Check top pen. Only check if this is not on the top row. + topPen = _topPen; + paintBorderTop = _paintBorderTop; + if ( row > 1 && !cell->isPartOfMerged() ) { + Cell *cellUp = format()->sheet()->cellAt( column, row - 1 ); + + if ( cellUp->isDefault() ) + paintBorderTop = false; + else { + // If the cell towards the top is part of a merged cell, get + // the pointer to the master cell. + cellUp = cellUp->ultimateObscuringCell(); + + topPen = cellUp->effBottomBorderPen( cellUp->column(), + cellUp->row() ); + +#if 0 + int penWidth = TQMAX(1, sheet()->doc()->zoomItY( topPen.width() )); + topPen.setWidth( penWidth ); +#endif + } + } + + // FIXME: I thought we had to check bottom pen as well. + // However, it looks as if we don't need to. It works anyway. + bottomPen = _bottomPen; + paintBorderBottom = _paintBorderBottom; + + int paintBorder = Border_None; + if (paintBorderLeft) paintBorder |= Cell::Border_Left; + if (paintBorderRight) paintBorder |= Cell::Border_Right; + if (paintBorderTop) paintBorder |= Cell::Border_Top; + if (paintBorderBottom) paintBorder |= Cell::Border_Bottom; + + /*Cell::BorderSides highlightBorder = Border_None; + TQPen highlightPen;*/ + + + //kdDebug(36001) << "calling paintcell for obscured cell " + // << cell->name() << endl; + cell->paintCell( rect, painter, view, + corner, + TQPoint( cellRef.x() + x, cellRef.y() + y ), + paintBorder, + rightPen, bottomPen, leftPen, topPen, + mergedCellsPainted); + } + xpos += cl->dblWidth(); + } + + ypos += rl->dblHeight(); + } +} + + +// Paint the background of this cell. +// +void Cell::paintBackground( TQPainter& painter, const KoRect &cellRect, + const TQPoint &cellRef, bool selected, + TQColor &backgroundColor ) +{ + TQColorGroup defaultColorGroup = TQApplication::palette().active(); + TQRect zoomedCellRect = sheet()->doc()->zoomRect( cellRect ); + + // If this is not the KS_rowMax and/or KS_colMax, then we reduce + // width and/or height by one. This is due to the fact that the + // right/bottom most pixel is shared with the left/top most pixel of + // the following cell. Only in the case of KS_colMax/KS_rowMax we + // need to draw even this pixel, as there isn't a following cell to + // draw the background pixel. + if ( cellRef.x() != KS_colMax ) + zoomedCellRect.setWidth( zoomedCellRect.width() - 1 ); + if ( cellRef.y() != KS_rowMax ) + zoomedCellRect.setHeight( zoomedCellRect.height() - 1 ); + + // Determine the correct background color + if ( selected ) + { + //If the cell's background color is too bright, use the default highlight color + //Otherwise use a lighter version of the cell's background color. + TQColor c; + + int averageColor = (backgroundColor.red() + backgroundColor.green() + backgroundColor.blue()) / 3; + + if (averageColor > 180) + if (averageColor > 225) + c = View::highlightColor(); + else + c = backgroundColor.light( 115 ); //15% lighter + else + c = backgroundColor.light( 125 ); //25% lighter + + painter.setBackgroundColor( c ); + } + else { + TQColor bg( backgroundColor ); + + // Handle printers separately. + if ( !painter.device()->isExtDev() ) { + if ( bg.isValid() ) + painter.setBackgroundColor( bg ); + else + painter.setBackgroundColor( defaultColorGroup.base() ); + } + else { + //bad hack but there is a qt bug + //so I can print backgroundcolor + TQBrush bb( bg ); + if ( !bg.isValid() ) + bb.setColor( TQt::white ); + + painter.fillRect( zoomedCellRect, bb ); + return; + } + } + + // Erase the background of the cell. + if ( !painter.device()->isExtDev() ) + painter.eraseRect( zoomedCellRect ); + + // Get a background brush + TQBrush bb; + if ( d->hasExtra() + && d->extra()->conditions + && d->extra()->conditions->matchedStyle() + && d->extra()->conditions->matchedStyle()->hasFeature( Style::SBackgroundBrush, true ) ) + bb = d->extra()->conditions->matchedStyle()->backGroundBrush(); + else + bb = backGroundBrush( cellRef.x(), cellRef.y() ); + + // Draw background pattern if necessary. + if ( bb.style() != TQt::NoBrush ) + painter.fillRect( zoomedCellRect, bb ); + + backgroundColor = painter.backgroundColor(); +} + + +// Paint the standard light grey borders that are always visible. +// +void Cell::paintDefaultBorders( TQPainter& painter, const KoRect &rect, + const KoRect &cellRect, + const TQPoint &cellRef, + bool paintBorderRight, bool /*paintBorderBottom*/, + bool paintBorderLeft, bool paintBorderTop, + TQPen const & rightPen, TQPen const & /*bottomPen*/, + TQPen const & leftPen, TQPen const & topPen ) +{ + /* + *** Notes about optimisation *** + + This function was painting the top , left , right & bottom lines in almost all cells previously, contrary to what the comment + below says should happen. There doesn't appear to be a UI option to enable or disable showing of the grid when printing at the moment, + so I have disabled drawing of right and bottom borders for all cells. + + I also couldn't work out under what conditions the variables dt / db would come out as anything other than 0 in the code + for painting the various borders. The effTopBorderPen / effBottomBorderPen calls were taking up a lot of time + according some profiling I did. If that code really is necessary, we need to find a more efficient way of getting the widths + than grabbing the whole TQPen object and asking it. + + + --Robert Knight ([email protected]) + */ + Doc* doc = sheet()->doc(); + + Sheet::LayoutDirection sheetDir = format()->sheet()->layoutDirection(); + bool paintingToExternalDevice = painter.device()->isExtDev(); + + // Each cell is responsible for drawing it's top and left portions + // of the "default" grid. --Or not drawing it if it shouldn't be + // there. It's also responsible to paint the right and bottom, if + // it is the last cell on a print out. + + bool paintTop; + bool paintLeft; + bool paintBottom=false; + bool paintRight=false; + + paintLeft = ( paintBorderLeft && leftPen.style() == TQt::NoPen + && sheet()->getShowGrid() && sheetDir==Sheet::LeftToRight ); + paintRight = ( paintBorderRight && rightPen.style() == TQt::NoPen + && sheet()->getShowGrid() && sheetDir==Sheet::RightToLeft ); + paintTop = ( paintBorderTop && topPen.style() == TQt::NoPen + && sheet()->getShowGrid() ); +// paintBottom = ( paintBorderBottom && sheet()->getShowGrid() +// && bottomPen.style() == TQt::NoPen ); + + + //Set the single-pixel with pen for drawing the borders with. + painter.setPen( TQPen( sheet()->doc()->gridColor(), 1, TQt::SolidLine ) ); + + // If there are extra cells, there might be more conditions. + if (d->hasExtra()) { + TQValueList<Cell*>::const_iterator it = d->extra()->obscuringCells.begin(); + TQValueList<Cell*>::const_iterator end = d->extra()->obscuringCells.end(); + for ( ; it != end; ++it ) { + Cell *cell = *it; + + paintTop = paintTop && ( cell->row() == cellRef.y() ); + paintBottom = false; + + if ( sheetDir == Sheet::RightToLeft ) { + paintRight = paintRight && ( cell->column() == cellRef.x() ); + paintLeft = false; + } + else { + paintLeft = paintLeft && ( cell->column() == cellRef.x() ); + paintRight = false; + } + } + } + + // The left border. + if ( paintLeft ) { + int dt = 0; + int db = 0; + + #if 0 + if ( cellRef.x() > 1 ) { + Cell *cell_west = format()->sheet()->cellAt( cellRef.x() - 1, + cellRef.y() ); + TQPen t = cell_west->effTopBorderPen( cellRef.x() - 1, cellRef.y() ); + TQPen b = cell_west->effBottomBorderPen( cellRef.x() - 1, cellRef.y() ); + + if ( t.style() != TQt::NoPen ) + dt = ( t.width() + 1 )/2; + if ( b.style() != TQt::NoPen ) + db = ( t.width() / 2); + } + #endif + + // If we are on paper printout, we limit the length of the lines. + // On paper, we always have full cells, on screen not. + if ( paintingToExternalDevice ) { + if ( sheetDir == Sheet::RightToLeft ) + painter.drawLine( doc->zoomItX( TQMAX( rect.left(), cellRect.right() ) ), + doc->zoomItY( TQMAX( rect.top(), cellRect.y() + dt ) ), + doc->zoomItX( TQMIN( rect.right(), cellRect.right() ) ), + doc->zoomItY( TQMIN( rect.bottom(), cellRect.bottom() - db ) ) ); + else + painter.drawLine( doc->zoomItX( TQMAX( rect.left(), cellRect.x() ) ), + doc->zoomItY( TQMAX( rect.top(), cellRect.y() + dt ) ), + doc->zoomItX( TQMIN( rect.right(), cellRect.x() ) ), + doc->zoomItY( TQMIN( rect.bottom(), cellRect.bottom() - db ) ) ); + } + else { + if ( sheetDir == Sheet::RightToLeft ) + painter.drawLine( doc->zoomItX( cellRect.right() ), + doc->zoomItY( cellRect.y() + dt ), + doc->zoomItX( cellRect.right() ), + doc->zoomItY( cellRect.bottom() - db ) ); + else + painter.drawLine( doc->zoomItX( cellRect.x() ), + doc->zoomItY( cellRect.y() + dt ), + doc->zoomItX( cellRect.x() ), + doc->zoomItY( cellRect.bottom() - db ) ); + } + } + + + // The top border. + if ( paintTop ) { + int dl = 0; + int dr = 0; + + #if 0 + if ( cellRef.y() > 1 ) { + Cell *cell_north = format()->sheet()->cellAt( cellRef.x(), + cellRef.y() - 1 ); + + TQPen l = cell_north->effLeftBorderPen( cellRef.x(), cellRef.y() - 1 ); + TQPen r = cell_north->effRightBorderPen( cellRef.x(), cellRef.y() - 1 ); + + if ( l.style() != TQt::NoPen ) + dl = ( l.width() - 1 ) / 2 + 1; + if ( r.style() != TQt::NoPen ) + dr = r.width() / 2; + } + #endif + + + + // If we are on paper printout, we limit the length of the lines. + // On paper, we always have full cells, on screen not. + if ( paintingToExternalDevice ) { + painter.drawLine( doc->zoomItX( TQMAX( rect.left(), cellRect.x() + dl ) ), + doc->zoomItY( TQMAX( rect.top(), cellRect.y() ) ), + doc->zoomItX( TQMIN( rect.right(), cellRect.right() - dr ) ), + doc->zoomItY( TQMIN( rect.bottom(), cellRect.y() ) ) ); + } + else { + painter.drawLine( doc->zoomItX( cellRect.x() + dl ), + doc->zoomItY( cellRect.y() ), + doc->zoomItX( cellRect.right() - dr ), + doc->zoomItY( cellRect.y() ) ); + } + } + + + // The right border. + if ( paintRight ) { + int dt = 0; + int db = 0; + + #if 0 + if ( cellRef.x() < KS_colMax ) { + Cell *cell_east = format()->sheet()->cellAt( cellRef.x() + 1, + cellRef.y() ); + + TQPen t = cell_east->effTopBorderPen( cellRef.x() + 1, cellRef.y() ); + TQPen b = cell_east->effBottomBorderPen( cellRef.x() + 1, cellRef.y() ); + + if ( t.style() != TQt::NoPen ) + dt = ( t.width() + 1 ) / 2; + if ( b.style() != TQt::NoPen ) + db = ( t.width() / 2); + } + #endif + + //painter.setPen( TQPen( sheet()->doc()->gridColor(), 1, TQt::SolidLine ) ); + + // If we are on paper printout, we limit the length of the lines. + // On paper, we always have full cells, on screen not. + if ( painter.device()->isExtDev() ) { + if ( sheetDir == Sheet::RightToLeft ) + painter.drawLine( doc->zoomItX( TQMAX( rect.left(), cellRect.x() ) ), + doc->zoomItY( TQMAX( rect.top(), cellRect.y() + dt ) ), + doc->zoomItX( TQMIN( rect.right(), cellRect.x() ) ), + doc->zoomItY( TQMIN( rect.bottom(), cellRect.bottom() - db ) ) ); + else + painter.drawLine( doc->zoomItX( TQMAX( rect.left(), cellRect.right() ) ), + doc->zoomItY( TQMAX( rect.top(), cellRect.y() + dt ) ), + doc->zoomItX( TQMIN( rect.right(), cellRect.right() ) ), + doc->zoomItY( TQMIN( rect.bottom(), cellRect.bottom() - db ) ) ); + } + else { + if ( sheetDir == Sheet::RightToLeft ) + painter.drawLine( doc->zoomItX( cellRect.x() ), + doc->zoomItY( cellRect.y() + dt ), + doc->zoomItX( cellRect.x() ), + doc->zoomItY( cellRect.bottom() - db ) ); + else + painter.drawLine( doc->zoomItX( cellRect.right() ), + doc->zoomItY( cellRect.y() + dt ), + doc->zoomItX( cellRect.right() ), + doc->zoomItY( cellRect.bottom() - db ) ); + } + } + + // The bottom border. + /*if ( paintBottom ) { + int dl = 0; + int dr = 0; + if ( cellRef.y() < KS_rowMax ) { + Cell *cell_south = format()->sheet()->cellAt( cellRef.x(), + cellRef.y() + 1 ); + + TQPen l = cell_south->effLeftBorderPen( cellRef.x(), cellRef.y() + 1 ); + TQPen r = cell_south->effRightBorderPen( cellRef.x(), cellRef.y() + 1 ); + + if ( l.style() != TQt::NoPen ) + dl = ( l.width() - 1 ) / 2 + 1; + if ( r.style() != TQt::NoPen ) + dr = r.width() / 2; + } + + painter.setPen( TQPen( sheet()->doc()->gridColor(), 1, TQt::SolidLine ) ); + + // If we are on paper printout, we limit the length of the lines. + // On paper, we always have full cells, on screen not. + if ( painter.device()->isExtDev() ) { + painter.drawLine( doc->zoomItX( TQMAX( rect.left(), cellRect.x() + dl ) ), + doc->zoomItY( TQMAX( rect.top(), cellRect.bottom() ) ), + doc->zoomItX( TQMIN( rect.right(), cellRect.right() - dr ) ), + doc->zoomItY( TQMIN( rect.bottom(), cellRect.bottom() ) ) ); + } + else { + painter.drawLine( doc->zoomItX( cellRect.x() + dl ), + doc->zoomItY( cellRect.bottom() ), + doc->zoomItX( cellRect.right() - dr ), + doc->zoomItY( cellRect.bottom() ) ); + } + }*/ +} + + +// Paint a comment indicator if the cell has a comment. +// +void Cell::paintCommentIndicator( TQPainter& painter, + const KoRect &cellRect, + const TQPoint &/*cellRef*/, + TQColor &backgroundColor ) +{ + Doc * doc = sheet()->doc(); + + // Point the little corner if there is a comment attached + // to this cell. + if ( ( format()->propertiesMask() & (uint) Format::PComment ) + && cellRect.width() > 10.0 + && cellRect.height() > 10.0 + && ( sheet()->print()->printCommentIndicator() + || ( !painter.device()->isExtDev() && sheet()->getShowCommentIndicator() ) ) ) { + TQColor penColor = TQt::red; + + // If background has high red part, switch to blue. + if ( tqRed( backgroundColor.rgb() ) > 127 && + tqGreen( backgroundColor.rgb() ) < 80 && + tqBlue( backgroundColor.rgb() ) < 80 ) + { + penColor = TQt::blue; + } + + // Get the triangle. + TQPointArray point( 3 ); + if ( format()->sheet()->layoutDirection()==Sheet::RightToLeft ) { + point.setPoint( 0, doc->zoomItX( cellRect.x() + 6.0 ), + doc->zoomItY( cellRect.y() ) ); + point.setPoint( 1, doc->zoomItX( cellRect.x() ), + doc->zoomItY( cellRect.y() ) ); + point.setPoint( 2, doc->zoomItX( cellRect.x() ), + doc->zoomItY( cellRect.y() + 6.0 ) ); + } + else { + point.setPoint( 0, doc->zoomItX( cellRect.right() - 5.0 ), + doc->zoomItY( cellRect.y() ) ); + point.setPoint( 1, doc->zoomItX( cellRect.right() ), + doc->zoomItY( cellRect.y() ) ); + point.setPoint( 2, doc->zoomItX( cellRect.right() ), + doc->zoomItY( cellRect.y() + 5.0 ) ); + } + + // And draw it. + painter.setBrush( TQBrush( penColor ) ); + painter.setPen( TQt::NoPen ); + painter.drawPolygon( point ); + } +} + + + +// Paint a small rectangle if this cell holds a formula. +// +void Cell::paintFormulaIndicator( TQPainter& painter, + const KoRect &cellRect, + TQColor &backgroundColor ) +{ + if ( isFormula() && + format()->sheet()->getShowFormulaIndicator() && + cellRect.width() > 10.0 && + cellRect.height() > 10.0 ) + { + Doc* doc = sheet()->doc(); + + TQColor penColor = TQt::blue; + // If background has high blue part, switch to red. + if ( tqRed( backgroundColor.rgb() ) < 80 && + tqGreen( backgroundColor.rgb() ) < 80 && + tqBlue( backgroundColor.rgb() ) > 127 ) + { + penColor = TQt::red; + } + + // Get the triangle... + TQPointArray point( 3 ); + if ( format()->sheet()->layoutDirection()==Sheet::RightToLeft ) { + point.setPoint( 0, doc->zoomItX( cellRect.right() - 6.0 ), + doc->zoomItY( cellRect.bottom() ) ); + point.setPoint( 1, doc->zoomItX( cellRect.right() ), + doc->zoomItY( cellRect.bottom() ) ); + point.setPoint( 2, doc->zoomItX( cellRect.right() ), + doc->zoomItY( cellRect.bottom() - 6.0 ) ); + } + else { + point.setPoint( 0, doc->zoomItX( cellRect.x() ), + doc->zoomItY( cellRect.bottom() - 6.0 ) ); + point.setPoint( 1, doc->zoomItX( cellRect.x() ), + doc->zoomItY( cellRect.bottom() ) ); + point.setPoint( 2, doc->zoomItX( cellRect.x() + 6.0 ), + doc->zoomItY( cellRect.bottom() ) ); + } + + // ...and draw it. + painter.setBrush( TQBrush( penColor ) ); + painter.setPen( TQt::NoPen ); + painter.drawPolygon( point ); + } +} + + +// Paint an indicator that the text in the cell is cut. +// +void Cell::paintMoreTextIndicator( TQPainter& painter, + const KoRect &cellRect, + TQColor &backgroundColor ) +{ + // Show a red triangle when it's not possible to write all text in cell. + // Don't print the red triangle if we're printing. + if( testFlag( Flag_CellTooShortX ) && + !painter.device()->isExtDev() && + cellRect.height() > 4.0 && + cellRect.width() > 4.0 ) + { + Doc* doc = sheet()->doc(); + + TQColor penColor = TQt::red; + // If background has high red part, switch to blue. + if ( tqRed( backgroundColor.rgb() ) > 127 + && tqGreen( backgroundColor.rgb() ) < 80 + && tqBlue( backgroundColor.rgb() ) < 80 ) + { + penColor = TQt::blue; + } + + // Get the triangle... + TQPointArray point( 3 ); + if ( d->strOutText.isRightToLeft() ) { + point.setPoint( 0, doc->zoomItX( cellRect.left() + 4.0 ), + doc->zoomItY( cellRect.y() + cellRect.height() / 2.0 -4.0 ) ); + point.setPoint( 1, doc->zoomItX( cellRect.left() ), + doc->zoomItY( cellRect.y() + cellRect.height() / 2.0 )); + point.setPoint( 2, doc->zoomItX( cellRect.left() + 4.0 ), + doc->zoomItY( cellRect.y() + cellRect.height() / 2.0 +4.0 ) ); + } + else { + point.setPoint( 0, doc->zoomItX( cellRect.right() - 4.0 ), + doc->zoomItY( cellRect.y() + cellRect.height() / 2.0 - 4.0 ) ); + point.setPoint( 1, doc->zoomItX( cellRect.right() ), + doc->zoomItY( cellRect.y() + cellRect.height() / 2.0 ) ); + point.setPoint( 2, doc->zoomItX( cellRect.right() - 4.0 ), + doc->zoomItY( cellRect.y() + cellRect.height() / 2.0 + 4.0 ) ); + } + + // ...and paint it. + painter.setBrush( TQBrush( penColor ) ); + painter.setPen( TQt::NoPen ); + painter.drawPolygon( point ); + } +} + + +// Paint the real contents of a cell - the text. +// +void Cell::paintText( TQPainter& painter, + const KoRect &cellRect, + const TQPoint &cellRef ) +{ + Doc *doc = sheet()->doc(); + + ColumnFormat *colFormat = format()->sheet()->columnFormat( cellRef.x() ); + + TQColorGroup defaultColorGroup = TQApplication::palette().active(); + TQColor textColorPrint = effTextColor( cellRef.x(), cellRef.y() ); + + // Resolve the text color if invalid (=default). + if ( !textColorPrint.isValid() ) { + if ( painter.device()->isExtDev() ) + textColorPrint = TQt::black; + else + textColorPrint = TQApplication::palette().active().text(); + } + + TQPen tmpPen( textColorPrint ); + + // Set the font according to the current zoom. + applyZoomedFont( painter, cellRef.x(), cellRef.y() ); + + // Check for red font color for negative values. + if ( !d->hasExtra() + || !d->extra()->conditions + || !d->extra()->conditions->matchedStyle() ) { + if ( value().isNumber() + && !( format()->sheet()->getShowFormula() + && !( format()->sheet()->isProtected() + && format()->isHideFormula( d->column, d->row ) ) ) ) + { + double v = value().asFloat(); + if ( format()->floatColor( cellRef.x(), cellRef.y()) == Format::NegRed + && v < 0.0 ) + tmpPen.setColor( TQt::red ); + } + } + + // Check for blue color, for hyperlink. + if ( !link().isEmpty() ) { + tmpPen.setColor( TQApplication::palette().active().link() ); + TQFont f = painter.font(); + f.setUnderline( true ); + painter.setFont( f ); + } + +#if 0 +/**** + + For now I am commenting this out -- with the default color display you + can read normal text through a highlighted background. Maybe this isn't + always the case, though, and we can put the highlighted text color back in. + In that case, we need to somewhere in here figure out if the text overlaps + another cell outside of the selection, otherwise that portion of the text + will be printed white on white. So just that portion would need to be + painted again in the normal color. + + This should probably be done eventually, anyway, because I like using the + reverse text color for highlighted cells. I just don't like extending the + cell 'highlight' background outside of the selection rectangle because it + looks REALLY ugly. +*/ + + if ( selected && ( cellRef.x() != marker.x() || cellRef.y() != marker.y() ) ) + { + TQPen p( tmpPen ); + p.setColor( defaultColorGroup.highlightedText() ); + painter.setPen( p ); + } + else { + painter.setPen(tmpPen); + } +#endif + painter.setPen( tmpPen ); + + TQString tmpText = d->strOutText; + double tmpHeight = d->textHeight; + double tmpWidth = d->textWidth; + + // If the cell is to narrow to paint the whole contents, then pick + // out a part of the content that we paint. The result of this is + // dependent on the data type of the content. + // + // FIXME: Make this dependent on the height as well. + // + if ( testFlag( Flag_CellTooShortX ) ) { + d->strOutText = textDisplaying( painter ); + + // Recalculate the text width and the offset. + textSize( painter ); + offsetAlign( column(), row() ); + } + + // Hide zero. + if ( format()->sheet()->getHideZero() + && value().isNumber() + && value().asFloat() == 0 ) { + d->strOutText = TQString(); + } + + // Clear extra cell if column or row is hidden + // + // FIXME: I think this should be done before the call to + // textDisplaying() above. + // + if ( colFormat->isHide() || ( cellRect.height() <= 2 ) ) { + freeAllObscuredCells(); /* TODO: This looks dangerous...must check when I + have time */ + d->strOutText = ""; + } + + double indent = 0.0; + double offsetCellTooShort = 0.0; + int a = effAlignX(); + + // Apply indent if text is align to left not when text is at right or middle. + if ( a == Format::Left && !isEmpty() ) { + // FIXME: The following condition should be remade into a call to + // a new convenience function: + // if ( hasConditionStyleFeature( Style::SIndent, true )... + // This should be done throughout the entire file. + // + if ( d->hasExtra() + && d->extra()->conditions + && d->extra()->conditions->matchedStyle() + && d->extra()->conditions->matchedStyle()->hasFeature( Style::SIndent, true ) ) + indent = d->extra()->conditions->matchedStyle()->indent(); + else + indent = format()->getIndent( column(), row() ); + } + + // Made an offset, otherwise ### is under red triangle. + if ( a == Format::Right && !isEmpty() && testFlag( Flag_CellTooShortX ) ) + offsetCellTooShort = format()->sheet()->doc()->unzoomItX( 4 ); + + TQFontMetrics fm2 = painter.fontMetrics(); + double offsetFont = 0.0; + + if ( format()->alignY( column(), row() ) == Format::Bottom + && format()->textFontUnderline( column(), row() ) ) + offsetFont = format()->sheet()->doc()->unzoomItX( fm2.underlinePos() + 1 ); + + int tmpAngle; + bool tmpMultiRow; + bool tmpVerticalText; + + // Check for angled or vertical text. + if ( d->hasExtra() + && d->extra()->conditions + && d->extra()->conditions->matchedStyle() ) + { + Style *matchedStyle = d->extra()->conditions->matchedStyle(); + + if ( matchedStyle->hasFeature( Style::SAngle, true ) ) + tmpAngle = d->extra()->conditions->matchedStyle()->rotateAngle(); + else + tmpAngle = format()->getAngle( cellRef.x(), cellRef.y() ); + + if ( matchedStyle->hasFeature( Style::SVerticalText, true ) ) + tmpVerticalText = matchedStyle->hasProperty( Style::PVerticalText ); + else + tmpVerticalText = format()->verticalText( cellRef.x(), cellRef.y() ); + + if ( matchedStyle->hasFeature( Style::SMultiRow, true ) ) + tmpMultiRow = matchedStyle->hasProperty( Style::PMultiRow ); + else + tmpMultiRow = format()->multiRow( cellRef.x(), cellRef.y() ); + } + else { + tmpAngle = format()->getAngle( cellRef.x(), cellRef.y() ); + tmpVerticalText = format()->verticalText( cellRef.x(), cellRef.y() ); + tmpMultiRow = format()->multiRow( cellRef.x(), cellRef.y() ); + } + + // Actually paint the text. + // There are 4 possible cases: + // - One line of text , horizontal + // - Angled text + // - Multiple rows of text , horizontal + // - Vertical text + if ( !tmpMultiRow && !tmpVerticalText && !tmpAngle ) { + // Case 1: The simple case, one line, no angle. + + painter.drawText( doc->zoomItX( indent + cellRect.x() + d->textX - offsetCellTooShort ), + doc->zoomItY( cellRect.y() + d->textY - offsetFont ), d->strOutText ); + } + else if ( tmpAngle != 0 ) { + // Case 2: an angle. + + int angle = tmpAngle; + TQFontMetrics fm = painter.fontMetrics(); + + painter.rotate( angle ); + double x; + if ( angle > 0 ) + x = indent + cellRect.x() + d->textX; + else + x = indent + cellRect.x() + d->textX + - doc->unzoomItX((int) (( fm.descent() + fm.ascent() ) * sin( angle * M_PI / 180 ))); + double y; + if ( angle > 0 ) + y = cellRect.y() + d->textY; + else + y = cellRect.y() + d->textY + d->textHeight; + painter.drawText( doc->zoomItX( x * cos( angle * M_PI / 180 ) + + y * sin( angle * M_PI / 180 ) ), + doc->zoomItY( -x * sin( angle * M_PI / 180 ) + + y * cos( angle * M_PI / 180 ) ), + d->strOutText ); + painter.rotate( -angle ); + } + else if ( tmpMultiRow && !tmpVerticalText ) { + // Case 3: Multiple rows, but horizontal. + + TQString t; + int i; + int pos = 0; + double dy = 0.0; + TQFontMetrics fm = painter.fontMetrics(); + do { + i = d->strOutText.find( "\n", pos ); + if ( i == -1 ) + t = d->strOutText.mid( pos, d->strOutText.length() - pos ); + else { + t = d->strOutText.mid( pos, i - pos ); + pos = i + 1; + } + + int align = effAlignX(); + if ( format()->sheet()->getShowFormula() + && !( format()->sheet()->isProtected() + && format()->isHideFormula( d->column, d->row ) ) ) + align = Format::Left; + + // #### Torben: This looks duplicated for me + switch ( align ) { + case Format::Left: + d->textX = effLeftBorderPen( cellRef.x(), cellRef.y() ).width() + BORDER_SPACE; + break; + + case Format::Right: + d->textX = cellRect.width() - BORDER_SPACE - doc->unzoomItX( fm.width( t ) ) + - effRightBorderPen( cellRef.x(), cellRef.y() ).width(); + break; + + case Format::Center: + d->textX = ( cellRect.width() - doc->unzoomItX( fm.width( t ) ) ) / 2; + } + + painter.drawText( doc->zoomItX( indent + cellRect.x() + d->textX ), + doc->zoomItY( cellRect.y() + d->textY + dy ), t ); + dy += doc->unzoomItY( fm.descent() + fm.ascent() ); + } while ( i != -1 ); + } + else if ( tmpVerticalText && !d->strOutText.isEmpty() ) { + // Case 4: Vertical text. + + TQString t; + int i = 0; + int len = 0; + double dy = 0.0; + TQFontMetrics fm = painter.fontMetrics(); + do { + len = d->strOutText.length(); + t = d->strOutText.at( i ); + painter.drawText( doc->zoomItX( indent + cellRect.x() + d->textX ), + doc->zoomItY( cellRect.y() + d->textY + dy ), t ); + dy += doc->unzoomItY( fm.descent() + fm.ascent() ); + i++; + } while ( i != len ); + } + + // Check for too short cell and set the outText for future reference. + if ( testFlag( Flag_CellTooShortX ) ) { + d->strOutText = tmpText; + d->textHeight = tmpHeight; + d->textWidth = tmpWidth; + } + + if ( format()->sheet()->getHideZero() && value().isNumber() + && value().asFloat() == 0 ) + d->strOutText = tmpText; + + if ( colFormat->isHide() || ( cellRect.height() <= 2 ) ) + d->strOutText = tmpText; +} + + +// Paint page borders on the page. Only do this on the screen. +// +void Cell::paintPageBorders( TQPainter& painter, + const KoRect &cellRect, + const TQPoint &cellRef, + bool paintBorderRight, + bool paintBorderBottom ) +{ + // Not screen? Return immediately. + if ( painter.device()->isExtDev() ) + return; + + if ( ! format()->sheet()->isShowPageBorders() ) + return; + + SheetPrint* print = format()->sheet()->print(); + + Sheet::LayoutDirection sheetDir = format()->sheet()->layoutDirection(); + + Doc* doc = sheet()->doc(); + int zcellRect_left = doc->zoomItX (cellRect.left()); + int zcellRect_right = doc->zoomItX (cellRect.right()); + int zcellRect_top = doc->zoomItY (cellRect.top()); + int zcellRect_bottom = doc->zoomItY (cellRect.bottom()); + + // Draw page borders + + if ( cellRef.x() >= print->printRange().left() + && cellRef.x() <= print->printRange().right() + 1 + && cellRef.y() >= print->printRange().top() + && cellRef.y() <= print->printRange().bottom() + 1 ) + { + if ( print->isOnNewPageX( cellRef.x() ) + && cellRef.y() <= print->printRange().bottom() ) + { + painter.setPen( sheet()->doc()->pageBorderColor() ); + + if ( sheetDir == Sheet::RightToLeft ) + painter.drawLine( zcellRect_right, zcellRect_top, + zcellRect_right, zcellRect_bottom ); + else + painter.drawLine( zcellRect_left, zcellRect_top, + zcellRect_left, zcellRect_bottom ); + } + + if ( print->isOnNewPageY( cellRef.y() ) && + ( cellRef.x() <= print->printRange().right() ) ) + { + painter.setPen( sheet()->doc()->pageBorderColor() ); + painter.drawLine( zcellRect_left, zcellRect_top, + zcellRect_right, zcellRect_top ); + } + + if ( paintBorderRight ) { + if ( print->isOnNewPageX( cellRef.x() + 1 ) + && cellRef.y() <= print->printRange().bottom() ) { + painter.setPen( sheet()->doc()->pageBorderColor() ); + + if ( sheetDir == Sheet::RightToLeft ) + painter.drawLine( zcellRect_left, zcellRect_top, + zcellRect_left, zcellRect_bottom ); + else + painter.drawLine( zcellRect_right, zcellRect_top, + zcellRect_right, zcellRect_bottom ); + } + } + + if ( paintBorderBottom ) { + if ( print->isOnNewPageY( cellRef.y() + 1 ) + && cellRef.x() <= print->printRange().right() ) { + painter.setPen( sheet()->doc()->pageBorderColor() ); + painter.drawLine( zcellRect_left, zcellRect_bottom, + zcellRect_right, zcellRect_bottom ); + } + } + } +} + + +// Paint the cell borders. +// +void Cell::paintCellBorders( TQPainter& painter, const KoRect& rect, + const KoRect &cellRect, + const TQPoint &cellRef, + bool paintRight, bool paintBottom, + bool paintLeft, bool paintTop, + TQPen & _rightPen, TQPen & _bottomPen, + TQPen & _leftPen, TQPen & _topPen ) +{ + + //Sanity check: If we are not painting any of the borders then the function + //really shouldn't be called at all. + if ( (!paintLeft) && (!paintRight) && (!paintTop) && (!paintBottom) ) + return; + + Doc * doc = sheet()->doc(); + + Sheet::LayoutDirection sheetDir = format()->sheet()->layoutDirection(); + + // compute zoomed rectangles + // I don't use KoRect, because that ends up producing lots of warnings + // about double->int conversions in calls to painter.drawLine + int zrect_left (doc->zoomItX (rect.left())); + int zrect_right (doc->zoomItX (rect.right())); + int zrect_top (doc->zoomItY (rect.top())); + int zrect_bottom (doc->zoomItY (rect.bottom())); + int zcellRect_left (doc->zoomItX (cellRect.left())); + int zcellRect_right (doc->zoomItX (cellRect.right())); + int zcellRect_top (doc->zoomItY (cellRect.top())); + int zcellRect_bottom (doc->zoomItY (cellRect.bottom())); + + /* we might not paint some borders if this cell is merged with another in + that direction + bool paintLeft = paintBorderLeft; + bool paintRight = paintBorderRight; + bool paintTop = paintBorderTop; + bool paintBottom = paintBorderBottom; + */ + + // paintRight = paintRight && ( extraXCells() == 0 ); + // paintBottom = paintBottom && ( d->extra()->extraYCells() == 0 ); + + if (d->hasExtra()) { + TQValueList<Cell*>::const_iterator it = d->extra()->obscuringCells.begin(); + TQValueList<Cell*>::const_iterator end = d->extra()->obscuringCells.end(); + for ( ; it != end; ++it ) { + Cell* cell = *it; + + int xDiff = cellRef.x() - cell->column(); + int yDiff = cellRef.y() - cell->row(); + paintLeft = paintLeft && xDiff == 0; + paintTop = paintTop && yDiff == 0; + + // Paint the border(s) if either this one should or if we have a + // merged cell with this cell as its border. + paintRight = paintRight && cell->mergedXCells() == xDiff; + paintBottom = paintBottom && cell->mergedYCells() == yDiff; + } + } + + // Must create copies of these since otherwise the zoomIt() + // operation will be performed on them repeatedly. + TQPen leftPen( _leftPen ); + TQPen rightPen( _rightPen ); + TQPen topPen( _topPen ); + TQPen bottomPen( _bottomPen ); + + // Determine the pens that should be used for drawing + // the borders. + // + int left_penWidth = TQMAX( 1, doc->zoomItX( leftPen.width() ) ); + int right_penWidth = TQMAX( 1, doc->zoomItX( rightPen.width() ) ); + int top_penWidth = TQMAX( 1, doc->zoomItY( topPen.width() ) ); + int bottom_penWidth = TQMAX( 1, doc->zoomItY( bottomPen.width() ) ); + + leftPen.setWidth( left_penWidth ); + rightPen.setWidth( right_penWidth ); + topPen.setWidth( top_penWidth ); + bottomPen.setWidth( bottom_penWidth ); + + if ( paintLeft && leftPen.style() != TQt::NoPen ) { + int top = ( TQMAX( 0, -1 + top_penWidth ) ) / 2 + + ( ( TQMAX( 0, -1 + top_penWidth ) ) % 2 ); + int bottom = ( TQMAX( 0, -1 + bottom_penWidth ) ) / 2 + 1; + + painter.setPen( leftPen ); + + //kdDebug(36001) << " painting left border of cell " << name() << endl; + + // If we are on paper printout, we limit the length of the lines. + // On paper, we always have full cells, on screen not. + if ( painter.device()->isExtDev() ) { + // FIXME: There is probably Cut&Paste bugs here as well as below. + // The TQMIN/TQMAX and left/right pairs don't really make sense. + // + // UPDATE: In fact, most of these TQMIN/TQMAX combinations + // are TOTALLY BOGUS. For one thing, the idea + // that we always have full cells on paper is wrong + // since we can have embedded sheets in e.g. kword, + // and those can be arbitrarily clipped. WE HAVE TO + // REVISE THIS WHOLE BORDER PAINTING SECTION! + // + if ( sheetDir == Sheet::RightToLeft ) + painter.drawLine( TQMIN( zrect_right, zcellRect_right ), + TQMAX( zrect_top, zcellRect_top - top ), + TQMIN( zrect_right, zcellRect_right ), + TQMIN( zrect_bottom, zcellRect_bottom + bottom ) ); + else + painter.drawLine( TQMAX( zrect_left, zcellRect_left ), + TQMAX( zrect_top, zcellRect_top - top ), + TQMAX( zrect_left, zcellRect_left ), + TQMIN( zrect_bottom, zcellRect_bottom + bottom ) ); + } + else { + if ( sheetDir == Sheet::RightToLeft ) + painter.drawLine( zcellRect_right, + zcellRect_top - top, + zcellRect_right, + zcellRect_bottom + bottom ); + else + painter.drawLine( zcellRect_left, + zcellRect_top - top, + zcellRect_left, + zcellRect_bottom + bottom ); + } + } + + if ( paintRight && rightPen.style() != TQt::NoPen ) { + int top = ( TQMAX( 0, -1 + top_penWidth ) ) / 2 + + ( ( TQMAX( 0, -1 + top_penWidth ) ) % 2 ); + int bottom = ( TQMAX( 0, -1 + bottom_penWidth ) ) / 2 + 1; + + painter.setPen( rightPen ); + + //kdDebug(36001) << " painting right border of cell " << name() << endl; + + // If we are on paper printout, we limit the length of the lines. + // On paper, we always have full cells, on screen not. + if ( painter.device()->isExtDev() ) { + if ( sheetDir == Sheet::RightToLeft ) + painter.drawLine( TQMAX( zrect_left, zcellRect_left ), + TQMAX( zrect_top, zcellRect_top - top ), + TQMAX( zrect_left, zcellRect_left ), + TQMIN( zrect_bottom, zcellRect_bottom + bottom ) ); + else { + // FIXME: This is the way all these things should look. + // Make it so. + // + // Only print the right border if it is visible. + if ( zcellRect_right <= zrect_right + right_penWidth / 2) + painter.drawLine( zcellRect_right, + TQMAX( zrect_top, zcellRect_top - top ), + zcellRect_right, + TQMIN( zrect_bottom, zcellRect_bottom + bottom ) ); + } + } + else { + if ( sheetDir == Sheet::RightToLeft ) + painter.drawLine( zcellRect_left, + zcellRect_top - top, + zcellRect_left, + zcellRect_bottom + bottom ); + else + painter.drawLine( zcellRect_right, + zcellRect_top - top, + zcellRect_right, + zcellRect_bottom + bottom ); + } + } + + if ( paintTop && topPen.style() != TQt::NoPen ) { + painter.setPen( topPen ); + + //kdDebug(36001) << " painting top border of cell " << name() + // << " [" << zcellRect_left << "," << zcellRect_right + // << ": " << zcellRect_right - zcellRect_left << "]" << endl; + + // If we are on paper printout, we limit the length of the lines. + // On paper, we always have full cells, on screen not. + if ( painter.device()->isExtDev() ) { + if ( zcellRect_top >= zrect_top + top_penWidth / 2) + painter.drawLine( TQMAX( zrect_left, zcellRect_left ), + zcellRect_top, + TQMIN( zrect_right, zcellRect_right ), + zcellRect_top ); + } + else { + painter.drawLine( zcellRect_left, zcellRect_top, + zcellRect_right, zcellRect_top ); + } + } + + if ( paintBottom && bottomPen.style() != TQt::NoPen ) { + painter.setPen( bottomPen ); + + //kdDebug(36001) << " painting bottom border of cell " << name() + // << " [" << zcellRect_left << "," << zcellRect_right + // << ": " << zcellRect_right - zcellRect_left << "]" << endl; + + // If we are on paper printout, we limit the length of the lines. + // On paper, we always have full cells, on screen not. + if ( painter.device()->isExtDev() ) { + if ( zcellRect_bottom <= zrect_bottom + bottom_penWidth / 2) + painter.drawLine( TQMAX( zrect_left, zcellRect_left ), + zcellRect_bottom, + TQMIN( zrect_right, zcellRect_right ), + zcellRect_bottom ); + } + else { + painter.drawLine( zcellRect_left, zcellRect_bottom, + zcellRect_right, zcellRect_bottom ); + } + } + + // FIXME: Look very closely at when the following code is really needed. + // I can't really see any case, but I might be wrong. + // Since the code below is buggy, and incredibly complex, + // I am currently disabling it. If somebody wants to enable + // it again, then please also solve bug 68977: "Embedded KSpread + // document printing problem" at the same time. + return; + +#if 0 + // Look at the cells on our corners. It may happen that we + // just erased parts of their borders corner, so we might need + // to repaint these corners. + // + TQPen vert_pen, horz_pen; + int vert_penWidth, horz_penWidth; + + // Some useful referenses. + Cell *cell_north = format()->sheet()->cellAt( cellRef.x(), + cellRef.y() - 1 ); + Cell *cell_northwest = format()->sheet()->cellAt( cellRef.x() - 1, + cellRef.y() - 1 ); + Cell *cell_west = format()->sheet()->cellAt( cellRef.x() - 1, + cellRef.y() ); + Cell *cell_northeast = format()->sheet()->cellAt( cellRef.x() + 1, + cellRef.y() - 1 ); + Cell *cell_east = format()->sheet()->cellAt( cellRef.x() + 1, + cellRef.y() ); + Cell *cell_south = format()->sheet()->cellAt( cellRef.x(), + cellRef.y() + 1 ); + Cell *cell_southwest = format()->sheet()->cellAt( cellRef.x() - 1, + cellRef.y() + 1 ); + Cell *cell_southeast = format()->sheet()->cellAt( cellRef.x() + 1, + cellRef.y() + 1 ); + + // Fix the borders which meet at the top left corner + if ( cell_north->effLeftBorderValue( cellRef.x(), cellRef.y() - 1 ) + >= cell_northwest->effRightBorderValue( cellRef.x() - 1, cellRef.y() - 1 ) ) + vert_pen = cell_north->effLeftBorderPen( cellRef.x(), cellRef.y() - 1 ); + else + vert_pen = cell_northwest->effRightBorderPen( cellRef.x() - 1, + cellRef.y() - 1 ); + + vert_penWidth = TQMAX( 1, doc->zoomItX( vert_pen.width() ) ); + vert_pen.setWidth( vert_penWidth ); + + if ( vert_pen.style() != TQt::NoPen ) { + if ( cell_west->effTopBorderValue( cellRef.x() - 1, cellRef.y() ) + >= cell_northwest->effBottomBorderValue( cellRef.x() - 1, cellRef.y() - 1 ) ) + horz_pen = cell_west->effTopBorderPen( cellRef.x() - 1, cellRef.y() ); + else + horz_pen = cell_northwest->effBottomBorderPen( cellRef.x() - 1, + cellRef.y() - 1 ); + + horz_penWidth = TQMAX( 1, doc->zoomItY( horz_pen.width() ) ); + int bottom = ( TQMAX( 0, -1 + horz_penWidth ) ) / 2 + 1; + + painter.setPen( vert_pen ); + // If we are on paper printout, we limit the length of the lines. + // On paper, we always have full cells, on screen not. + if ( painter.device()->isExtDev() ) { + if ( sheetDir == Sheet::RightToLeft ) + painter.drawLine( TQMAX( zrect_left, zcellRect_right ), + TQMAX( zrect_top, zcellRect_top ), + TQMIN( zrect_right, zcellRect_right ), + TQMIN( zrect_bottom, zcellRect_top + bottom ) ); + else + painter.drawLine( TQMAX( zrect_left, zcellRect_left ), + TQMAX( zrect_top, zcellRect_top ), + TQMIN( zrect_right, zcellRect_left ), + TQMIN( zrect_bottom, zcellRect_top + bottom ) ); + } + else { + if ( sheetDir == Sheet::RightToLeft ) + painter.drawLine( zcellRect_right, zcellRect_top, + zcellRect_right, zcellRect_top + bottom ); + else + painter.drawLine( zcellRect_left, zcellRect_top, + zcellRect_left, zcellRect_top + bottom ); + } + } + + // Fix the borders which meet at the top right corner + if ( cell_north->effRightBorderValue( cellRef.x(), cellRef.y() - 1 ) + >= cell_northeast->effLeftBorderValue( cellRef.x() + 1, + cellRef.y() - 1 ) ) + vert_pen = cell_north->effRightBorderPen( cellRef.x(), cellRef.y() - 1 ); + else + vert_pen = cell_northeast->effLeftBorderPen( cellRef.x() + 1, + cellRef.y() - 1 ); + + // vert_pen = effRightBorderPen( cellRef.x(), cellRef.y() - 1 ); + vert_penWidth = TQMAX( 1, doc->zoomItX( vert_pen.width() ) ); + vert_pen.setWidth( vert_penWidth ); + if ( ( vert_pen.style() != TQt::NoPen ) && ( cellRef.x() < KS_colMax ) ) { + if ( cell_east->effTopBorderValue( cellRef.x() + 1, cellRef.y() ) + >= cell_northeast->effBottomBorderValue( cellRef.x() + 1, + cellRef.y() - 1 ) ) + horz_pen = cell_east->effTopBorderPen( cellRef.x() + 1, cellRef.y() ); + else + horz_pen = cell_northeast->effBottomBorderPen( cellRef.x() + 1, + cellRef.y() - 1 ); + + // horz_pen = effTopBorderPen( cellRef.x() + 1, cellRef.y() ); + horz_penWidth = TQMAX( 1, doc->zoomItY( horz_pen.width() ) ); + int bottom = ( TQMAX( 0, -1 + horz_penWidth ) ) / 2 + 1; + + painter.setPen( vert_pen ); + //If we are on paper printout, we limit the length of the lines + //On paper, we always have full cells, on screen not + if ( painter.device()->isExtDev() ) { + if ( sheetDir == Sheet::RightToLeft ) + painter.drawLine( TQMAX( zrect_left, zcellRect_left ), + TQMAX( zrect_top, zcellRect_top ), + TQMIN( zrect_right, zcellRect_left ), + TQMIN( zrect_bottom, zcellRect_top + bottom ) ); + else + painter.drawLine( TQMAX( zrect_left, zcellRect_right ), + TQMAX( zrect_top, zcellRect_top ), + TQMIN( zrect_right, zcellRect_right ), + TQMIN( zrect_bottom, zcellRect_top + bottom ) ); + } + else { + if ( sheetDir == Sheet::RightToLeft ) + painter.drawLine( zcellRect_left, zcellRect_top, + zcellRect_left, zcellRect_top + bottom ); + else + painter.drawLine( zcellRect_right, zcellRect_top, + zcellRect_right, zcellRect_top + bottom ); + } + } + + // Bottom + if ( cellRef.y() < KS_rowMax ) { + // Fix the borders which meet at the bottom left corner + if ( cell_south->effLeftBorderValue( cellRef.x(), cellRef.y() + 1 ) + >= cell_southwest->effRightBorderValue( cellRef.x() - 1, + cellRef.y() + 1 ) ) + vert_pen = cell_south->effLeftBorderPen( cellRef.x(), cellRef.y() + 1 ); + else + vert_pen = cell_southwest->effRightBorderPen( cellRef.x() - 1, + cellRef.y() + 1 ); + + // vert_pen = effLeftBorderPen( cellRef.x(), cellRef.y() + 1 ); + vert_penWidth = TQMAX( 1, doc->zoomItY( vert_pen.width() ) ); + vert_pen.setWidth( vert_penWidth ); + if ( vert_pen.style() != TQt::NoPen ) { + if ( cell_west->effBottomBorderValue( cellRef.x() - 1, cellRef.y() ) + >= cell_southwest->effTopBorderValue( cellRef.x() - 1, + cellRef.y() + 1 ) ) + horz_pen = cell_west->effBottomBorderPen( cellRef.x() - 1, + cellRef.y() ); + else + horz_pen = cell_southwest->effTopBorderPen( cellRef.x() - 1, + cellRef.y() + 1 ); + + // horz_pen = effBottomBorderPen( cellRef.x() - 1, cellRef.y() ); + horz_penWidth = TQMAX( 1, doc->zoomItX( horz_pen.width() ) ); + int bottom = ( TQMAX( 0, -1 + horz_penWidth ) ) / 2; + + painter.setPen( vert_pen ); + // If we are on paper printout, we limit the length of the lines. + // On paper, we always have full cells, on screen not. + if ( painter.device()->isExtDev() ) { + if ( sheetDir == Sheet::RightToLeft ) + painter.drawLine( TQMAX( zrect_left, zcellRect_right ), + TQMAX( zrect_top, zcellRect_bottom - bottom ), + TQMIN( zrect_right, zcellRect_right ), + TQMIN( zrect_bottom, zcellRect_bottom ) ); + else + painter.drawLine( TQMAX( zrect_left, zcellRect_left ), + TQMAX( zrect_top, zcellRect_bottom - bottom ), + TQMIN( zrect_right, zcellRect_left ), + TQMIN( zrect_bottom, zcellRect_bottom ) ); + } + else { + if ( sheetDir == Sheet::RightToLeft ) + painter.drawLine( zcellRect_right, zcellRect_bottom - bottom, + zcellRect_right, zcellRect_bottom ); + else + painter.drawLine( zcellRect_left, zcellRect_bottom - bottom, + zcellRect_left, zcellRect_bottom ); + } + } + + // Fix the borders which meet at the bottom right corner + if ( cell_south->effRightBorderValue( cellRef.x(), cellRef.y() + 1 ) + >= cell_southeast->effLeftBorderValue( cellRef.x() + 1, + cellRef.y() + 1 ) ) + vert_pen = cell_south->effRightBorderPen( cellRef.x(), cellRef.y() + 1 ); + else + vert_pen = cell_southeast->effLeftBorderPen( cellRef.x() + 1, + cellRef.y() + 1 ); + + // vert_pen = effRightBorderPen( cellRef.x(), cellRef.y() + 1 ); + vert_penWidth = TQMAX( 1, doc->zoomItY( vert_pen.width() ) ); + vert_pen.setWidth( vert_penWidth ); + if ( ( vert_pen.style() != TQt::NoPen ) && ( cellRef.x() < KS_colMax ) ) { + if ( cell_east ->effBottomBorderValue( cellRef.x() + 1, cellRef.y() ) + >= cell_southeast->effTopBorderValue( cellRef.x() + 1, + cellRef.y() + 1 ) ) + + horz_pen = format()->sheet()->cellAt( cellRef.x() + 1, cellRef.y() ) + ->effBottomBorderPen( cellRef.x() + 1, cellRef.y() ); + else + horz_pen = format()->sheet()->cellAt( cellRef.x() + 1, cellRef.y() + 1 ) + ->effTopBorderPen( cellRef.x() + 1, cellRef.y() + 1 ); + + // horz_pen = effBottomBorderPen( cellRef.x() + 1, cellRef.y() ); + horz_penWidth = TQMAX( 1, doc->zoomItX( horz_pen.width() ) ); + int bottom = ( TQMAX( 0, -1 + horz_penWidth ) ) / 2; + + painter.setPen( vert_pen ); + // If we are on paper printout, we limit the length of the lines. + // On paper, we always have full cells, on screen not. + if ( painter.device()->isExtDev() ) { + if ( sheetDir == Sheet::RightToLeft ) + painter.drawLine( TQMAX( zrect_left, zcellRect_left ), + TQMAX( zrect_top, zcellRect_bottom - bottom ), + TQMIN( zrect_right, zcellRect_left ), + TQMIN( zrect_bottom, zcellRect_bottom ) ); + else + painter.drawLine( TQMAX( zrect_left, zcellRect_right ), + TQMAX( zrect_top, zcellRect_bottom - bottom ), + TQMIN( zrect_right, zcellRect_right ), + TQMIN( zrect_bottom, zcellRect_bottom ) ); + } + else { + if ( sheetDir == Sheet::RightToLeft ) + painter.drawLine( zcellRect_left, zcellRect_bottom - bottom, + zcellRect_left, zcellRect_bottom ); + else + painter.drawLine( zcellRect_right, zcellRect_bottom - bottom, + zcellRect_right, zcellRect_bottom ); + } + } + } + #endif +} + + +// Paint diagonal lines through the cell. +// +void Cell::paintCellDiagonalLines( TQPainter& painter, + const KoRect &cellRect, + const TQPoint &cellRef ) +{ + if ( isPartOfMerged() ) + return; + + Doc* doc = sheet()->doc(); + + if ( effFallDiagonalPen( cellRef.x(), cellRef.y() ).style() != TQt::NoPen ) { + painter.setPen( effFallDiagonalPen( cellRef.x(), cellRef.y() ) ); + painter.drawLine( doc->zoomItX( cellRect.x() ), + doc->zoomItY( cellRect.y() ), + doc->zoomItX( cellRect.right() ), + doc->zoomItY( cellRect.bottom() ) ); + } + + if ( effGoUpDiagonalPen( cellRef.x(), cellRef.y() ).style() != TQt::NoPen ) { + painter.setPen( effGoUpDiagonalPen( cellRef.x(), cellRef.y() ) ); + painter.drawLine( doc->zoomItX( cellRect.x() ), + doc->zoomItY( cellRect.bottom() ), + doc->zoomItX( cellRect.right() ), + doc->zoomItY( cellRect.y() ) ); + } +} + + +// End of Painting +// ================================================================ + + +int Cell::defineAlignX() +{ + int a = format()->align( column(), row() ); + if ( a == Format::Undefined ) + { + //numbers should be right-aligned by default, as well as BiDi text + if ((formatType() == Text_format) || value().isString()) + a = (d->strOutText.isRightToLeft()) ? + Format::Right : Format::Left; + else { + Value val = value(); + while (val.isArray()) val = val.element (0, 0); + if (val.isBoolean() || val.isNumber()) + a = Format::Right; + else + a = Format::Left; + } + } + return a; +} + +int Cell::effAlignX() +{ + if ( d->hasExtra() && d->extra()->conditions + && d->extra()->conditions->matchedStyle() + && d->extra()->conditions->matchedStyle()->hasFeature( Style::SAlignX, true ) ) + return d->extra()->conditions->matchedStyle()->alignX(); + + return defineAlignX(); +} + +// Cut strOutText, so that it only holds the part that can be displayed. +// +// Used in paintText(). +// + +TQString Cell::textDisplaying( TQPainter &_painter ) +{ + TQFontMetrics fm = _painter.fontMetrics(); + int a = format()->align( column(), row() ); + + bool isNumeric = value().isNumber(); + + if ( !format()->verticalText( column(),row() ) ) { + // Non-vertical text: the ordinary case. + + // Not enough space but align to left + double len = 0.0; + int extraXCells = d->hasExtra() ? d->extra()->extraXCells : 0; + + for ( int i = column(); i <= column() + extraXCells; i++ ) { + ColumnFormat *cl2 = format()->sheet()->columnFormat( i ); + len += cl2->dblWidth() - 1.0; //-1.0 because the pixel in between 2 cells is shared between both cells + } + + TQString tmp; + double tmpIndent = 0.0; + if ( !isEmpty() ) + tmpIndent = format()->getIndent( column(), row() ); + + // Start out with the whole text, cut one character at a time, and + // when the text finally fits, return it. + for ( int i = d->strOutText.length(); i != 0; i-- ) + { + //Note that numbers are always treated as left-aligned since if we have to cut digits off, they should + //always be the least significant ones at the end of the string + if ( a == Format::Left || a == Format::Undefined || isNumeric) + tmp = d->strOutText.left(i); + else if ( a == Format::Right) + tmp = d->strOutText.right(i); + else + tmp = d->strOutText.mid( ( d->strOutText.length() - i ) / 2, i); + + if (isNumeric) + { + //For numeric values, we can cut off digits after the decimal point to make it fit, + //but not the integer part of the number. + //If this number still contains a fraction part then we don't need to do anything, if we have run + //out of space to fit even the integer part of the number then display ######### + //TODO Perhaps try to display integer part in standard form if there is not enough room for it? + + if (!tmp.contains('.')) + d->strOutText=TQString().fill('#',20); + } + + // 4 equal length of red triangle +1 point. + if ( format()->sheet()->doc()->unzoomItX( fm.width( tmp ) ) + tmpIndent + < len - 4.0 - 1.0 ) + { + if ( format()->getAngle( column(), row() ) != 0 ) + { + TQString tmp2; + RowFormat *rl = format()->sheet()->rowFormat( row() ); + if ( d->textHeight > rl->dblHeight() ) + { + for ( int j = d->strOutText.length(); j != 0; j-- ) + { + tmp2 = d->strOutText.left( j ); + if ( format()->sheet()->doc()->unzoomItY( fm.width( tmp2 ) ) < rl->dblHeight() - 1.0 ) + { + return d->strOutText.left( TQMIN( tmp.length(), tmp2.length() ) ); + } + } + } + else + return tmp; + + } + else + return tmp; + } + } + return TQString( "" ); + } + else if ( format()->verticalText( column(), row() ) ) { + // Vertical text. + + RowFormat *rl = format()->sheet()->rowFormat( row() ); + double tmpIndent = 0.0; + + // Not enough space but align to left. + double len = 0.0; + int extraXCells = d->hasExtra() ? d->extra()->extraXCells : 0; + + for ( int i = column(); i <= column() + extraXCells; i++ ) { + ColumnFormat *cl2 = format()->sheet()->columnFormat( i ); + + // -1.0 because the pixel in between 2 cells is shared between both cells + len += cl2->dblWidth() - 1.0; + } + + if ( !isEmpty() ) + tmpIndent = format()->getIndent( column(), row() ); + + if ( ( d->textWidth + tmpIndent > len ) || d->textWidth == 0.0 ) + return TQString( "" ); + + for ( int i = d->strOutText.length(); i != 0; i-- ) { + if ( format()->sheet()->doc()->unzoomItY( fm.ascent() + fm.descent() ) * i + < rl->dblHeight() - 1.0 ) + return d->strOutText.left( i ); + } + + return TQString( "" ); + } + + ColumnFormat *cl = format()->sheet()->columnFormat( column() ); + double w = cl->dblWidth(); + + if ( d->hasExtra() && (d->extra()->extraWidth != 0.0) ) + w = d->extra()->extraWidth; + + TQString tmp; + for ( int i = d->strOutText.length(); i != 0; i-- ) { + tmp = d->strOutText.left( i ); + + // 4 equals length of red triangle +1 pixel + if ( format()->sheet()->doc()->unzoomItX( fm.width( tmp ) ) < w - 4.0 - 1.0 ) + return tmp; + } + + return TQString(); +} + + +double Cell::dblWidth( int _col, const Canvas *_canvas ) const +{ + if ( _col < 0 ) + _col = d->column; + + if ( _canvas ) + { + if ( testFlag(Flag_Merged) ) + return d->extra()->extraWidth; + + const ColumnFormat *cl = format()->sheet()->columnFormat( _col ); + return cl->dblWidth( _canvas ); + } + + if ( testFlag(Flag_Merged) ) + return d->extra()->extraWidth; + + const ColumnFormat *cl = format()->sheet()->columnFormat( _col ); + return cl->dblWidth(); +} + +int Cell::width( int _col, const Canvas *_canvas ) const +{ + return int( dblWidth( _col, _canvas ) ); +} + +double Cell::dblHeight( int _row, const Canvas *_canvas ) const +{ + if ( _row < 0 ) + _row = d->row; + + if ( _canvas ) + { + if ( testFlag(Flag_Merged) ) + return d->extra()->extraHeight; + + const RowFormat *rl = format()->sheet()->rowFormat( _row ); + return rl->dblHeight( _canvas ); + } + + if ( testFlag(Flag_Merged) ) + return d->extra()->extraHeight; + + const RowFormat *rl = format()->sheet()->rowFormat( _row ); + return rl->dblHeight(); +} + +int Cell::height( int _row, const Canvas *_canvas ) const +{ + return int( dblHeight( _row, _canvas ) ); +} + +/////////////////////////////////////////// +// +// Misc Properties. +// Reimplementation of Format methods. +// +/////////////////////////////////////////// + +const TQBrush& Cell::backGroundBrush( int _col, int _row ) const +{ + if ( d->hasExtra() && (!d->extra()->obscuringCells.isEmpty()) ) + { + const Cell* cell = d->extra()->obscuringCells.first(); + return cell->backGroundBrush( cell->column(), cell->row() ); + } + + return format()->backGroundBrush( _col, _row ); +} + +const TQColor& Cell::bgColor( int _col, int _row ) const +{ + if ( d->hasExtra() && (!d->extra()->obscuringCells.isEmpty()) ) + { + const Cell* cell = d->extra()->obscuringCells.first(); + return cell->bgColor( cell->column(), cell->row() ); + } + + return format()->bgColor( _col, _row ); +} + +/////////////////////////////////////////// +// +// Borders. +// Reimplementation of Format methods. +// +/////////////////////////////////////////// + +void Cell::setLeftBorderPen( const TQPen& p ) +{ + if ( column() == 1 ) + { + Cell* cell = format()->sheet()->cellAt( column() - 1, row() ); + if ( cell && cell->format()->hasProperty( Format::PRightBorder ) + && format()->sheet()->cellAt( column(), row() ) == this ) + cell->format()->clearProperty( Format::PRightBorder ); + } + + format()->setLeftBorderPen( p ); +} + +void Cell::setTopBorderPen( const TQPen& p ) +{ + if ( row() == 1 ) + { + Cell* cell = format()->sheet()->cellAt( column(), row() - 1 ); + if ( cell && cell->format()->hasProperty( Format::PBottomBorder ) + && format()->sheet()->cellAt( column(), row() ) == this ) + cell->format()->clearProperty( Format::PBottomBorder ); + } + format()->setTopBorderPen( p ); +} + +void Cell::setRightBorderPen( const TQPen& p ) +{ + Cell* cell = 0L; + if ( column() < KS_colMax ) + cell = format()->sheet()->cellAt( column() + 1, row() ); + + if ( cell && cell->format()->hasProperty( Format::PLeftBorder ) + && format()->sheet()->cellAt( column(), row() ) == this ) + cell->format()->clearProperty( Format::PLeftBorder ); + + format()->setRightBorderPen( p ); +} + +void Cell::setBottomBorderPen( const TQPen& p ) +{ + Cell* cell = 0L; + if ( row() < KS_rowMax ) + cell = format()->sheet()->cellAt( column(), row() + 1 ); + + if ( cell && cell->format()->hasProperty( Format::PTopBorder ) + && format()->sheet()->cellAt( column(), row() ) == this ) + cell->format()->clearProperty( Format::PTopBorder ); + + format()->setBottomBorderPen( p ); +} + +const TQPen& Cell::rightBorderPen( int _col, int _row ) const +{ + if ( !format()->hasProperty( Format::PRightBorder ) && ( _col < KS_colMax ) ) + { + Cell * cell = format()->sheet()->cellAt( _col + 1, _row ); + if ( cell && cell->format()->hasProperty( Format::PLeftBorder ) ) + return cell->leftBorderPen( _col + 1, _row ); + } + + return format()->rightBorderPen( _col, _row ); +} + +const TQPen& Cell::leftBorderPen( int _col, int _row ) const +{ + if ( !format()->hasProperty( Format::PLeftBorder ) ) + { + const Cell * cell = format()->sheet()->cellAt( _col - 1, _row ); + if ( cell && cell->format()->hasProperty( Format::PRightBorder ) ) + return cell->rightBorderPen( _col - 1, _row ); + } + + return format()->leftBorderPen( _col, _row ); +} + +const TQPen& Cell::bottomBorderPen( int _col, int _row ) const +{ + if ( !format()->hasProperty( Format::PBottomBorder ) && ( _row < KS_rowMax ) ) + { + const Cell * cell = format()->sheet()->cellAt( _col, _row + 1 ); + if ( cell && cell->format()->hasProperty( Format::PTopBorder ) ) + return cell->topBorderPen( _col, _row + 1 ); + } + + return format()->bottomBorderPen( _col, _row ); +} + +const TQPen& Cell::topBorderPen( int _col, int _row ) const +{ + if ( !format()->hasProperty( Format::PTopBorder ) ) + { + const Cell * cell = format()->sheet()->cellAt( _col, _row - 1 ); + if ( cell->format()->hasProperty( Format::PBottomBorder ) ) + return cell->bottomBorderPen( _col, _row - 1 ); + } + + return format()->topBorderPen( _col, _row ); +} + +const TQColor & Cell::effTextColor( int col, int row ) const +{ + if ( d->hasExtra() && d->extra()->conditions + && d->extra()->conditions->matchedStyle() + && d->extra()->conditions->matchedStyle()->hasFeature( Style::STextPen, true ) ) + return d->extra()->conditions->matchedStyle()->pen().color(); + + return format()->textColor( col, row ); +} + +const TQPen& Cell::effLeftBorderPen( int col, int row ) const +{ + if ( isPartOfMerged() ) + { + Cell * cell = d->extra()->obscuringCells.first(); + return cell->effLeftBorderPen( cell->column(), cell->row() ); + } + + if ( d->hasExtra() && d->extra()->conditions + && d->extra()->conditions->matchedStyle() + && d->extra()->conditions->matchedStyle()->hasFeature( Style::SLeftBorder, true ) ) + return d->extra()->conditions->matchedStyle()->leftBorderPen(); + + return leftBorderPen( col, row ); +} + +const TQPen& Cell::effTopBorderPen( int col, int row ) const +{ + if ( isPartOfMerged() ) + { + Cell * cell = d->extra()->obscuringCells.first(); + return cell->effTopBorderPen( cell->column(), cell->row() ); + } + + if ( d->hasExtra() && d->extra()->conditions + && d->extra()->conditions->matchedStyle() + && d->extra()->conditions->matchedStyle()->hasFeature( Style::STopBorder, true ) ) + return d->extra()->conditions->matchedStyle()->topBorderPen(); + + return topBorderPen( col, row ); +} + +const TQPen& Cell::effRightBorderPen( int col, int row ) const +{ + if ( isPartOfMerged() ) + { + Cell * cell = d->extra()->obscuringCells.first(); + return cell->effRightBorderPen( cell->column(), cell->row() ); + } + + if ( d->hasExtra() && d->extra()->conditions + && d->extra()->conditions->matchedStyle() + && d->extra()->conditions->matchedStyle()->hasFeature( Style::SRightBorder, true ) ) + return d->extra()->conditions->matchedStyle()->rightBorderPen(); + + return rightBorderPen( col, row ); +} + +const TQPen& Cell::effBottomBorderPen( int col, int row ) const +{ + if ( isPartOfMerged() ) + { + Cell * cell = d->extra()->obscuringCells.first(); + return cell->effBottomBorderPen( cell->column(), cell->row() ); + } + + if ( d->hasExtra() && d->extra()->conditions + && d->extra()->conditions->matchedStyle() + && d->extra()->conditions->matchedStyle()->hasFeature( Style::SBottomBorder, true ) ) + return d->extra()->conditions->matchedStyle()->bottomBorderPen(); + + return bottomBorderPen( col, row ); +} + +const TQPen & Cell::effGoUpDiagonalPen( int col, int row ) const +{ + if ( d->hasExtra() && d->extra()->conditions + && d->extra()->conditions->matchedStyle() + && d->extra()->conditions->matchedStyle()->hasFeature( Style::SGoUpDiagonal, true ) ) + return d->extra()->conditions->matchedStyle()->goUpDiagonalPen(); + + return format()->goUpDiagonalPen( col, row ); +} + +const TQPen & Cell::effFallDiagonalPen( int col, int row ) const +{ + if ( d->hasExtra() && d->extra()->conditions + && d->extra()->conditions->matchedStyle() + && d->extra()->conditions->matchedStyle()->hasFeature( Style::SFallDiagonal, true ) ) + return d->extra()->conditions->matchedStyle()->fallDiagonalPen(); + + return format()->fallDiagonalPen( col, row ); +} + +uint Cell::effBottomBorderValue( int col, int row ) const +{ + if ( isPartOfMerged() ) + { + Cell * cell = d->extra()->obscuringCells.first(); + return cell->effBottomBorderValue( cell->column(), cell->row() ); + } + + if ( d->hasExtra() && d->extra()->conditions + && d->extra()->conditions->matchedStyle() ) + return d->extra()->conditions->matchedStyle()->bottomPenValue(); + + return format()->bottomBorderValue( col, row ); +} + +uint Cell::effRightBorderValue( int col, int row ) const +{ + if ( isPartOfMerged() ) + { + Cell * cell = d->extra()->obscuringCells.first(); + return cell->effRightBorderValue( cell->column(), cell->row() ); + } + + if ( d->hasExtra() && d->extra()->conditions + && d->extra()->conditions->matchedStyle() ) + return d->extra()->conditions->matchedStyle()->rightPenValue(); + + return format()->rightBorderValue( col, row ); +} + +uint Cell::effLeftBorderValue( int col, int row ) const +{ + if ( isPartOfMerged() ) + { + Cell * cell = d->extra()->obscuringCells.first(); + return cell->effLeftBorderValue( cell->column(), cell->row() ); + } + + if ( d->hasExtra() && d->extra()->conditions + && d->extra()->conditions->matchedStyle() ) + return d->extra()->conditions->matchedStyle()->leftPenValue(); + + return format()->leftBorderValue( col, row ); +} + +uint Cell::effTopBorderValue( int col, int row ) const +{ + if ( isPartOfMerged() ) + { + Cell * cell = d->extra()->obscuringCells.first(); + return cell->effTopBorderValue( cell->column(), cell->row() ); + } + + if ( d->hasExtra() && d->extra()->conditions + && d->extra()->conditions->matchedStyle() ) + return d->extra()->conditions->matchedStyle()->topPenValue(); + + return format()->topBorderValue( col, row ); +} + +/////////////////////////////////////////// +// +// Precision +// +/////////////////////////////////////////// + +void Cell::incPrecision() +{ + //TODO: This is ugly. Why not simply regenerate the text to display? Tomas + + if ( !value().isNumber() ) + return; + int tmpPreci = format()->precision( column(), row() ); + + if ( tmpPreci == -1 ) + { + int pos = d->strOutText.find(decimal_point); + if ( pos == -1 ) + pos = d->strOutText.find('.'); + if ( pos == -1 ) + format()->setPrecision(1); + else + { + int start = 0; + if ( d->strOutText.find('%') != -1 ) + start = 2; + else if ( d->strOutText.find(locale()->currencySymbol()) == ((int)(d->strOutText.length()-locale()->currencySymbol().length())) ) + start = locale()->currencySymbol().length() + 1; + else if ( (start=d->strOutText.find('E')) != -1 ) + start = d->strOutText.length() - start; + + //kdDebug(36001) << "start=" << start << " pos=" << pos << " length=" << d->strOutText.length() << endl; + format()->setPrecision( TQMAX( 0, (int)d->strOutText.length() - start - pos ) ); + } + } + else if ( tmpPreci < 10 ) + { + format()->setPrecision( ++tmpPreci ); + } + setFlag(Flag_LayoutDirty); +} + +void Cell::decPrecision() +{ + //TODO: This is ugly. Why not simply regenerate the text to display? Tomas + + if ( !value().isNumber() ) + return; + int preciTmp = format()->precision( column(), row() ); +// kdDebug(36001) << "decPrecision: tmpPreci = " << tmpPreci << endl; + if ( format()->precision(column(),row()) == -1 ) + { + int pos = d->strOutText.find( decimal_point ); + int start = 0; + if ( d->strOutText.find('%') != -1 ) + start = 2; + else if ( d->strOutText.find(locale()->currencySymbol()) == ((int)(d->strOutText.length()-locale()->currencySymbol().length())) ) + start = locale()->currencySymbol().length() + 1; + else if ( (start = d->strOutText.find('E')) != -1 ) + start = d->strOutText.length() - start; + else + start = 0; + + if ( pos == -1 ) + return; + + format()->setPrecision(d->strOutText.length() - pos - 2 - start); + // if ( preciTmp < 0 ) + // format()->setPrecision( preciTmp ); + } + else if ( preciTmp > 0 ) + { + format()->setPrecision( --preciTmp ); + } + setFlag( Flag_LayoutDirty ); +} + +//set numerical value +//used in Sheet::setSeries (nowhere else yet) +void Cell::setNumber( double number ) +{ + setValue( Value( number ) ); + + d->strText.setNum( number ); + setDisplayText(d->strText); + checkNumberFormat(); +} + +void Cell::setCellText( const TQString& _text, bool asText ) +{ + // TQString ctext = _text; + +// (Tomas) is this trim necessary for anything ? +// if( ctext.length() > 5000 ) +// ctext = ctext.left( 5000 ); + + // empty string ? + if (_text.length() == 0) { + d->strOutText = d->strText = ""; + setValue (Value::empty()); + return; + } + + // as text ? + if (asText) { + d->strOutText = _text; + d->strText = _text; + setValue (Value (_text)); + + return; + } + + TQString oldText = d->strText; + setDisplayText( _text ); + if(!format()->sheet()->isLoading() && !testValidity() ) + { + //reapply old value if action == stop + setDisplayText( oldText ); + } +} + +void Cell::setDisplayText( const TQString& _text ) +{ + bool isLoading = format()->sheet()->isLoading(); + + if (!isLoading) + format()->sheet()->doc()->emitBeginOperation( false ); + + d->strText = _text; + + /** + * A real formula "=A1+A2*3" was entered. + */ + if ( !d->strText.isEmpty() && d->strText[0] == '=' ) + { + setFlag(Flag_LayoutDirty); + setFlag(Flag_TextFormatDirty); + + if ( !makeFormula() ) + kdError(36001) << "ERROR: Syntax ERROR" << endl; + setCalcDirtyFlag (); + } + + /** + * Some numeric value or a string. + */ + else + { + // Find out what data type it is + checkTextInput(); + + setFlag(Flag_LayoutDirty); + setFlag(Flag_TextFormatDirty); + } + + if ( !isLoading ) + format()->sheet()->doc()->emitEndOperation( TQRect( d->column, d->row, 1, 1 ) ); +} + +void Cell::setLink( const TQString& link ) +{ + d->extra()->link = link; + + if( !link.isEmpty() && d->strText.isEmpty() ) + setCellText( link ); +} + +TQString Cell::link() const +{ + return d->hasExtra() ? d->extra()->link : TQString(); +} + +void Cell::update() +{ + /* those obscuring us need to redo their layout cause they can't obscure us + now that we've got text. + This includes cells obscuring cells that we are obscuring + */ + for (int x = d->column; x <= d->column + extraXCells(); x++) + { + for (int y = d->row; y <= d->row + extraYCells(); y++) + { + Cell* cell = format()->sheet()->cellAt(x,y); + cell->setLayoutDirtyFlag(); + } + } + + setCalcDirtyFlag(); + + /* TODO - is this a good place for this? */ + updateChart(true); +} + +bool Cell::testValidity() const +{ + bool valid = false; + if( d->hasExtra() && d->extra()->validity && d->extra()->validity->m_restriction != Restriction::None ) + { + //fixme + if ( d->extra()->validity->allowEmptyCell && d->strText.isEmpty() ) + return true; + + if( value().isNumber() && + (d->extra()->validity->m_restriction == Restriction::Number || + (d->extra()->validity->m_restriction == Restriction::Integer && + value().asFloat() == ceil(value().asFloat())))) + { + switch( d->extra()->validity->m_cond) + { + case Conditional::Equal: + valid = ( value().asFloat() - d->extra()->validity->valMin < DBL_EPSILON + && value().asFloat() - d->extra()->validity->valMin > + (0.0 - DBL_EPSILON)); + break; + case Conditional::DifferentTo: + valid = !( ( value().asFloat() - d->extra()->validity->valMin < DBL_EPSILON + && value().asFloat() - d->extra()->validity->valMin > + (0.0 - DBL_EPSILON)) ); + break; + case Conditional::Superior: + valid = ( value().asFloat() > d->extra()->validity->valMin); + break; + case Conditional::Inferior: + valid = ( value().asFloat() <d->extra()->validity->valMin); + break; + case Conditional::SuperiorEqual: + valid = ( value().asFloat() >= d->extra()->validity->valMin); + break; + case Conditional::InferiorEqual: + valid = (value().asFloat() <= d->extra()->validity->valMin); + break; + case Conditional::Between: + valid = ( value().asFloat() >= d->extra()->validity->valMin && + value().asFloat() <= d->extra()->validity->valMax); + break; + case Conditional::Different: + valid = (value().asFloat() < d->extra()->validity->valMin || + value().asFloat() > d->extra()->validity->valMax); + break; + default : + break; + } + } + else if(d->extra()->validity->m_restriction==Restriction::Text) + { + valid = value().isString(); + } + else if ( d->extra()->validity->m_restriction == Restriction::List ) + { + //test int value + if ( value().isString() && d->extra()->validity->listValidity.contains( value().asString() ) ) + valid = true; + } + else if(d->extra()->validity->m_restriction==Restriction::TextLength) + { + if( value().isString() ) + { + int len = d->strOutText.length(); + switch( d->extra()->validity->m_cond) + { + case Conditional::Equal: + if (len == d->extra()->validity->valMin) + valid = true; + break; + case Conditional::DifferentTo: + if (len != d->extra()->validity->valMin) + valid = true; + break; + case Conditional::Superior: + if(len > d->extra()->validity->valMin) + valid = true; + break; + case Conditional::Inferior: + if(len < d->extra()->validity->valMin) + valid = true; + break; + case Conditional::SuperiorEqual: + if(len >= d->extra()->validity->valMin) + valid = true; + break; + case Conditional::InferiorEqual: + if(len <= d->extra()->validity->valMin) + valid = true; + break; + case Conditional::Between: + if(len >= d->extra()->validity->valMin && len <= d->extra()->validity->valMax) + valid = true; + break; + case Conditional::Different: + if(len <d->extra()->validity->valMin || len >d->extra()->validity->valMax) + valid = true; + break; + default : + break; + } + } + } + else if(d->extra()->validity->m_restriction == Restriction::Time && isTime()) + { + switch( d->extra()->validity->m_cond) + { + case Conditional::Equal: + valid = (value().asTime() == d->extra()->validity->timeMin); + break; + case Conditional::DifferentTo: + valid = (value().asTime() != d->extra()->validity->timeMin); + break; + case Conditional::Superior: + valid = (value().asTime() > d->extra()->validity->timeMin); + break; + case Conditional::Inferior: + valid = (value().asTime() < d->extra()->validity->timeMin); + break; + case Conditional::SuperiorEqual: + valid = (value().asTime() >= d->extra()->validity->timeMin); + break; + case Conditional::InferiorEqual: + valid = (value().asTime() <= d->extra()->validity->timeMin); + break; + case Conditional::Between: + valid = (value().asTime() >= d->extra()->validity->timeMin && + value().asTime() <= d->extra()->validity->timeMax); + break; + case Conditional::Different: + valid = (value().asTime() < d->extra()->validity->timeMin || + value().asTime() > d->extra()->validity->timeMax); + break; + default : + break; + + } + } + else if(d->extra()->validity->m_restriction == Restriction::Date && isDate()) + { + switch( d->extra()->validity->m_cond) + { + case Conditional::Equal: + valid = (value().asDate() == d->extra()->validity->dateMin); + break; + case Conditional::DifferentTo: + valid = (value().asDate() != d->extra()->validity->dateMin); + break; + case Conditional::Superior: + valid = (value().asDate() > d->extra()->validity->dateMin); + break; + case Conditional::Inferior: + valid = (value().asDate() < d->extra()->validity->dateMin); + break; + case Conditional::SuperiorEqual: + valid = (value().asDate() >= d->extra()->validity->dateMin); + break; + case Conditional::InferiorEqual: + valid = (value().asDate() <= d->extra()->validity->dateMin); + break; + case Conditional::Between: + valid = (value().asDate() >= d->extra()->validity->dateMin && + value().asDate() <= d->extra()->validity->dateMax); + break; + case Conditional::Different: + valid = (value().asDate() < d->extra()->validity->dateMin || + value().asDate() > d->extra()->validity->dateMax); + break; + default : + break; + + } + } + } + else + { + valid= true; + } + + if(!valid &&d->extra()->validity != NULL && d->extra()->validity->displayMessage) + { + switch (d->extra()->validity->m_action ) + { + case Action::Stop: + KMessageBox::error((TQWidget*)0L, d->extra()->validity->message, + d->extra()->validity->title); + break; + case Action::Warning: + KMessageBox::warningYesNo((TQWidget*)0L, d->extra()->validity->message, + d->extra()->validity->title); + break; + case Action::Information: + KMessageBox::information((TQWidget*)0L, d->extra()->validity->message, + d->extra()->validity->title); + break; + } + } + if (!d->hasExtra()) + return true; //okay if there's no validity + return (valid || d->extra()->validity == NULL || d->extra()->validity->m_action != Action::Stop); +} + +FormatType Cell::formatType() const +{ + return format()->getFormatType( d->column, d->row ); +} + +double Cell::textWidth() const +{ + return d->textWidth; +} + +double Cell::textHeight() const +{ + return d->textHeight; +} + +int Cell::mergedXCells() const +{ + return d->hasExtra() ? d->extra()->mergedXCells : 0; +} + +int Cell::mergedYCells() const +{ + return d->hasExtra() ? d->extra()->mergedYCells : 0; +} + +int Cell::extraXCells() const +{ + return d->hasExtra() ? d->extra()->extraXCells : 0; +} + +int Cell::extraYCells() const +{ + return d->hasExtra() ? d->extra()->extraYCells : 0; +} + +double Cell::extraWidth() const +{ + return d->hasExtra() ? d->extra()->extraWidth : 0; +} + +double Cell::extraHeight() const +{ + return d->hasExtra() ? d->extra()->extraHeight : 0; +} + + +bool Cell::isDate() const +{ + FormatType ft = formatType(); + + return (formatIsDate (ft) || ((ft == Generic_format) && + (value().format() == Value::fmt_Date))); +} + +bool Cell::isTime() const +{ + FormatType ft = formatType(); + + return (formatIsTime (ft) || ((ft == Generic_format) && + (value().format() == Value::fmt_Time))); +} + +void Cell::setCalcDirtyFlag() +{ + if ( !isFormula() ) + { + //don't set the flag if we don't hold a formula + clearFlag(Flag_CalcDirty); + return; + } + setFlag(Flag_CalcDirty); + format()->sheet()->setRegionPaintDirty(cellRect()); +} + + +bool Cell::updateChart(bool refresh) +{ + // Update a chart for example if it depends on this cell. + if ( d->row != 0 && d->column != 0 ) + { + CellBinding *bind; + for ( bind = format()->sheet()->firstCellBinding(); bind != 0L; bind = format()->sheet()->nextCellBinding() ) + { + if ( bind->contains( d->column, d->row ) ) + { + if (!refresh) + return true; + + bind->cellChanged( this ); + } + } + return true; + } + return false; + +} + +double Cell::getDouble () +{ + if (isDefault()) + return 0.0; + //(Tomas) umm can't we simply call value().asFloat() ? + if (isDate()) + { + TQDate date = value().asDate(); + TQDate dummy (1900, 1, 1); + return (dummy.daysTo (date) + 1); + } + if (isTime()) + { + TQTime time = value().asTime(); + TQTime dummy; + return dummy.secsTo( time ); + } + if (value().isNumber()) + return value().asFloat(); + + return 0.0; +} + +void Cell::convertToDouble () +{ + if (isDefault()) + return; + + setValue (getDouble ()); +} + +void Cell::convertToPercent () +{ + if (isDefault()) + return; + + setValue (getDouble ()); + d->value.setFormat (Value::fmt_Percent); +} + +void Cell::convertToMoney () +{ + if (isDefault()) + return; + + setValue (getDouble ()); + d->value.setFormat (Value::fmt_Money); + format()->setPrecision (locale()->fracDigits()); +} + +void Cell::convertToTime () +{ + //(Tomas) This is weird. And I mean *REALLY* weird. First, we + //generate a time (TQTime), then we convert it to text, then + //we give the text to the cell and ask it to parse it. Weird... + + if (isDefault() || isEmpty()) + return; + + setValue (getDouble ()); + TQTime time = value().asDateTime().time(); + int msec = (int) ( (value().asFloat() - (int) value().asFloat()) * 1000 ); + time = time.addMSecs( msec ); + setCellText( time.toString() ); +} + +void Cell::convertToDate () +{ + //(Tomas) This is weird. And I mean *REALLY* weird. First, we + //generate a date (TQDate), then we convert it to text, then + //we give the text to the cell and ask it to parse it. Weird... + + if (isDefault() || isEmpty()) + return; + + setValue (getDouble ()); + + //TODO: why did we call setValue(), when we override it here? + TQDate date(1900, 1, 1); + date = date.addDays( (int) value().asFloat() - 1 ); + date = value().asDateTime().date(); + setCellText (locale()->formatDate (date, true)); +} + +void Cell::checkTextInput() +{ + // Goal of this method: determine the value of the cell + clearAllErrors(); + + d->value = Value::empty(); + + // Get the text from that cell + TQString str = d->strText; + + sheet()->doc()->parser()->parse (str, this); + + // Parsing as time acts like an autoformat: we even change d->strText + // [h]:mm:ss -> might get set by ValueParser + if (isTime() && (formatType() != Time_format7)) + d->strText = locale()->formatTime( value().asDateTime().time(), true); + + // convert first letter to uppercase ? + if (format()->sheet()->getFirstLetterUpper() && value().isString() && + (!d->strText.isEmpty())) + { + TQString str = value().asString(); + setValue( Value( str[0].upper() + str.right( str.length()-1 ) ) ); + } +} + +//used in calc, setNumber, ValueParser +void Cell::checkNumberFormat() +{ + if ( formatType() == Number_format && value().isNumber() ) + { + if ( value().asFloat() > 1e+10 ) + format()->setFormatType( Scientific_format ); + } +} + + +// ================================================================ +// Saving and loading + + +TQDomElement Cell::save( TQDomDocument& doc, + int _x_offset, int _y_offset, + bool force, bool copy, bool era ) +{ + // Save the position of this cell + TQDomElement cell = doc.createElement( "cell" ); + cell.setAttribute( "row", d->row - _y_offset ); + cell.setAttribute( "column", d->column - _x_offset ); + // + // Save the formatting information + // + TQDomElement formatElement = format()->save( doc, d->column, d->row, force, copy ); + if ( formatElement.hasChildNodes() || formatElement.attributes().length() ) // don't save empty tags + cell.appendChild( formatElement ); + + if ( doesMergeCells() ) + { + if ( extraXCells() ) + formatElement.setAttribute( "colspan", extraXCells() ); + if ( extraYCells() ) + formatElement.setAttribute( "rowspan", extraYCells() ); + } + + if ( d->hasExtra() && d->extra()->conditions ) + { + TQDomElement conditionElement = d->extra()->conditions->saveConditions( doc ); + + if ( !conditionElement.isNull() ) + cell.appendChild( conditionElement ); + } + + if ( d->hasExtra() && (d->extra()->validity != 0) ) + { + TQDomElement validity = doc.createElement("validity"); + + TQDomElement param=doc.createElement("param"); + param.setAttribute("cond",(int)d->extra()->validity->m_cond); + param.setAttribute("action",(int)d->extra()->validity->m_action); + param.setAttribute("allow",(int)d->extra()->validity->m_restriction); + param.setAttribute("valmin",d->extra()->validity->valMin); + param.setAttribute("valmax",d->extra()->validity->valMax); + param.setAttribute("displaymessage",d->extra()->validity->displayMessage); + param.setAttribute("displayvalidationinformation",d->extra()->validity->displayValidationInformation); + param.setAttribute("allowemptycell",d->extra()->validity->allowEmptyCell); + if ( !d->extra()->validity->listValidity.isEmpty() ) + param.setAttribute( "listvalidity", d->extra()->validity->listValidity.join( ";" ) ); + validity.appendChild(param); + TQDomElement title = doc.createElement( "title" ); + title.appendChild( doc.createTextNode( d->extra()->validity->title ) ); + validity.appendChild( title ); + TQDomElement message = doc.createElement( "message" ); + message.appendChild( doc.createCDATASection( d->extra()->validity->message ) ); + validity.appendChild( message ); + + TQDomElement inputTitle = doc.createElement( "inputtitle" ); + inputTitle.appendChild( doc.createTextNode( d->extra()->validity->titleInfo ) ); + validity.appendChild( inputTitle ); + + TQDomElement inputMessage = doc.createElement( "inputmessage" ); + inputMessage.appendChild( doc.createTextNode( d->extra()->validity->messageInfo ) ); + validity.appendChild( inputMessage ); + + + + TQString tmp; + if ( d->extra()->validity->timeMin.isValid() ) + { + TQDomElement timeMin = doc.createElement( "timemin" ); + tmp=d->extra()->validity->timeMin.toString(); + timeMin.appendChild( doc.createTextNode( tmp ) ); + validity.appendChild( timeMin ); + } + if ( d->extra()->validity->timeMax.isValid() ) + { + TQDomElement timeMax = doc.createElement( "timemax" ); + tmp=d->extra()->validity->timeMax.toString(); + timeMax.appendChild( doc.createTextNode( tmp ) ); + validity.appendChild( timeMax ); + } + + if ( d->extra()->validity->dateMin.isValid() ) + { + TQDomElement dateMin = doc.createElement( "datemin" ); + TQString tmp("%1/%2/%3"); + tmp = tmp.arg(d->extra()->validity->dateMin.year()).arg(d->extra()->validity->dateMin.month()).arg(d->extra()->validity->dateMin.day()); + dateMin.appendChild( doc.createTextNode( tmp ) ); + validity.appendChild( dateMin ); + } + if ( d->extra()->validity->dateMax.isValid() ) + { + TQDomElement dateMax = doc.createElement( "datemax" ); + TQString tmp("%1/%2/%3"); + tmp = tmp.arg(d->extra()->validity->dateMax.year()).arg(d->extra()->validity->dateMax.month()).arg(d->extra()->validity->dateMax.day()); + dateMax.appendChild( doc.createTextNode( tmp ) ); + validity.appendChild( dateMax ); + } + + cell.appendChild( validity ); + } + + if ( format()->comment() ) + { + TQDomElement comment = doc.createElement( "comment" ); + comment.appendChild( doc.createCDATASection( *format()->comment() ) ); + cell.appendChild( comment ); + } + + // + // Save the text + // + if ( !d->strText.isEmpty() ) + { + // Formulas need to be encoded to ensure that they + // are position independent. + if ( isFormula() ) + { + TQDomElement text = doc.createElement( "text" ); + // if we are cutting to the clipboard, relative references need to be encoded absolutely + text.appendChild( doc.createTextNode( encodeFormula( era ) ) ); + cell.appendChild( text ); + + /* we still want to save the results of the formula */ + TQDomElement formulaResult = doc.createElement( "result" ); + saveCellResult( doc, formulaResult, d->strOutText ); + cell.appendChild( formulaResult ); + + } + else if ( !link().isEmpty() ) + { + // KSpread pre 1.4 saves link as rich text, marked with first char ' + // Have to be saved in some CDATA section because of too many special charatcers. + TQDomElement text = doc.createElement( "text" ); + TQString qml = "!<a href=\"" + link() + "\">" + d->strText + "</a>"; + text.appendChild( doc.createCDATASection( qml ) ); + cell.appendChild( text ); + } + else + { + // Save the cell contents (in a locale-independent way) + TQDomElement text = doc.createElement( "text" ); + saveCellResult( doc, text, d->strText ); + cell.appendChild( text ); + } + } + if ( cell.hasChildNodes() || cell.attributes().length() > 2 ) // don't save empty tags + // (the >2 is due to "row" and "column" attributes) + return cell; + else + return TQDomElement(); +} + +bool Cell::saveCellResult( TQDomDocument& doc, TQDomElement& result, + TQString str ) +{ + TQString dataType = "Other"; // fallback + + if ( value().isNumber() ) + { + if ( isDate() ) + { + // serial number of date + TQDate dd = value().asDateTime().date(); + dataType = "Date"; + str = "%1/%2/%3"; + str = str.arg(dd.year()).arg(dd.month()).arg(dd.day()); + } + else if( isTime() ) + { + // serial number of time + dataType = "Time"; + str = value().asDateTime().time().toString(); + } + else + { + // real number + dataType = "Num"; + if (value().isInteger()) + str = TQString::number(value().asInteger()); + else + str = TQString::number(value().asFloat(), 'g', DBL_DIG); + } + } + + if ( value().isBoolean() ) + { + dataType = "Bool"; + str = value().asBoolean() ? "true" : "false"; + } + + if ( value().isString() ) + { + dataType = "Str"; + str = value().asString(); + } + + result.setAttribute( "dataType", dataType ); + if ( !d->strOutText.isEmpty() ) + result.setAttribute( "outStr", d->strOutText ); + result.appendChild( doc.createTextNode( str ) ); + + return true; /* really isn't much of a way for this function to fail */ +} + +void Cell::saveOasisAnnotation( KoXmlWriter &xmlwriter ) +{ + if ( format()->comment() ) + { + //<office:annotation draw:style-name="gr1" draw:text-style-name="P1" svg:width="2.899cm" svg:height="2.691cm" svg:x="2.858cm" svg:y="0.001cm" draw:caption-point-x="-2.858cm" draw:caption-point-y="-0.001cm"> + xmlwriter.startElement( "office:annotation" ); + TQStringList text = TQStringList::split( "\n", *format()->comment() ); + for ( TQStringList::Iterator it = text.begin(); it != text.end(); ++it ) { + xmlwriter.startElement( "text:p" ); + xmlwriter.addTextNode( *it ); + xmlwriter.endElement(); + } + xmlwriter.endElement(); + } +} + + + +TQString Cell::saveOasisCellStyle( KoGenStyle ¤tCellStyle, KoGenStyles &mainStyles ) +{ + if ( d->hasExtra() && d->extra()->conditions ) + { + // this has to be an automatic style + currentCellStyle = KoGenStyle( Doc::STYLE_CELL_AUTO, "table-cell" ); + d->extra()->conditions->saveOasisConditions( currentCellStyle ); + } + return format()->saveOasisCellStyle( currentCellStyle, mainStyles ); +} + + +bool Cell::saveOasis( KoXmlWriter& xmlwriter, KoGenStyles &mainStyles, + int row, int column, int &repeated, + GenValidationStyles &valStyle ) +{ + if ( !isPartOfMerged() ) + xmlwriter.startElement( "table:table-cell" ); + else + xmlwriter.startElement( "table:covered-table-cell" ); +#if 0 + //add font style + TQFont font; + Value const value( cell->value() ); + if ( !cell->isDefault() ) + { + font = cell->format()->textFont( i, row ); + m_styles.addFont( font ); + + if ( cell->format()->hasProperty( Format::PComment ) ) + hasComment = true; + } +#endif + // NOTE save the value before the style as long as the Formatter does not work correctly + if ( link().isEmpty() ) + saveOasisValue (xmlwriter); + + KoGenStyle currentCellStyle; // the type determined in saveOasisCellStyle + saveOasisCellStyle( currentCellStyle,mainStyles ); + // skip 'table:style-name' attribute for the default style + if ( !currentCellStyle.isDefaultStyle() ) + xmlwriter.addAttribute( "table:style-name", mainStyles.styles()[currentCellStyle] ); + + // group empty cells with the same style + if ( isEmpty() && !format()->hasProperty( Format::PComment ) && + !isPartOfMerged() && !doesMergeCells() ) + { + bool refCellIsDefault = isDefault(); + int j = column + 1; + Cell *nextCell = format()->sheet()->getNextCellRight( column, row ); + while ( nextCell ) + { + // if + // the next cell is not the adjacent one + // or + // the next cell is not empty + if ( nextCell->column() != j || !nextCell->isEmpty() ) + { + if ( refCellIsDefault ) + { + // if the origin cell was a default cell, + // we count the default cells + repeated = nextCell->column() - j + 1; + } + // otherwise we just stop here to process the adjacent + // cell in the next iteration of the outer loop + // (in Sheet::saveOasisCells) + break; + } + + KoGenStyle nextCellStyle; // the type is determined in saveOasisCellStyle + nextCell->saveOasisCellStyle( nextCellStyle,mainStyles ); + + if ( nextCell->isPartOfMerged() || nextCell->doesMergeCells() || + nextCell->format()->hasProperty( Format::PComment ) || + !(nextCellStyle == currentCellStyle) ) + { + break; + } + ++repeated; + // get the next cell and set the index to the adjacent cell + nextCell = format()->sheet()->getNextCellRight( j++, row ); + } + kdDebug() << "Cell::saveOasis: empty cell in column " << column << " " + << "repeated " << repeated << " time(s)" << endl; + + if ( repeated > 1 ) + xmlwriter.addAttribute( "table:number-columns-repeated", TQString::number( repeated ) ); + } + + + if (d->hasExtra() && d->extra()->validity) + { + GenValidationStyle styleVal(d->extra()->validity); + xmlwriter.addAttribute( "table:validation-name", valStyle.lookup( styleVal ) ); + } + if ( isFormula() ) + { + //kdDebug() << "Formula found" << endl; + TQString formula( convertFormulaToOasisFormat( text() ) ); + xmlwriter.addAttribute( "table:formula", formula ); + } + else if ( !link().isEmpty() ) + { + //kdDebug()<<"Link found \n"; + xmlwriter.startElement( "text:p" ); + xmlwriter.startElement( "text:a" ); + //Reference cell is started by "#" + if ( localReferenceAnchor( link() ) ) + xmlwriter.addAttribute( " xlink:href", ( "#"+link() ) ); + else + xmlwriter.addAttribute( " xlink:href", link() ); + xmlwriter.addTextNode( text() ); + xmlwriter.endElement(); + xmlwriter.endElement(); + } + + if ( doesMergeCells() ) + { + int colSpan = mergedXCells() + 1; + int rowSpan = mergedYCells() + 1; + + if ( colSpan > 1 ) + xmlwriter.addAttribute( "table:number-columns-spanned", TQString::number( colSpan ) ); + + if ( rowSpan > 1 ) + xmlwriter.addAttribute( "table:number-rows-spanned", TQString::number( rowSpan ) ); + } + + if ( !isEmpty() && link().isEmpty() ) + { + xmlwriter.startElement( "text:p" ); + xmlwriter.addTextNode( strOutText().utf8() ); + xmlwriter.endElement(); + } + + saveOasisAnnotation( xmlwriter ); + + xmlwriter.endElement(); + return true; +} + +void Cell::saveOasisValue (KoXmlWriter &xmlWriter) +{ + switch (value().format()) + { + case Value::fmt_None: break; //NOTHING HERE + case Value::fmt_Boolean: + { + xmlWriter.addAttribute( "office:value-type", "boolean" ); + xmlWriter.addAttribute( "office:boolean-value", ( value().asBoolean() ? + "true" : "false" ) ); + break; + } + case Value::fmt_Number: + { + xmlWriter.addAttribute( "office:value-type", "float" ); + if (value().isInteger()) + xmlWriter.addAttribute( "office:value", TQString::number( value().asInteger() ) ); + else + xmlWriter.addAttribute( "office:value", TQString::number( value().asFloat(), 'g', DBL_DIG ) ); + break; + } + case Value::fmt_Percent: + { + xmlWriter.addAttribute( "office:value-type", "percentage" ); + xmlWriter.addAttribute( "office:value", + TQString::number( value().asFloat() ) ); + break; + } + case Value::fmt_Money: + { + xmlWriter.addAttribute( "office:value-type", "currency" ); + Format::Currency currency; + if (format()->currencyInfo(currency)) + xmlWriter.addAttribute( "office:currency", Currency::getCurrencyCode(currency.type) ); + xmlWriter.addAttribute( "office:value", + TQString::number( value().asFloat() ) ); + break; + } + case Value::fmt_DateTime: break; //NOTHING HERE + case Value::fmt_Date: + { + xmlWriter.addAttribute( "office:value-type", "date" ); + xmlWriter.addAttribute( "office:date-value", + value().asDate().toString( Qt::ISODate ) ); + break; + } + case Value::fmt_Time: + { + xmlWriter.addAttribute( "office:value-type", "time" ); + xmlWriter.addAttribute( "office:time-value", + value().asTime().toString( "PThhHmmMssS" ) ); + break; + } + case Value::fmt_String: + { + xmlWriter.addAttribute( "office:value-type", "string" ); + xmlWriter.addAttribute( "office:string-value", value().asString() ); + break; + } + }; +} + +TQString Cell::convertFormulaToOasisFormat( const TQString & formula ) const +{ + TQString s; + TQRegExp exp("(\\$?)([a-zA-Z]+)(\\$?)([0-9]+)"); + int n = exp.search( formula, 0 ); + kdDebug() << "Exp: " << formula << ", n: " << n << ", Length: " << formula.length() + << ", Matched length: " << exp.matchedLength() << endl; + + bool inQuote1 = false; + bool inQuote2 = false; + int i = 0; + int l = (int) formula.length(); + if ( l <= 0 ) + return formula; + while ( i < l ) + { + if ( ( n != -1 ) && ( n < i ) ) + { + n = exp.search( formula, i ); + kdDebug() << "Exp: " << formula.right( l - i ) << ", n: " << n << endl; + } + if ( formula[i] == '"' ) + { + inQuote1 = !inQuote1; + s += formula[i]; + ++i; + continue; + } + if ( formula[i] == '\'' ) + { + // named area + inQuote2 = !inQuote2; + ++i; + continue; + } + if ( inQuote1 || inQuote2 ) + { + s += formula[i]; + ++i; + continue; + } + if ( ( formula[i] == '=' ) && ( formula[i + 1] == '=' ) ) + { + s += '='; + ++i;++i; + continue; + } + if ( formula[i] == '!' ) + { + insertBracket( s ); + s += '.'; + ++i; + continue; + } + if ( formula[i] == ',' ) + { + s += '.'; + ++i; + continue; + } + if ( n == i ) + { + int ml = exp.matchedLength(); + if ( formula[ i + ml ] == '!' ) + { + kdDebug() << "No cell ref but sheet name" << endl; + s += formula[i]; + ++i; + continue; + } + if ( ( i > 0 ) && ( formula[i - 1] != '!' ) ) + s += "[."; + for ( int j = 0; j < ml; ++j ) + { + s += formula[i]; + ++i; + } + s += ']'; + continue; + } + + s += formula[i]; + ++i; + } + + return s; +} + +void Cell::loadOasisConditional( TQDomElement * style ) +{ + if ( style )//safe + { + TQDomElement e; + forEachElement( e, style->toElement() ) + { + if ( e.localName() == "map" && e.namespaceURI() == KoXmlNS::style ) + { + if (d->hasExtra()) + delete d->extra()->conditions; + d->extra()->conditions = new Conditions( this ); + d->extra()->conditions->loadOasisConditions( e ); + d->extra()->conditions->checkMatches(); + // break here + // Conditions::loadOasisConditions finishes the iteration + break; + } + } + } +} + +bool Cell::loadOasis( const TQDomElement& element , KoOasisLoadingContext& oasisContext , Style* style ) +{ + kdDebug() << "*** Loading cell properties ***** at " << column() << "," << row () << endl; + + if ( element.hasAttributeNS( KoXmlNS::table, "style-name" ) ) + { + kdDebug()<<" table:style-name: "<<element.attributeNS( KoXmlNS::table, "style-name", TQString() )<<endl; + oasisContext.fillStyleStack( element, KoXmlNS::table, "styleName", "table-cell" ); + + TQString str = element.attributeNS( KoXmlNS::table, "style-name", TQString() ); + const TQDomElement* cellStyle = oasisContext.oasisStyles().findStyle( str, "table-cell" ); + + if ( cellStyle ) + loadOasisConditional( const_cast<TQDomElement *>( cellStyle ) ); + } + + if (style) + { + format()->setStyle( style ); + } + + //Search and load each paragraph of text. Each paragraph is separated by a line break. + loadOasisCellText( element ); + + // + // formula + // + bool isFormula = false; + if ( element.hasAttributeNS( KoXmlNS::table, "formula" ) ) + { + kdDebug()<<" formula :"<<element.attributeNS( KoXmlNS::table, "formula", TQString() )<<endl; + isFormula = true; + TQString oasisFormula( element.attributeNS( KoXmlNS::table, "formula", TQString() ) ); + //necessary to remove it to load formula from oocalc2.0 (use namespace) + if (oasisFormula.startsWith( "oooc:" ) ) + oasisFormula= oasisFormula.mid( 5 ); + else if (oasisFormula.startsWith( "kspr:" ) ) + oasisFormula= oasisFormula.mid( 5 ); + // TODO Stefan: merge this into Oasis::decodeFormula + checkForNamedAreas( oasisFormula ); + oasisFormula = Oasis::decodeFormula( oasisFormula, locale() ); + setCellText( oasisFormula ); + } + else if ( d->strText.at(0) == '=' ) //prepend ' to the text to avoid = to be painted + d->strText.prepend('\''); + + // + // validation + // + if ( element.hasAttributeNS( KoXmlNS::table, "validation-name" ) ) + { + kdDebug()<<" validation-name: "<<element.attributeNS( KoXmlNS::table, "validation-name", TQString() )<<endl; + loadOasisValidation( element.attributeNS( KoXmlNS::table, "validation-name", TQString() ) ); + } + + // + // value type + // + if( element.hasAttributeNS( KoXmlNS::office, "value-type" ) ) + { + TQString valuetype = element.attributeNS( KoXmlNS::office, "value-type", TQString() ); + kdDebug()<<" value-type: " << valuetype << endl; + if( valuetype == "boolean" ) + { + TQString val = element.attributeNS( KoXmlNS::office, "boolean-value", TQString() ).lower(); + if( ( val == "true" ) || ( val == "false" ) ) + { + bool value = val == "true"; + setCellValue( value ); + } + } + + // integer and floating-point value + else if( valuetype == "float" ) + { + bool ok = false; + double value = element.attributeNS( KoXmlNS::office, "value", TQString() ).toDouble( &ok ); + if( ok ) + setCellValue( value ); + + if ( !isFormula && d->strText.isEmpty()) + { + TQString str = locale()->formatNumber( value, 15 ); + setCellText( str ); + } + } + + // currency value + else if( valuetype == "currency" ) + { + bool ok = false; + double value = element.attributeNS( KoXmlNS::office, "value", TQString() ).toDouble( &ok ); + if( ok ) + { + setCellValue( value, Money_format ); + + if (element.hasAttributeNS( KoXmlNS::office, "currency" ) ) + { + Currency currency(element.attributeNS( KoXmlNS::office, "currency", TQString() ) ); + format()->setCurrency( currency.getIndex(), currency.getDisplayCode() ); + } + } + } + else if( valuetype == "percentage" ) + { + bool ok = false; + double v = element.attributeNS( KoXmlNS::office, "value", TQString() ).toDouble( &ok ); + if( ok ) + { + Value value; + value.setValue (v); + value.setFormat (Value::fmt_Percent); + setCellValue( value ); + + if ( !isFormula && d->strText.isEmpty()) + { + TQString str = locale()->formatNumber( v, 15 ); + setCellText( str ); + } + + format()->setFormatType (Percentage_format); + } + } + else if ( valuetype == "date" ) + { + TQString value = element.attributeNS( KoXmlNS::office, "value", TQString() ); + if ( value.isEmpty() ) + value = element.attributeNS( KoXmlNS::office, "date-value", TQString() ); + kdDebug() << "Type: date, value: " << value << endl; + + // "1980-10-15" + int year = 0, month = 0, day = 0; + bool ok = false; + + int p1 = value.find( '-' ); + if ( p1 > 0 ) + year = value.left( p1 ).toInt( &ok ); + + kdDebug() << "year: " << value.left( p1 ) << endl; + + int p2 = value.find( '-', ++p1 ); + + if ( ok ) + month = value.mid( p1, p2 - p1 ).toInt( &ok ); + + kdDebug() << "month: " << value.mid( p1, p2 - p1 ) << endl; + + if ( ok ) + day = value.right( value.length() - p2 - 1 ).toInt( &ok ); + + kdDebug() << "day: " << value.right( value.length() - p2 ) << endl; + + if ( ok ) + { + setCellValue( TQDate( year, month, day ) ); + if ( style ) + format()->setFormatType (style->formatType()); + kdDebug() << "Set TQDate: " << year << " - " << month << " - " << day << endl; + } + + } + else if ( valuetype == "time" ) + { + TQString value = element.attributeNS( KoXmlNS::office, "value", TQString() ); + if ( value.isEmpty() ) + value = element.attributeNS( KoXmlNS::office, "time-value", TQString() ); + kdDebug() << "Type: time: " << value << endl; + // "PT15H10M12S" + int hours = 0, minutes = 0, seconds = 0; + int l = value.length(); + TQString num; + bool ok = false; + for ( int i = 0; i < l; ++i ) + { + if ( value[i].isNumber() ) + { + num += value[i]; + continue; + } + else if ( value[i] == 'H' ) + hours = num.toInt( &ok ); + else if ( value[i] == 'M' ) + minutes = num.toInt( &ok ); + else if ( value[i] == 'S' ) + seconds = num.toInt( &ok ); + else + continue; + + kdDebug() << "Num: " << num << endl; + + num = ""; + if ( !ok ) + break; + } + kdDebug() << "Hours: " << hours << ", " << minutes << ", " << seconds << endl; + + if ( ok ) + { + // Value kval( timeToNum( hours, minutes, seconds ) ); + // cell->setValue( kval ); + setCellValue( TQTime( hours % 24, minutes, seconds ) ); + if ( style ) + format()->setFormatType (style->formatType()); + } + } + else if( valuetype == "string" ) + { + TQString value = element.attributeNS( KoXmlNS::office, "value", TQString() ); + if ( value.isEmpty() && element.hasAttributeNS( KoXmlNS::office, "string-value" )) + { + //if there is not string-value entry don't overwrite value stored into <text:p> + value = element.attributeNS( KoXmlNS::office, "string-value", TQString() ); + setCellValue( value ); + } + format()->setFormatType (Text_format); + } + else + kdDebug()<<" type of value found : "<<valuetype<<endl; + } + + // + // merged cells ? + // + int colSpan = 1; + int rowSpan = 1; + if ( element.hasAttributeNS( KoXmlNS::table, "number-columns-spanned" ) ) + { + bool ok = false; + int span = element.attributeNS( KoXmlNS::table, "number-columns-spanned", TQString() ).toInt( &ok ); + if( ok ) colSpan = span; + } + if ( element.hasAttributeNS( KoXmlNS::table, "number-rows-spanned" ) ) + { + bool ok = false; + int span = element.attributeNS( KoXmlNS::table, "number-rows-spanned", TQString() ).toInt( &ok ); + if( ok ) rowSpan = span; + } + if ( colSpan > 1 || rowSpan > 1 ) + mergeCells( d->column, d->row, colSpan - 1, rowSpan - 1 ); + + // + // cell comment/annotation + // + TQDomElement annotationElement = KoDom::namedItemNS( element, KoXmlNS::office, "annotation" ); + if ( !annotationElement.isNull() ) + { + TQString comment; + TQDomNode node = annotationElement.firstChild(); + while( !node.isNull() ) + { + TQDomElement commentElement = node.toElement(); + if( !commentElement.isNull() ) + if( commentElement.localName() == "p" && commentElement.namespaceURI() == KoXmlNS::text ) + { + if( !comment.isEmpty() ) comment.append( '\n' ); + comment.append( commentElement.text() ); + } + + node = node.nextSibling(); + } + + if( !comment.isEmpty() ) + format()->setComment( comment ); + } + + TQDomElement frame = KoDom::namedItemNS( element, KoXmlNS::draw, "frame" ); + if ( !frame.isNull() ) + loadOasisObjects( frame, oasisContext ); + + if (isFormula) + setCalcDirtyFlag (); // formulas must be recalculated + + return true; +} + +void Cell::loadOasisCellText( const TQDomElement& parent ) +{ + //Search and load each paragraph of text. Each paragraph is separated by a line break + TQDomElement textParagraphElement; + TQString cellText; + + bool multipleTextParagraphsFound=false; + + forEachElement( textParagraphElement , parent ) + { + if ( textParagraphElement.localName()=="p" && + textParagraphElement.namespaceURI()== KoXmlNS::text ) + { + // our text, could contain formating for value or result of formul + if (cellText.isEmpty()) + cellText = textParagraphElement.text(); + else + { + cellText += "\n"+textParagraphElement.text(); + multipleTextParagraphsFound=true; + } + + TQDomElement textA = KoDom::namedItemNS( textParagraphElement, KoXmlNS::text, "a" ); + if( !textA.isNull() ) + { + if ( textA.hasAttributeNS( KoXmlNS::xlink, "href" ) ) + { + TQString link = textA.attributeNS( KoXmlNS::xlink, "href", TQString() ); + cellText = textA.text(); + setCellText( cellText ); + setValue( cellText ); + if ( link[0]=='#' ) + link=link.remove( 0, 1 ); + setLink( link ); + } + } + } + } + + if (!cellText.isNull()) + { + setCellText( cellText ); + setValue( cellText ); + } + + //Enable word wrapping if multiple lines of text have been found. + if ( multipleTextParagraphsFound ) + { + format()->setMultiRow(true); + } +} + +void Cell::loadOasisObjects( const TQDomElement &parent, KoOasisLoadingContext& oasisContext ) +{ + for( TQDomElement e = parent; !e.isNull(); e = e.nextSibling().toElement() ) + { + if ( e.localName() == "frame" && e.namespaceURI() == KoXmlNS::draw ) + { + EmbeddedObject *obj = 0; + TQDomNode object = KoDom::namedItemNS( e, KoXmlNS::draw, "object" ); + if ( !object.isNull() ) + { + if ( !object.toElement().attributeNS( KoXmlNS::draw, "notify-on-update-of-ranges", TQString()).isNull() ) + obj = new EmbeddedChart( sheet()->doc(), sheet() ); + else + obj = new EmbeddedKOfficeObject( sheet()->doc(), sheet() ); + } + else + { + TQDomNode image = KoDom::namedItemNS( e, KoXmlNS::draw, "image" ); + if ( !image.isNull() ) + obj = new EmbeddedPictureObject( sheet(), sheet()->doc()->pictureCollection() ); + else + kdDebug() << "Object type wasn't loaded!" << endl; + } + + if ( obj ) + { + obj->loadOasis( e, oasisContext ); + sheet()->doc()->insertObject( obj ); + + TQString ref = e.attributeNS( KoXmlNS::table, "end-cell-address", TQString() ); + if ( ref.isNull() ) + continue; + + ref = Oasis::decodeFormula( ref ); + Point point( ref ); + if ( !point.isValid() ) + continue; + + KoRect geometry = obj->geometry(); + geometry.setLeft( geometry.left() + sheet()->columnPos( d->column, 0 ) ); + geometry.setTop( geometry.top() + sheet()->rowPos( d->row, 0 ) ); + + TQString str = e.attributeNS( KoXmlNS::table, "end-x", TQString() ); + if ( !str.isNull() ) + { + uint end_x = (uint) KoUnit::parseValue( str ); + geometry.setRight( sheet()->columnPos( point.column(), 0) + end_x ); + } + + str = e.attributeNS( KoXmlNS::table, "end-y", TQString() ); + if ( !str.isNull() ) + { + uint end_y = (uint) KoUnit::parseValue( str ); + geometry.setBottom( sheet()->rowPos( point.row(), 0) + end_y ); + } + + obj->setGeometry( geometry ); + } + } + } +} + +void Cell::loadOasisValidation( const TQString& validationName ) +{ + TQDomElement element = sheet()->doc()->loadingInfo()->validation( validationName); + if (d->hasExtra()) + delete d->extra()->validity; + d->extra()->validity = new Validity; + if ( element.hasAttributeNS( KoXmlNS::table, "condition" ) ) + { + TQString valExpression = element.attributeNS( KoXmlNS::table, "condition", TQString() ); + kdDebug()<<" element.attribute( table:condition ) "<<valExpression<<endl; + //Condition ::= ExtendedTrueCondition | TrueFunction 'and' TrueCondition + //TrueFunction ::= cell-content-is-whole-number() | cell-content-is-decimal-number() | cell-content-is-date() | cell-content-is-time() + //ExtendedTrueCondition ::= ExtendedGetFunction | cell-content-text-length() Operator Value + //TrueCondition ::= GetFunction | cell-content() Operator Value + //GetFunction ::= cell-content-is-between(Value, Value) | cell-content-is-not-between(Value, Value) + //ExtendedGetFunction ::= cell-content-text-length-is-between(Value, Value) | cell-content-text-length-is-not-between(Value, Value) + //Operator ::= '<' | '>' | '<=' | '>=' | '=' | '!=' + //Value ::= NumberValue | String | Formula + //A Formula is a formula without an equals (=) sign at the beginning. See section 8.1.3 for more information. + //A String comprises one or more characters surrounded by quotation marks. + //A NumberValue is a whole or decimal number. It must not contain comma separators for numbers of 1000 or greater. + + //ExtendedTrueCondition + if ( valExpression.contains( "cell-content-text-length()" ) ) + { + //"cell-content-text-length()>45" + valExpression = valExpression.remove("oooc:cell-content-text-length()" ); + kdDebug()<<" valExpression = :"<<valExpression<<endl; + d->extra()->validity->m_restriction = Restriction::TextLength; + + loadOasisValidationCondition( valExpression ); + } + else if ( valExpression.contains( "cell-content-is-text()" ) ) + { + d->extra()->validity->m_restriction = Restriction::Text; + } + //cell-content-text-length-is-between(Value, Value) | cell-content-text-length-is-not-between(Value, Value) | cell-content-is-in-list( StringList ) + else if ( valExpression.contains( "cell-content-text-length-is-between" ) ) + { + d->extra()->validity->m_restriction = Restriction::TextLength; + d->extra()->validity->m_cond = Conditional::Between; + valExpression = valExpression.remove( "oooc:cell-content-text-length-is-between(" ); + kdDebug()<<" valExpression :"<<valExpression<<endl; + valExpression = valExpression.remove( ")" ); + TQStringList listVal = TQStringList::split( ",", valExpression ); + loadOasisValidationValue( listVal ); + } + else if ( valExpression.contains( "cell-content-text-length-is-not-between" ) ) + { + d->extra()->validity->m_restriction = Restriction::TextLength; + d->extra()->validity->m_cond = Conditional::Different; + valExpression = valExpression.remove( "oooc:cell-content-text-length-is-not-between(" ); + kdDebug()<<" valExpression :"<<valExpression<<endl; + valExpression = valExpression.remove( ")" ); + kdDebug()<<" valExpression :"<<valExpression<<endl; + TQStringList listVal = TQStringList::split( ",", valExpression ); + loadOasisValidationValue( listVal ); + } + else if ( valExpression.contains( "cell-content-is-in-list(" ) ) + { + d->extra()->validity->m_restriction = Restriction::List; + valExpression = valExpression.remove( "oooc:cell-content-is-in-list(" ); + kdDebug()<<" valExpression :"<<valExpression<<endl; + valExpression = valExpression.remove( ")" ); + d->extra()->validity->listValidity = TQStringList::split( ";", valExpression ); + + } + //TrueFunction ::= cell-content-is-whole-number() | cell-content-is-decimal-number() | cell-content-is-date() | cell-content-is-time() + else + { + if (valExpression.contains( "cell-content-is-whole-number()" ) ) + { + d->extra()->validity->m_restriction = Restriction::Number; + valExpression = valExpression.remove( "oooc:cell-content-is-whole-number() and " ); + } + else if (valExpression.contains( "cell-content-is-decimal-number()" ) ) + { + d->extra()->validity->m_restriction = Restriction::Integer; + valExpression = valExpression.remove( "oooc:cell-content-is-decimal-number() and " ); + } + else if (valExpression.contains( "cell-content-is-date()" ) ) + { + d->extra()->validity->m_restriction = Restriction::Date; + valExpression = valExpression.remove( "oooc:cell-content-is-date() and " ); + } + else if (valExpression.contains( "cell-content-is-time()" ) ) + { + d->extra()->validity->m_restriction = Restriction::Time; + valExpression = valExpression.remove( "oooc:cell-content-is-time() and " ); + } + kdDebug()<<"valExpression :"<<valExpression<<endl; + + if ( valExpression.contains( "cell-content()" ) ) + { + valExpression = valExpression.remove( "cell-content()" ); + loadOasisValidationCondition( valExpression ); + } + //GetFunction ::= cell-content-is-between(Value, Value) | cell-content-is-not-between(Value, Value) + //for the moment we support just int/double value, not text/date/time :( + if ( valExpression.contains( "cell-content-is-between(" ) ) + { + valExpression = valExpression.remove( "cell-content-is-between(" ); + valExpression = valExpression.remove( ")" ); + TQStringList listVal = TQStringList::split( "," , valExpression ); + loadOasisValidationValue( listVal ); + d->extra()->validity->m_cond = Conditional::Between; + } + if ( valExpression.contains( "cell-content-is-not-between(" ) ) + { + valExpression = valExpression.remove( "cell-content-is-not-between(" ); + valExpression = valExpression.remove( ")" ); + TQStringList listVal = TQStringList::split( ",", valExpression ); + loadOasisValidationValue( listVal ); + d->extra()->validity->m_cond = Conditional::Different; + } + } + } + if ( element.hasAttributeNS( KoXmlNS::table, "allow-empty-cell" ) ) + { + kdDebug()<<" element.hasAttribute( table:allow-empty-cell ) :"<<element.hasAttributeNS( KoXmlNS::table, "allow-empty-cell" )<<endl; + d->extra()->validity->allowEmptyCell = ( ( element.attributeNS( KoXmlNS::table, "allow-empty-cell", TQString() )=="true" ) ? true : false ); + } + if ( element.hasAttributeNS( KoXmlNS::table, "base-cell-address" ) ) + { + //todo what is it ? + } + + TQDomElement help = KoDom::namedItemNS( element, KoXmlNS::table, "help-message" ); + if ( !help.isNull() ) + { + if ( help.hasAttributeNS( KoXmlNS::table, "title" ) ) + { + kdDebug()<<"help.attribute( table:title ) :"<<help.attributeNS( KoXmlNS::table, "title", TQString() )<<endl; + d->extra()->validity->titleInfo = help.attributeNS( KoXmlNS::table, "title", TQString() ); + } + if ( help.hasAttributeNS( KoXmlNS::table, "display" ) ) + { + kdDebug()<<"help.attribute( table:display ) :"<<help.attributeNS( KoXmlNS::table, "display", TQString() )<<endl; + d->extra()->validity->displayValidationInformation = ( ( help.attributeNS( KoXmlNS::table, "display", TQString() )=="true" ) ? true : false ); + } + TQDomElement attrText = KoDom::namedItemNS( help, KoXmlNS::text, "p" ); + if ( !attrText.isNull() ) + { + kdDebug()<<"help text :"<<attrText.text()<<endl; + d->extra()->validity->messageInfo = attrText.text(); + } + } + + TQDomElement error = KoDom::namedItemNS( element, KoXmlNS::table, "error-message" ); + if ( !error.isNull() ) + { + if ( error.hasAttributeNS( KoXmlNS::table, "title" ) ) + d->extra()->validity->title = error.attributeNS( KoXmlNS::table, "title", TQString() ); + if ( error.hasAttributeNS( KoXmlNS::table, "message-type" ) ) + { + TQString str = error.attributeNS( KoXmlNS::table, "message-type", TQString() ); + if ( str == "warning" ) + d->extra()->validity->m_action = Action::Warning; + else if ( str == "information" ) + d->extra()->validity->m_action = Action::Information; + else if ( str == "stop" ) + d->extra()->validity->m_action = Action::Stop; + else + kdDebug()<<"validation : message type unknown :"<<str<<endl; + } + + if ( error.hasAttributeNS( KoXmlNS::table, "display" ) ) + { + kdDebug()<<" display message :"<<error.attributeNS( KoXmlNS::table, "display", TQString() )<<endl; + d->extra()->validity->displayMessage = (error.attributeNS( KoXmlNS::table, "display", TQString() )=="true"); + } + TQDomElement attrText = KoDom::namedItemNS( error, KoXmlNS::text, "p" ); + if ( !attrText.isNull() ) + d->extra()->validity->message = attrText.text(); + } +} + + +void Cell::loadOasisValidationValue( const TQStringList &listVal ) +{ + bool ok = false; + kdDebug()<<" listVal[0] :"<<listVal[0]<<" listVal[1] :"<<listVal[1]<<endl; + + if ( d->extra()->validity->m_restriction == Restriction::Date ) + { + d->extra()->validity->dateMin = TQDate::fromString( listVal[0] ); + d->extra()->validity->dateMax = TQDate::fromString( listVal[1] ); + } + else if ( d->extra()->validity->m_restriction == Restriction::Time ) + { + d->extra()->validity->timeMin = TQTime::fromString( listVal[0] ); + d->extra()->validity->timeMax = TQTime::fromString( listVal[1] ); + } + else + { + d->extra()->validity->valMin = listVal[0].toDouble(&ok); + if ( !ok ) + { + d->extra()->validity->valMin = listVal[0].toInt(&ok); + if ( !ok ) + kdDebug()<<" Try to parse this value :"<<listVal[0]<<endl; + +#if 0 + if ( !ok ) + d->extra()->validity->valMin = listVal[0]; +#endif + } + ok=false; + d->extra()->validity->valMax = listVal[1].toDouble(&ok); + if ( !ok ) + { + d->extra()->validity->valMax = listVal[1].toInt(&ok); + if ( !ok ) + kdDebug()<<" Try to parse this value :"<<listVal[1]<<endl; + +#if 0 + if ( !ok ) + d->extra()->validity->valMax = listVal[1]; +#endif + } + } +} + +void Cell::loadOasisValidationCondition( TQString &valExpression ) +{ + TQString value; + if (valExpression.find( "<=" )==0 ) + { + value = valExpression.remove( 0,2 ); + d->extra()->validity->m_cond = Conditional::InferiorEqual; + } + else if (valExpression.find( ">=" )==0 ) + { + value = valExpression.remove( 0,2 ); + d->extra()->validity->m_cond = Conditional::SuperiorEqual; + } + else if (valExpression.find( "!=" )==0 ) + { + //add Differentto attribute + value = valExpression.remove( 0,2 ); + d->extra()->validity->m_cond = Conditional::DifferentTo; + } + else if ( valExpression.find( "<" )==0 ) + { + value = valExpression.remove( 0,1 ); + d->extra()->validity->m_cond = Conditional::Inferior; + } + else if(valExpression.find( ">" )==0 ) + { + value = valExpression.remove( 0,1 ); + d->extra()->validity->m_cond = Conditional::Superior; + } + else if (valExpression.find( "=" )==0 ) + { + value = valExpression.remove( 0,1 ); + d->extra()->validity->m_cond = Conditional::Equal; + } + else + kdDebug()<<" I don't know how to parse it :"<<valExpression<<endl; + if ( d->extra()->validity->m_restriction == Restriction::Date ) + { + d->extra()->validity->dateMin = TQDate::fromString( value ); + } + else if (d->extra()->validity->m_restriction == Restriction::Date ) + { + d->extra()->validity->timeMin = TQTime::fromString( value ); + } + else + { + bool ok = false; + d->extra()->validity->valMin = value.toDouble(&ok); + if ( !ok ) + { + d->extra()->validity->valMin = value.toInt(&ok); + if ( !ok ) + kdDebug()<<" Try to parse this value :"<<value<<endl; + +#if 0 + if ( !ok ) + d->extra()->validity->valMin = value; +#endif + } + } +} + + +bool Cell::load( const TQDomElement & cell, int _xshift, int _yshift, + Paste::Mode pm, Paste::Operation op, bool paste ) +{ + bool ok; + + // + // First of all determine in which row and column this + // cell belongs. + // + d->row = cell.attribute( "row" ).toInt( &ok ) + _yshift; + if ( !ok ) return false; + d->column = cell.attribute( "column" ).toInt( &ok ) + _xshift; + if ( !ok ) return false; + + // Validation + if ( d->row < 1 || d->row > KS_rowMax ) + { + kdDebug(36001) << "Cell::load: Value out of Range Cell:row=" << d->row << endl; + return false; + } + if ( d->column < 1 || d->column > KS_colMax ) + { + kdDebug(36001) << "Cell::load: Value out of Range Cell:column=" << d->column << endl; + return false; + } + + // + // Load formatting information. + // + TQDomElement f = cell.namedItem( "format" ).toElement(); + if ( !f.isNull() + && ( (pm == Paste::Normal) || (pm == Paste::Format) || (pm == Paste::NoBorder) ) ) + { + // send pm parameter. Didn't load Borders if pm==NoBorder + + if ( !format()->load( f, pm, paste ) ) + return false; + + if ( f.hasAttribute( "colspan" ) ) + { + int i = f.attribute("colspan").toInt( &ok ); + if ( !ok ) return false; + // Validation + if ( i < 0 || i > KS_spanMax ) + { + kdDebug(36001) << "Value out of range Cell::colspan=" << i << endl; + return false; + } + if (i || d->hasExtra()) + d->extra()->extraXCells = i; + if ( i > 0 ) + { + setFlag(Flag_Merged); + } + } + + if ( f.hasAttribute( "rowspan" ) ) + { + int i = f.attribute("rowspan").toInt( &ok ); + if ( !ok ) return false; + // Validation + if ( i < 0 || i > KS_spanMax ) + { + kdDebug(36001) << "Value out of range Cell::rowspan=" << i << endl; + return false; + } + if (i || d->hasExtra()) + d->extra()->extraYCells = i; + if ( i > 0 ) + { + setFlag(Flag_Merged); + } + } + + if ( testFlag( Flag_Merged ) ) + { + if (d->hasExtra()) + mergeCells( d->column, d->row, d->extra()->extraXCells, d->extra()->extraYCells ); + } + + } + + // + // Load the condition section of a cell. + // + TQDomElement conditionsElement = cell.namedItem( "condition" ).toElement(); + if ( !conditionsElement.isNull()) + { + if (d->hasExtra()) + delete d->extra()->conditions; + d->extra()->conditions = new Conditions( this ); + d->extra()->conditions->loadConditions( conditionsElement ); + d->extra()->conditions->checkMatches(); + } + else if ((pm == Paste::Normal) || (pm == Paste::NoBorder)) + { + //clear the conditional formatting + if (d->hasExtra()) + { + delete d->extra()->conditions; + d->extra()->conditions = 0; + } + } + + TQDomElement validity = cell.namedItem( "validity" ).toElement(); + if ( !validity.isNull()) + { + TQDomElement param = validity.namedItem( "param" ).toElement(); + if(!param.isNull()) + { + d->extra()->validity = new Validity; + if ( param.hasAttribute( "cond" ) ) + { + d->extra()->validity->m_cond = (Conditional::Type) param.attribute("cond").toInt( &ok ); + if ( !ok ) + return false; + } + if ( param.hasAttribute( "action" ) ) + { + d->extra()->validity->m_action = (Action::Type) param.attribute("action").toInt( &ok ); + if ( !ok ) + return false; + } + if ( param.hasAttribute( "allow" ) ) + { + d->extra()->validity->m_restriction = (Restriction::Type) param.attribute("allow").toInt( &ok ); + if ( !ok ) + return false; + } + if ( param.hasAttribute( "valmin" ) ) + { + d->extra()->validity->valMin = param.attribute("valmin").toDouble( &ok ); + if ( !ok ) + return false; + } + if ( param.hasAttribute( "valmax" ) ) + { + d->extra()->validity->valMax = param.attribute("valmax").toDouble( &ok ); + if ( !ok ) + return false; + } + if ( param.hasAttribute( "displaymessage" ) ) + { + d->extra()->validity->displayMessage = ( bool )param.attribute("displaymessage").toInt(); + } + if ( param.hasAttribute( "displayvalidationinformation" ) ) + { + d->extra()->validity->displayValidationInformation = ( bool )param.attribute("displayvalidationinformation").toInt(); + } + if ( param.hasAttribute( "allowemptycell" ) ) + { + d->extra()->validity->allowEmptyCell = ( bool )param.attribute("allowemptycell").toInt(); + } + if ( param.hasAttribute("listvalidity") ) + { + d->extra()->validity->listValidity=TQStringList::split(";", param.attribute("listvalidity") ); + } + } + TQDomElement inputTitle = validity.namedItem( "inputtitle" ).toElement(); + if (!inputTitle.isNull()) + { + d->extra()->validity->titleInfo = inputTitle.text(); + } + TQDomElement inputMessage = validity.namedItem( "inputmessage" ).toElement(); + if (!inputMessage.isNull()) + { + d->extra()->validity->messageInfo = inputMessage.text(); + } + + TQDomElement title = validity.namedItem( "title" ).toElement(); + if (!title.isNull()) + { + d->extra()->validity->title = title.text(); + } + TQDomElement message = validity.namedItem( "message" ).toElement(); + if (!message.isNull()) + { + d->extra()->validity->message = message.text(); + } + TQDomElement timeMin = validity.namedItem( "timemin" ).toElement(); + if ( !timeMin.isNull() ) + { + d->extra()->validity->timeMin = toTime(timeMin); + } + TQDomElement timeMax = validity.namedItem( "timemax" ).toElement(); + if ( !timeMax.isNull() ) + { + d->extra()->validity->timeMax = toTime(timeMax); + } + TQDomElement dateMin = validity.namedItem( "datemin" ).toElement(); + if ( !dateMin.isNull() ) + { + d->extra()->validity->dateMin = toDate(dateMin); + } + TQDomElement dateMax = validity.namedItem( "datemax" ).toElement(); + if ( !dateMax.isNull() ) + { + d->extra()->validity->dateMax = toDate(dateMax); + } + } + else if ((pm == Paste::Normal) || (pm == Paste::NoBorder)) + { + // clear the validity + removeValidity(); + } + + // + // Load the comment + // + TQDomElement comment = cell.namedItem( "comment" ).toElement(); + if ( !comment.isNull() && ( pm == Paste::Normal || pm == Paste::Comment || pm == Paste::NoBorder )) + { + TQString t = comment.text(); + //t = t.stripWhiteSpace(); + format()->setComment( t ); + } + + // + // The real content of the cell is loaded here. It is stored in + // the "text" tag, which contains either a text or a CDATA section. + // + // TODO: make this suck less. We set data twice, in loadCellData, and + // also here. Not good. + TQDomElement text = cell.namedItem( "text" ).toElement(); + + if ( !text.isNull() && + ( pm == Paste::Normal || pm == Paste::Text || pm == Paste::NoBorder || pm == Paste::Result ) ) + { + /* older versions mistakenly put the datatype attribute on the cell + instead of the text. Just move it over in case we're parsing + an old document */ + if ( cell.hasAttribute( "dataType" ) ) // new docs + text.setAttribute( "dataType", cell.attribute( "dataType" ) ); + + TQDomElement result = cell.namedItem( "result" ).toElement(); + TQString txt = text.text(); + if ((pm == Paste::Result) && (txt[0] == '=')) + // paste text of the element, if we want to paste result + // and the source cell contains a formula + // note that we mustn't use setCellValue after this, or else we lose + // all the formulas ... + d->strText = result.text(); + else + //otherwise copy everything + loadCellData(text, op); + + if ( !result.isNull() ) + { + TQString dataType; + TQString t = result.text(); + + if ( result.hasAttribute( "dataType" ) ) + dataType = result.attribute( "dataType" ); + if ( result.hasAttribute( "outStr" ) ) + { + d->strOutText = result.attribute( "outStr" ); + if ( !d->strOutText.isEmpty() ) + clearFlag( Flag_TextFormatDirty ); + } + + bool clear = true; + // boolean ? + if( dataType == "Bool" ) + { + if ( t == "false" ) + setValue( false ); + else if ( t == "true" ) + setValue( true ); + else + clear = false; + } + else if( dataType == "Num" ) + { + bool ok = false; + double dd = t.toDouble( &ok ); + if ( ok ) + setValue ( dd ); + else + clear = false; + } + else if( dataType == "Date" ) + { + bool ok = false; + double dd = t.toDouble( &ok ); + if ( ok ) + setValue ( dd ); + else + { + int pos = t.find( '/' ); + int year = t.mid( 0, pos ).toInt(); + int pos1 = t.find( '/', pos + 1 ); + int month = t.mid( pos + 1, ( ( pos1 - 1 ) - pos ) ).toInt(); + int day = t.right( t.length() - pos1 - 1 ).toInt(); + TQDate date( year, month, day ); + if ( date.isValid() ) + setValue( date ); + else + clear = false; + } + } + else if( dataType == "Time" ) + { + bool ok = false; + double dd = t.toDouble( &ok ); + if ( ok ) + setCellValue( dd ); + else + { + int hours = -1; + int minutes = -1; + int second = -1; + int pos, pos1; + pos = t.find( ':' ); + hours = t.mid( 0, pos ).toInt(); + pos1 = t.find( ':', pos + 1 ); + minutes = t.mid( pos + 1, ( ( pos1 - 1 ) - pos ) ).toInt(); + second = t.right( t.length() - pos1 - 1 ).toInt(); + TQTime time( hours, minutes, second ); + if ( time.isValid() ) + setValue( time ); + else + clear = false; + } + } + else + { + setValue( t ); + } + + // if ( clear ) + // clearFlag( Flag_CalcDirty ); + } + } + + return true; +} + +bool Cell::loadCellData(const TQDomElement & text, Paste::Operation op ) +{ + //TODO: use converter()->asString() to generate strText + + TQString t = text.text(); + t = t.stripWhiteSpace(); + + setFlag(Flag_LayoutDirty); + setFlag(Flag_TextFormatDirty); + + // A formula like =A1+A2 ? + if( t[0] == '=' ) + { + t = decodeFormula( t, d->column, d->row ); + setCellText (pasteOperation( t, d->strText, op )); + + setFlag(Flag_CalcDirty); + clearAllErrors(); + + if ( !makeFormula() ) + kdError(36001) << "ERROR: Syntax ERROR" << endl; + } + // rich text ? + else if (t[0] == '!' ) + { + // KSpread pre 1.4 stores hyperlink as rich text (first char is '!') + // extract the link and the correspoding text + // This is a rather dirty hack, but enough for KSpread generated XML + bool inside_tag = false; + TQString qml_text; + TQString tag; + TQString qml_link; + + for( unsigned i = 1; i < t.length(); i++ ) + { + TQChar ch = t[i]; + if( ch == '<' ) + { + if( !inside_tag ) + { + inside_tag = true; + tag = TQString(); + } + } + else if( ch == '>' ) + { + if( inside_tag ) + { + inside_tag = false; + if( tag.startsWith( "a href=\"", true ) ) + if( tag.endsWith( "\"" ) ) + qml_link = tag.mid( 8, tag.length()-9 ); + tag = TQString(); + } + } + else + { + if( !inside_tag ) + qml_text += ch; + else + tag += ch; + } + } + + if( !qml_link.isEmpty() ) + d->extra()->link = qml_link; + d->strText = qml_text; + setValue( d->strText ); + } + else + { + bool newStyleLoading = true; + TQString dataType; + + if ( text.hasAttribute( "dataType" ) ) // new docs + { + dataType = text.attribute( "dataType" ); + } + else // old docs: do the ugly solution of calling checkTextInput to parse the text + { + // ...except for date/time + if (isDate() && ( t.contains('/') == 2 )) + dataType = "Date"; + else if (isTime() && ( t.contains(':') == 2 ) ) + dataType = "Time"; + else + { + d->strText = pasteOperation( t, d->strText, op ); + checkTextInput(); + //kdDebug(36001) << "Cell::load called checkTextInput, got dataType=" << dataType << " t=" << t << endl; + newStyleLoading = false; + } + } + + if ( newStyleLoading ) + { + d->value = Value::empty(); + clearAllErrors(); + + // boolean ? + if( dataType == "Bool" ) + { + bool val = (t.lower() == "true"); + setCellValue (val); + } + + // number ? + else if( dataType == "Num" ) + { + bool ok = false; + if (t.contains('.')) + setValue ( Value( t.toDouble(&ok) ) ); // We save in non-localized format + else + setValue ( Value( t.toLong(&ok) ) ); + if ( !ok ) + { + kdWarning(36001) << "Couldn't parse '" << t << "' as number." << endl; + } + /* We will need to localize the text version of the number */ + TDELocale* locale = format()->sheet()->doc()->locale(); + + /* TDELocale::formatNumber requires the precision we want to return. + */ + int precision = t.length() - t.find('.') - 1; + + if ( formatType() == Percentage_format ) + { + if (value().isInteger()) + t = locale->formatNumber( value().asInteger() * 100 ); + else + t = locale->formatNumber( value().asFloat() * 100.0, precision ); + d->strText = pasteOperation( t, d->strText, op ); + d->strText += '%'; + } + else + { + if (value().isInteger()) + t = locale->formatLong(value().asInteger()); + else + t = locale->formatNumber(value().asFloat(), precision); + d->strText = pasteOperation( t, d->strText, op ); + } + } + + // date ? + else if( dataType == "Date" ) + { + int pos = t.find('/'); + int year = t.mid(0,pos).toInt(); + int pos1 = t.find('/',pos+1); + int month = t.mid(pos+1,((pos1-1)-pos)).toInt(); + int day = t.right(t.length()-pos1-1).toInt(); + setValue( TQDate(year,month,day) ); + if ( value().asDate().isValid() ) // Should always be the case for new docs + d->strText = locale()->formatDate( value().asDate(), true ); + else // This happens with old docs, when format is set wrongly to date + { + d->strText = pasteOperation( t, d->strText, op ); + checkTextInput(); + } + } + + // time ? + else if( dataType == "Time" ) + { + int hours = -1; + int minutes = -1; + int second = -1; + int pos, pos1; + pos = t.find(':'); + hours = t.mid(0,pos).toInt(); + pos1 = t.find(':',pos+1); + minutes = t.mid(pos+1,((pos1-1)-pos)).toInt(); + second = t.right(t.length()-pos1-1).toInt(); + setValue( TQTime(hours,minutes,second) ); + if ( value().asTime().isValid() ) // Should always be the case for new docs + d->strText = locale()->formatTime( value().asTime(), true ); + else // This happens with old docs, when format is set wrongly to time + { + d->strText = pasteOperation( t, d->strText, op ); + checkTextInput(); + } + } + + else + { + // Set the cell's text + d->strText = pasteOperation( t, d->strText, op ); + setValue( d->strText ); + } + } + } + + if ( text.hasAttribute( "outStr" ) ) // very new docs + { + d->strOutText = text.attribute( "outStr" ); + if ( !d->strOutText.isEmpty() ) + clearFlag( Flag_TextFormatDirty ); + } + + if ( !format()->sheet()->isLoading() ) + setCellText( d->strText ); + + if ( d->hasExtra() && d->extra()->conditions ) + d->extra()->conditions->checkMatches(); + + return true; +} + +TQTime Cell::toTime(const TQDomElement &element) +{ + //TODO: can't we use tryParseTime (after modification) instead? + TQString t = element.text(); + t = t.stripWhiteSpace(); + int hours = -1; + int minutes = -1; + int second = -1; + int pos, pos1; + pos = t.find(':'); + hours = t.mid(0,pos).toInt(); + pos1 = t.find(':',pos+1); + minutes = t.mid(pos+1,((pos1-1)-pos)).toInt(); + second = t.right(t.length()-pos1-1).toInt(); + setValue( Value( TQTime(hours,minutes,second)) ); + return value().asTime(); +} + +TQDate Cell::toDate(const TQDomElement &element) +{ + TQString t = element.text(); + int pos; + int pos1; + int year = -1; + int month = -1; + int day = -1; + pos = t.find('/'); + year = t.mid(0,pos).toInt(); + pos1 = t.find('/',pos+1); + month = t.mid(pos+1,((pos1-1)-pos)).toInt(); + day = t.right(t.length()-pos1-1).toInt(); + setValue( Value( TQDate(year,month,day) ) ); + return value().asDate(); +} + +TQString Cell::pasteOperation( const TQString &new_text, const TQString &old_text, Paste::Operation op ) +{ + if ( op == Paste::OverWrite ) + return new_text; + + TQString tmp_op; + TQString tmp; + TQString old; + + if( !new_text.isEmpty() && new_text[0] == '=' ) + { + tmp = new_text.right( new_text.length() - 1 ); + } + else + { + tmp = new_text; + } + + if ( old_text.isEmpty() && + ( op == Paste::Add || op == Paste::Mul || op == Paste::Sub || op == Paste::Div ) ) + { + old = "=0"; + } + + if( !old_text.isEmpty() && old_text[0] == '=' ) + { + old = old_text.right( old_text.length() - 1 ); + } + else + { + old = old_text; + } + + bool b1, b2; + tmp.toDouble( &b1 ); + old.toDouble( &b2 ); + if (b1 && !b2 && old.length() == 0) + { + old = "0"; + b2 = true; + } + + if( b1 && b2 ) + { + switch( op ) + { + case Paste::Add: + tmp_op = TQString::number(old.toDouble()+tmp.toDouble()); + break; + case Paste::Mul : + tmp_op = TQString::number(old.toDouble()*tmp.toDouble()); + break; + case Paste::Sub: + tmp_op = TQString::number(old.toDouble()-tmp.toDouble()); + break; + case Paste::Div: + tmp_op = TQString::number(old.toDouble()/tmp.toDouble()); + break; + default: + Q_ASSERT( 0 ); + } + + setFlag(Flag_LayoutDirty); + clearAllErrors(); + + return tmp_op; + } + else if ( ( new_text[0] == '=' && old_text[0] == '=' ) || + ( b1 && old_text[0] == '=' ) || ( new_text[0] == '=' && b2 ) ) + { + switch( op ) + { + case Paste::Add : + tmp_op="=("+old+")+"+"("+tmp+")"; + break; + case Paste::Mul : + tmp_op="=("+old+")*"+"("+tmp+")"; + break; + case Paste::Sub: + tmp_op="=("+old+")-"+"("+tmp+")"; + break; + case Paste::Div: + tmp_op="=("+old+")/"+"("+tmp+")"; + break; + default : + Q_ASSERT( 0 ); + } + + tmp_op = decodeFormula( tmp_op, d->column, d->row ); + setFlag(Flag_LayoutDirty); + clearAllErrors(); + + return tmp_op; + } + + tmp = decodeFormula( new_text, d->column, d->row ); + setFlag(Flag_LayoutDirty); + clearAllErrors(); + + return tmp; +} + +TQString Cell::testAnchor( int x, int y ) const +{ + if( link().isEmpty() ) + return TQString(); + + const Doc* doc = format()->sheet()->doc(); + int x1 = doc->zoomItX( d->textX ); + int y1 = doc->zoomItX( d->textY - d->textHeight ); + int x2 = doc->zoomItX( d->textX + d->textWidth ); + int y2 = doc->zoomItX( d->textY ); + + if( x > x1 ) if( x < x2 ) + if( y > y1 ) if( y < y2 ) + return link(); + + return TQString(); +} + +void Cell::sheetDies() +{ + // Avoid unobscuring the cells in the destructor. + if (d->hasExtra()) + { + d->extra()->extraXCells = 0; + d->extra()->extraYCells = 0; + d->extra()->mergedXCells = 0; + d->extra()->mergedYCells = 0; + } + + //d->nextCell = 0; + //d->previousCell = 0; +} + +Cell::~Cell() +{ + if ( d->nextCell ) + d->nextCell->setPreviousCell( d->previousCell ); + if ( d->previousCell ) + d->previousCell->setNextCell( d->nextCell ); + + if (d->hasExtra()) + { + delete d->extra()->validity; + } + + // Unobscure cells. + int extraXCells = d->hasExtra() ? d->extra()->extraXCells : 0; + int extraYCells = d->hasExtra() ? d->extra()->extraYCells : 0; + for( int x = 0; x <= extraXCells; ++x ) + for( int y = (x == 0) ? 1 : 0; // avoid looking at (+0,+0) + y <= extraYCells; ++y ) + { + Cell* cell = format()->sheet()->cellAt( d->column + x, d->row + y ); + if ( cell ) + cell->unobscure(this); + } + + d->value = Value::empty(); + + if (!isDefault()) + valueChanged (); //our value has been changed (is now null), but only if we aren't default + + delete d->format; + delete d; +} + +bool Cell::operator > ( const Cell & cell ) const +{ + if ( value().isNumber() ) // ### what about bools ? + { + if ( cell.value().isNumber() ) + return value().asFloat() > cell.value().asFloat(); + else + return false; // numbers are always < than texts + } + else if(isDate()) + { + if( cell.isDate() ) + return value().asDate() > cell.value().asDate(); + else if (cell.value().isNumber()) + return true; + else + return false; //date are always < than texts and time + } + else if(isTime()) + { + if( cell.isTime() ) + return value().asTime() > cell.value().asTime(); + else if( cell.isDate()) + return true; //time are always > than date + else if( cell.value().isNumber()) + return true; + else + return false; //time are always < than texts + } + else + { + if ( Map::respectCase ) + return value().asString().compare(cell.value().asString()) > 0; + else + return ( value().asString() ).lower().compare(cell.value().asString().lower()) > 0; + } +} + +bool Cell::operator < ( const Cell & cell ) const +{ + if ( value().isNumber() ) + { + if ( cell.value().isNumber() ) + return value().asFloat() < cell.value().asFloat(); + else + return true; // numbers are always < than texts + } + else if(isDate()) + { + if( cell.isDate() ) + return value().asDateTime().date() < cell.value().asDateTime().date(); + else if( cell.value().isNumber()) + return false; + else + return true; //date are always < than texts and time + } + else if(isTime()) + { + if( cell.isTime() ) + return value().asDateTime().time() < cell.value().asDateTime().time(); + else if(cell.isDate()) + return false; //time are always > than date + else if( cell.value().isNumber()) + return false; + else + return true; //time are always < than texts + } + else + { + if ( Map::respectCase ) + return value().asString().compare(cell.value().asString()) < 0; + else + return ( value().asString() ).lower().compare(cell.value().asString().lower()) < 0; + } +} + +bool Cell::operator==( const Cell& other ) const +{ + if ( d->strText != other.d->strText ) + return false; + if ( d->value != other.d->value ) + return false; + if ( *d->format != *other.d->format ) + return false; + if ( d->hasExtra() ) + { + if ( !other.d->hasExtra() ) + return false; + if ( d->extra()->link != other.d->extra()->link ) + return false; + if ( d->extra()->mergedXCells != other.d->extra()->mergedXCells ) + return false; + if ( d->extra()->mergedYCells != other.d->extra()->mergedYCells ) + return false; + if ( d->extra()->conditions ) + { + if ( !other.d->extra()->conditions ) + return false; + if ( *d->extra()->conditions != *other.d->extra()->conditions ) + return false; + } + if ( d->extra()->validity ) + { + if ( !other.d->extra()->validity ) + return false; + if ( *d->extra()->validity != *other.d->extra()->validity ) + return false; + } + } + return true; +} + +TQRect Cell::cellRect() +{ + Q_ASSERT(!isDefault()); + return TQRect(TQPoint(d->column, d->row), TQPoint(d->column, d->row)); +} + +TQValueList<Conditional> Cell::conditionList() const +{ + if ( !d->hasExtra() || !d->extra()->conditions ) + { + TQValueList<Conditional> emptyList; + return emptyList; + } + + return d->extra()->conditions->conditionList(); +} + +void Cell::setConditionList( const TQValueList<Conditional> & newList ) +{ + if (d->hasExtra()) + delete d->extra()->conditions; + d->extra()->conditions = new Conditions( this ); + d->extra()->conditions->setConditionList( newList ); + d->extra()->conditions->checkMatches(); +} + +bool Cell::hasError() const +{ + return ( testFlag(Flag_ParseError) || + testFlag(Flag_CircularCalculation) || + testFlag(Flag_DependancyError)); +} + +void Cell::clearAllErrors() +{ + clearFlag( Flag_ParseError ); + clearFlag( Flag_CircularCalculation ); + clearFlag( Flag_DependancyError ); +} + +bool Cell::calcDirtyFlag() +{ + return isFormula() ? testFlag( Flag_CalcDirty ) : false; +} + +bool Cell::layoutDirtyFlag() const +{ + return testFlag( Flag_LayoutDirty ); +} + +void Cell::clearDisplayDirtyFlag() +{ + clearFlag( Flag_DisplayDirty ); +} + +void Cell::setDisplayDirtyFlag() +{ + setFlag( Flag_DisplayDirty ); +} + +bool Cell::doesMergeCells() const +{ + return testFlag( Flag_Merged ); +} + +void Cell::clearFlag( CellFlags flag ) +{ + d->flags &= ~(TQ_UINT32)flag; +} + +void Cell::setFlag( CellFlags flag ) +{ + d->flags |= (TQ_UINT32)flag; +} + +bool Cell::testFlag( CellFlags flag ) const +{ + return ( d->flags & (TQ_UINT32)flag ); +} + + +void Cell::checkForNamedAreas( TQString & formula ) const +{ + KSPLoadingInfo* loadinginfo = sheet()->doc()->loadingInfo(); + if(! loadinginfo) { + kdDebug() << "Cell::checkForNamedAreas loadinginfo is NULL" << endl; + return; + } + + int l = formula.length(); + int i = 0; + TQString word; + int start = 0; + while ( i < l ) + { + if ( formula[i].isLetterOrNumber() ) + { + word += formula[i]; + ++i; + continue; + } + if ( !word.isEmpty() ) + { + if ( loadinginfo->findWordInAreaList(word) ) + { + formula = formula.replace( start, word.length(), "'" + word + "'" ); + l = formula.length(); + ++i; + kdDebug() << "Formula: " << formula << ", L: " << l << ", i: " << i + 1 <<endl; + } + } + + ++i; + word = ""; + start = i; + } + if ( !word.isEmpty() ) + { + if ( loadinginfo->findWordInAreaList(word) ) + { + formula = formula.replace( start, word.length(), "'" + word + "'" ); + l = formula.length(); + ++i; + kdDebug() << "Formula: " << formula << ", L: " << l << ", i: " << i + 1 <<endl; + } + } +} |