1 | /* This file is part of the KDE project |
2 | Copyright 2010 Marijn Kruisselbrink <mkruisselbrink@kde.org> |
3 | Copyright 2006-2007 Stefan Nikolaus <stefan.nikolaus@kdemail.net> |
4 | Copyright 2005 Raphael Langerhorst <raphael.langerhorst@kdemail.net> |
5 | Copyright 2004-2005 Tomas Mecir <mecirt@gmail.com> |
6 | Copyright 2004-2006 Inge Wallin <inge@lysator.liu.se> |
7 | Copyright 1999-2002,2004,2005 Laurent Montel <montel@kde.org> |
8 | Copyright 2002-2005 Ariya Hidayat <ariya@kde.org> |
9 | Copyright 2001-2003 Philipp Mueller <philipp.mueller@gmx.de> |
10 | Copyright 2002-2003 Norbert Andres <nandres@web.de> |
11 | Copyright 2003 Reinhart Geiser <geiseri@kde.org> |
12 | Copyright 2003-2005 Meni Livne <livne@kde.org> |
13 | Copyright 2003 Peter Simonsson <psn@linux.se> |
14 | Copyright 1999-2002 David Faure <faure@kde.org> |
15 | Copyright 2000-2002 Werner Trobin <trobin@kde.org> |
16 | Copyright 1999,2002 Harri Porten <porten@kde.org> |
17 | Copyright 2002 John Dailey <dailey@vt.edu> |
18 | Copyright 1998-2000 Torben Weis <weis@kde.org> |
19 | Copyright 2000 Bernd Wuebben <wuebben@kde.org> |
20 | Copyright 2000 Simon Hausmann <hausmann@kde.org |
21 | Copyright 1999 Stephan Kulow <coolo@kde.org> |
22 | Copyright 1999 Michael Reiher <michael.reiher@gmx.de> |
23 | Copyright 1999 Boris Wedl <boris.wedl@kfunigraz.ac.at> |
24 | Copyright 1998-1999 Reginald Stadlbauer <reggie@kde.org> |
25 | |
26 | This library is free software; you can redistribute it and/or |
27 | modify it under the terms of the GNU Library General Public |
28 | License as published by the Free Software Foundation; either |
29 | version 2 of the License, or (at your option) any later version. |
30 | |
31 | This library is distributed in the hope that it will be useful, |
32 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
33 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
34 | Library General Public License for more details. |
35 | |
36 | You should have received a copy of the GNU Library General Public License |
37 | along with this library; see the file COPYING.LIB. If not, write to |
38 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
39 | Boston, MA 02110-1301, USA. |
40 | */ |
41 | |
42 | // Local |
43 | #include "Cell.h" |
44 | |
45 | #include <stdlib.h> |
46 | #include <ctype.h> |
47 | #include <float.h> |
48 | #include <math.h> |
49 | |
50 | #include "CalculationSettings.h" |
51 | #include "CellStorage.h" |
52 | #include "Condition.h" |
53 | #include "Formula.h" |
54 | #include "GenValidationStyle.h" |
55 | #include "Global.h" |
56 | #include "Localization.h" |
57 | #include "LoadingInfo.h" |
58 | #include "Map.h" |
59 | #include "NamedAreaManager.h" |
60 | #include "OdfLoadingContext.h" |
61 | #include "OdfSavingContext.h" |
62 | #include "RowColumnFormat.h" |
63 | #include "RowFormatStorage.h" |
64 | #include "ShapeApplicationData.h" |
65 | #include "Sheet.h" |
66 | #include "Style.h" |
67 | #include "StyleManager.h" |
68 | #include "Util.h" |
69 | #include "Value.h" |
70 | #include "Validity.h" |
71 | #include "ValueConverter.h" |
72 | #include "ValueFormatter.h" |
73 | #include "ValueParser.h" |
74 | #include "StyleStorage.h" |
75 | |
76 | #include <KoShape.h> |
77 | #include <KoShapeLoadingContext.h> |
78 | #include <KoShapeRegistry.h> |
79 | #include <KoStyleStack.h> |
80 | #include <KoXmlNS.h> |
81 | #include <KoXmlReader.h> |
82 | #include <KoOdfStylesReader.h> |
83 | #include <KoXmlWriter.h> |
84 | |
85 | #include <KoTextLoader.h> |
86 | #include <KoStyleManager.h> |
87 | #include <KoTextSharedLoadingData.h> |
88 | #include <KoTextDocument.h> |
89 | #include <KoTextWriter.h> |
90 | #include <KoParagraphStyle.h> |
91 | |
92 | #include <kdebug.h> |
93 | |
94 | #include <QTimer> |
95 | #include <QTextDocument> |
96 | #include <QTextCursor> |
97 | |
98 | using namespace Calligra::Sheets; |
99 | |
100 | class Cell::Private : public QSharedData |
101 | { |
102 | public: |
103 | Private() : sheet(0), column(0), row(0) {} |
104 | |
105 | Sheet* sheet; |
106 | uint column : 17; // KS_colMax |
107 | uint row : 21; // KS_rowMax |
108 | }; |
109 | |
110 | |
111 | Cell::Cell() |
112 | : d(0) |
113 | { |
114 | } |
115 | |
116 | Cell::Cell(const Sheet* sheet, int col, int row) |
117 | : d(new Private) |
118 | { |
119 | Q_ASSERT(sheet != 0); |
120 | Q_ASSERT_X(1 <= col && col <= KS_colMax, __FUNCTION__, QString("%1 out of bounds" ).arg(col).toLocal8Bit()); |
121 | Q_ASSERT_X(1 <= row && row <= KS_rowMax, __FUNCTION__, QString("%1 out of bounds" ).arg(row).toLocal8Bit()); |
122 | d->sheet = const_cast<Sheet*>(sheet); |
123 | d->column = col; |
124 | d->row = row; |
125 | } |
126 | |
127 | Cell::Cell(const Sheet* sheet, const QPoint& pos) |
128 | : d(new Private) |
129 | { |
130 | Q_ASSERT(sheet != 0); |
131 | Q_ASSERT_X(1 <= pos.x() && pos.x() <= KS_colMax, __FUNCTION__, QString("%1 out of bounds" ).arg(pos.x()).toLocal8Bit()); |
132 | Q_ASSERT_X(1 <= pos.y() && pos.y() <= KS_rowMax, __FUNCTION__, QString("%1 out of bounds" ).arg(pos.y()).toLocal8Bit()); |
133 | d->sheet = const_cast<Sheet*>(sheet); |
134 | d->column = pos.x(); |
135 | d->row = pos.y(); |
136 | } |
137 | |
138 | Cell::Cell(const Cell& other) |
139 | : d(other.d) |
140 | { |
141 | } |
142 | |
143 | Cell::~Cell() |
144 | { |
145 | } |
146 | |
147 | // Return the sheet that this cell belongs to. |
148 | Sheet* Cell::sheet() const |
149 | { |
150 | Q_ASSERT(!isNull()); |
151 | return d->sheet; |
152 | } |
153 | |
154 | KLocale* Cell::locale() const |
155 | { |
156 | return sheet()->map()->calculationSettings()->locale(); |
157 | } |
158 | |
159 | // Return true if this is the default cell. |
160 | bool Cell::isDefault() const |
161 | { |
162 | // check each stored attribute |
163 | if (!value().isEmpty()) |
164 | return false; |
165 | if (formula() != Formula::empty()) |
166 | return false; |
167 | if (!link().isEmpty()) |
168 | return false; |
169 | if (doesMergeCells() == true) |
170 | return false; |
171 | if (!style().isDefault()) |
172 | return false; |
173 | if (!comment().isEmpty()) |
174 | return false; |
175 | if (!conditions().isEmpty()) |
176 | return false; |
177 | if (!validity().isEmpty()) |
178 | return false; |
179 | return true; |
180 | } |
181 | |
182 | // Return true if this is the default cell (apart from maybe a custom style). |
183 | bool Cell::hasDefaultContent() const |
184 | { |
185 | // check each stored attribute |
186 | if (value() != Value()) |
187 | return false; |
188 | if (formula() != Formula::empty()) |
189 | return false; |
190 | if (!link().isEmpty()) |
191 | return false; |
192 | if (doesMergeCells() == true) |
193 | return false; |
194 | if (!comment().isEmpty()) |
195 | return false; |
196 | if (!conditions().isEmpty()) |
197 | return false; |
198 | if (!validity().isEmpty()) |
199 | return false; |
200 | return true; |
201 | } |
202 | |
203 | bool Cell::isEmpty() const |
204 | { |
205 | // empty = no value or formula |
206 | if (value() != Value()) |
207 | return false; |
208 | if (formula() != Formula()) |
209 | return false; |
210 | return true; |
211 | } |
212 | |
213 | bool Cell::isNull() const |
214 | { |
215 | return (!d); |
216 | } |
217 | |
218 | // Return true if this cell is a formula. |
219 | // |
220 | bool Cell::isFormula() const |
221 | { |
222 | return !formula().expression().isEmpty(); |
223 | } |
224 | |
225 | // Return the column number of this cell. |
226 | // |
227 | int Cell::column() const |
228 | { |
229 | // Make sure this isn't called for the null cell. This assert |
230 | // can save you (could have saved me!) the hassle of some very |
231 | // obscure bugs. |
232 | Q_ASSERT(!isNull()); |
233 | Q_ASSERT(1 <= d->column); //&& d->column <= KS_colMax ); |
234 | return d->column; |
235 | } |
236 | |
237 | // Return the row number of this cell. |
238 | int Cell::row() const |
239 | { |
240 | // Make sure this isn't called for the null cell. This assert |
241 | // can save you (could have saved me!) the hassle of some very |
242 | // obscure bugs. |
243 | Q_ASSERT(!isNull()); |
244 | Q_ASSERT(1 <= d->row); //&& d->row <= KS_rowMax ); |
245 | return d->row; |
246 | } |
247 | |
248 | // Return the name of this cell, i.e. the string that the user would |
249 | // use to reference it. Example: A1, BZ16 |
250 | // |
251 | QString Cell::name() const |
252 | { |
253 | return name(column(), row()); |
254 | } |
255 | |
256 | // Return the name of any cell given by (col, row). |
257 | // |
258 | // static |
259 | QString Cell::name(int col, int row) |
260 | { |
261 | return columnName(col) + QString::number(row); |
262 | } |
263 | |
264 | // Return the name of this cell, including the sheet name. |
265 | // Example: sheet1!A5 |
266 | // |
267 | QString Cell::fullName() const |
268 | { |
269 | return fullName(sheet(), column(), row()); |
270 | } |
271 | |
272 | // Return the full name of any cell given a sheet and (col, row). |
273 | // |
274 | // static |
275 | QString Cell::fullName(const Sheet* s, int col, int row) |
276 | { |
277 | return s->sheetName() + '!' + name(col, row); |
278 | } |
279 | |
280 | // Return the symbolic name of the column of this cell. Examples: A, BB. |
281 | // |
282 | QString Cell::columnName() const |
283 | { |
284 | return columnName(column()); |
285 | } |
286 | |
287 | // Return the symbolic name of any column. |
288 | // |
289 | // static |
290 | QString Cell::columnName(uint column) |
291 | { |
292 | if (column < 1) //|| column > KS_colMax) |
293 | return QString("@@@" ); |
294 | |
295 | QString str; |
296 | unsigned digits = 1; |
297 | unsigned offset = 0; |
298 | |
299 | --column; |
300 | |
301 | for (unsigned limit = 26; column >= limit + offset; limit *= 26, ++digits) |
302 | offset += limit; |
303 | |
304 | for (unsigned col = column - offset; digits; --digits, col /= 26) |
305 | str.prepend(QChar('A' + (col % 26))); |
306 | |
307 | return str; |
308 | } |
309 | |
310 | QString Cell::() const |
311 | { |
312 | return sheet()->cellStorage()->comment(d->column, d->row); |
313 | } |
314 | |
315 | void Cell::(const QString& ) |
316 | { |
317 | sheet()->cellStorage()->setComment(Region(cellPosition()), comment); |
318 | } |
319 | |
320 | Conditions Cell::conditions() const |
321 | { |
322 | return sheet()->cellStorage()->conditions(d->column, d->row); |
323 | } |
324 | |
325 | void Cell::setConditions(const Conditions& conditions) |
326 | { |
327 | sheet()->cellStorage()->setConditions(Region(cellPosition()), conditions); |
328 | } |
329 | |
330 | Database Cell::database() const |
331 | { |
332 | return sheet()->cellStorage()->database(d->column, d->row); |
333 | } |
334 | |
335 | Formula Cell::formula() const |
336 | { |
337 | return sheet()->cellStorage()->formula(d->column, d->row); |
338 | } |
339 | |
340 | void Cell::setFormula(const Formula& formula) |
341 | { |
342 | sheet()->cellStorage()->setFormula(column(), row(), formula); |
343 | } |
344 | |
345 | Style Cell::style() const |
346 | { |
347 | return sheet()->cellStorage()->style(d->column, d->row); |
348 | } |
349 | |
350 | Style Cell::effectiveStyle() const |
351 | { |
352 | Style style = sheet()->cellStorage()->style(d->column, d->row); |
353 | // use conditional formatting attributes |
354 | const Style conditionalStyle = conditions().testConditions(*this); |
355 | if (!conditionalStyle.isEmpty()) { |
356 | style.merge(conditionalStyle); |
357 | } |
358 | return style; |
359 | } |
360 | |
361 | void Cell::setStyle(const Style& style) |
362 | { |
363 | sheet()->cellStorage()->setStyle(Region(cellPosition()), style); |
364 | sheet()->cellStorage()->styleStorage()->contains(cellPosition()); |
365 | } |
366 | |
367 | Validity Cell::validity() const |
368 | { |
369 | return sheet()->cellStorage()->validity(d->column, d->row); |
370 | } |
371 | |
372 | void Cell::setValidity(Validity validity) |
373 | { |
374 | sheet()->cellStorage()->setValidity(Region(cellPosition()), validity); |
375 | } |
376 | |
377 | |
378 | |
379 | |
380 | |
381 | // Return the user input of this cell. This could, for instance, be a |
382 | // formula. |
383 | // |
384 | QString Cell::userInput() const |
385 | { |
386 | const Formula formula = this->formula(); |
387 | if (!formula.expression().isEmpty()) |
388 | return formula.expression(); |
389 | return sheet()->cellStorage()->userInput(d->column, d->row); |
390 | } |
391 | |
392 | void Cell::setUserInput(const QString& string) |
393 | { |
394 | QString old = userInput(); |
395 | |
396 | if (!string.isEmpty() && string[0] == '=') { |
397 | // set the formula |
398 | Formula formula(sheet(), *this); |
399 | formula.setExpression(string); |
400 | setFormula(formula); |
401 | // remove an existing user input (the non-formula one) |
402 | sheet()->cellStorage()->setUserInput(d->column, d->row, QString()); |
403 | } else { |
404 | // remove an existing formula |
405 | setFormula(Formula::empty()); |
406 | // set the value |
407 | sheet()->cellStorage()->setUserInput(d->column, d->row, string); |
408 | } |
409 | |
410 | if (old != string) { |
411 | // remove any existing richtext |
412 | setRichText(QSharedPointer<QTextDocument>()); |
413 | } |
414 | } |
415 | |
416 | void Cell::setRawUserInput(const QString& string) |
417 | { |
418 | if (!string.isEmpty() && string[0] == '=') { |
419 | // set the formula |
420 | Formula formula(sheet(), *this); |
421 | formula.setExpression(string); |
422 | setFormula(formula); |
423 | } else { |
424 | // set the value |
425 | sheet()->cellStorage()->setUserInput(d->column, d->row, string); |
426 | } |
427 | } |
428 | |
429 | |
430 | // Return the out text, i.e. the text that is visible in the cells |
431 | // square when shown. This could, for instance, be the calculated |
432 | // result of a formula. |
433 | // |
434 | QString Cell::displayText(const Style& s, Value *v, bool *showFormula) const |
435 | { |
436 | if (isNull()) |
437 | return QString(); |
438 | |
439 | QString string; |
440 | const Style style = s.isEmpty() ? effectiveStyle() : s; |
441 | // Display a formula if warranted. If not, display the value instead; |
442 | // this is the most common case. |
443 | if ( isFormula() && !(sheet()->isProtected() && style.hideFormula()) && |
444 | ( (showFormula && *showFormula) || (!showFormula && sheet()->getShowFormula()) ) ) |
445 | { |
446 | string = userInput(); |
447 | if (showFormula) |
448 | *showFormula = true; |
449 | } else if (!isEmpty()) { |
450 | Value theValue = sheet()->map()->formatter()->formatText(value(), style.formatType(), style.precision(), |
451 | style.floatFormat(), style.prefix(), |
452 | style.postfix(), style.currency().symbol(), |
453 | style.customFormat(), style.thousandsSep()); |
454 | if (v) *v = theValue; |
455 | string = theValue.asString(); |
456 | if (showFormula) |
457 | *showFormula = false; |
458 | } |
459 | return string; |
460 | } |
461 | |
462 | |
463 | // Return the value of this cell. |
464 | // |
465 | const Value Cell::value() const |
466 | { |
467 | return sheet()->cellStorage()->value(d->column, d->row); |
468 | } |
469 | |
470 | |
471 | // Set the value of this cell. |
472 | // |
473 | void Cell::setValue(const Value& value) |
474 | { |
475 | sheet()->cellStorage()->setValue(d->column, d->row, value); |
476 | } |
477 | |
478 | |
479 | QSharedPointer<QTextDocument> Cell::richText() const |
480 | { |
481 | return sheet()->cellStorage()->richText(d->column, d->row); |
482 | } |
483 | |
484 | void Cell::setRichText(QSharedPointer<QTextDocument> text) |
485 | { |
486 | sheet()->cellStorage()->setRichText(d->column, d->row, text); |
487 | } |
488 | |
489 | // FIXME: Continue commenting and cleaning here (ingwa) |
490 | |
491 | |
492 | void Cell::copyFormat(const Cell& cell) |
493 | { |
494 | Q_ASSERT(!isNull()); // trouble ahead... |
495 | Q_ASSERT(!cell.isNull()); |
496 | Value value = this->value(); |
497 | value.setFormat(cell.value().format()); |
498 | sheet()->cellStorage()->setValue(d->column, d->row, value); |
499 | if (!style().isDefault() || !cell.style().isDefault()) |
500 | setStyle(cell.style()); |
501 | if (!conditions().isEmpty() || !cell.conditions().isEmpty()) |
502 | setConditions(cell.conditions()); |
503 | } |
504 | |
505 | void Cell::copyAll(const Cell& cell) |
506 | { |
507 | Q_ASSERT(!isNull()); // trouble ahead... |
508 | Q_ASSERT(!cell.isNull()); |
509 | copyFormat(cell); |
510 | copyContent(cell); |
511 | if (!comment().isEmpty() || !cell.comment().isEmpty()) |
512 | setComment(cell.comment()); |
513 | if (!validity().isEmpty() || !cell.validity().isEmpty()) |
514 | setValidity(cell.validity()); |
515 | } |
516 | |
517 | void Cell::copyContent(const Cell& cell) |
518 | { |
519 | Q_ASSERT(!isNull()); // trouble ahead... |
520 | Q_ASSERT(!cell.isNull()); |
521 | if (cell.isFormula()) { |
522 | // change all the references, e.g. from A1 to A3 if copying |
523 | // from e.g. B2 to B4 |
524 | Formula formula(sheet(), *this); |
525 | formula.setExpression(decodeFormula(cell.encodeFormula())); |
526 | setFormula(formula); |
527 | } else { |
528 | // copy the user input |
529 | sheet()->cellStorage()->setUserInput(d->column, d->row, cell.userInput()); |
530 | } |
531 | // copy the value in both cases |
532 | sheet()->cellStorage()->setValue(d->column, d->row, cell.value()); |
533 | } |
534 | |
535 | bool Cell::needsPrinting() const |
536 | { |
537 | if (!userInput().trimmed().isEmpty()) |
538 | return true; |
539 | if (!comment().trimmed().isEmpty()) |
540 | return true; |
541 | |
542 | const Style style = effectiveStyle(); |
543 | |
544 | // Cell borders? |
545 | if (style.hasAttribute(Style::TopPen) || |
546 | style.hasAttribute(Style::LeftPen) || |
547 | style.hasAttribute(Style::RightPen) || |
548 | style.hasAttribute(Style::BottomPen) || |
549 | style.hasAttribute(Style::FallDiagonalPen) || |
550 | style.hasAttribute(Style::GoUpDiagonalPen)) |
551 | return true; |
552 | |
553 | // Background color or brush? |
554 | if (style.hasAttribute(Style::BackgroundBrush)) { |
555 | QBrush brush = style.backgroundBrush(); |
556 | |
557 | // Only brushes that are visible (ie. they have a brush style |
558 | // and are not white) need to be drawn |
559 | if ((brush.style() != Qt::NoBrush) && |
560 | (brush.color() != Qt::white || !brush.texture().isNull())) |
561 | return true; |
562 | } |
563 | |
564 | if (style.hasAttribute(Style::BackgroundColor)) { |
565 | kDebug(36004) << "needsPrinting: Has background color" ; |
566 | QColor backgroundColor = style.backgroundColor(); |
567 | |
568 | // We don't need to print anything, if the background is white opaque or fully transparent. |
569 | if (!(backgroundColor == Qt::white || backgroundColor.alpha() == 0)) |
570 | return true; |
571 | } |
572 | |
573 | return false; |
574 | } |
575 | |
576 | |
577 | QString Cell::encodeFormula(bool fixedReferences) const |
578 | { |
579 | if (!isFormula()) |
580 | return QString(); |
581 | |
582 | QString result('='); |
583 | const Tokens tokens = formula().tokens(); |
584 | for (int i = 0; i < tokens.count(); ++i) { |
585 | const Token token = tokens[i]; |
586 | switch (token.type()) { |
587 | case Token::Cell: |
588 | case Token::Range: { |
589 | if (sheet()->map()->namedAreaManager()->contains(token.text())) { |
590 | result.append(token.text()); // simply keep the area name |
591 | break; |
592 | } |
593 | const Region region(token.text(), sheet()->map()); |
594 | // Actually, a contiguous region, but the fixation is needed |
595 | Region::ConstIterator end = region.constEnd(); |
596 | for (Region::ConstIterator it = region.constBegin(); it != end; ++it) { |
597 | if (!(*it)->isValid()) |
598 | continue; |
599 | if ((*it)->type() == Region::Element::Point) { |
600 | if ((*it)->sheet()) |
601 | result.append((*it)->sheet()->sheetName() + '!'); |
602 | const QPoint pos = (*it)->rect().topLeft(); |
603 | if ((*it)->isColumnFixed()) |
604 | result.append(QString("$%1" ).arg(pos.x())); |
605 | else if (fixedReferences) |
606 | result.append(QChar(0xA7) + QString("%1" ).arg(pos.x())); |
607 | else |
608 | result.append(QString("#%1" ).arg(pos.x() - (int)d->column)); |
609 | if ((*it)->isRowFixed()) |
610 | result.append(QString("$%1#" ).arg(pos.y())); |
611 | else if (fixedReferences) |
612 | result.append(QChar(0xA7) + QString("%1#" ).arg(pos.y())); |
613 | else |
614 | result.append(QString("#%1#" ).arg(pos.y() - (int)d->row)); |
615 | } else { // ((*it)->type() == Region::Range) |
616 | if ((*it)->sheet()) |
617 | result.append((*it)->sheet()->sheetName() + '!'); |
618 | QPoint pos = (*it)->rect().topLeft(); |
619 | if ((*it)->isLeftFixed()) |
620 | result.append(QString("$%1" ).arg(pos.x())); |
621 | else if (fixedReferences) |
622 | result.append(QChar(0xA7) + QString("%1" ).arg(pos.x())); |
623 | else |
624 | result.append(QString("#%1" ).arg(pos.x() - (int)d->column)); |
625 | if ((*it)->isTopFixed()) |
626 | result.append(QString("$%1#" ).arg(pos.y())); |
627 | else if (fixedReferences) |
628 | result.append(QChar(0xA7) + QString("%1#" ).arg(pos.y())); |
629 | else |
630 | result.append(QString("#%1#" ).arg(pos.y() - (int)d->row)); |
631 | result.append(':'); |
632 | pos = (*it)->rect().bottomRight(); |
633 | if ((*it)->isRightFixed()) |
634 | result.append(QString("$%1" ).arg(pos.x())); |
635 | else if (fixedReferences) |
636 | result.append(QChar(0xA7) + QString("%1" ).arg(pos.x())); |
637 | else |
638 | result.append(QString("#%1" ).arg(pos.x() - (int)d->column)); |
639 | if ((*it)->isBottomFixed()) |
640 | result.append(QString("$%1#" ).arg(pos.y())); |
641 | else if (fixedReferences) |
642 | result.append(QChar(0xA7) + QString("%1#" ).arg(pos.y())); |
643 | else |
644 | result.append(QString("#%1#" ).arg(pos.y() - (int)d->row)); |
645 | } |
646 | } |
647 | break; |
648 | } |
649 | default: { |
650 | result.append(token.text()); |
651 | break; |
652 | } |
653 | } |
654 | } |
655 | //kDebug() << result; |
656 | return result; |
657 | } |
658 | |
659 | QString Cell::decodeFormula(const QString &_text) const |
660 | { |
661 | QString erg; |
662 | unsigned int pos = 0; |
663 | const unsigned int length = _text.length(); |
664 | |
665 | if (_text.isEmpty()) |
666 | return QString(); |
667 | |
668 | while (pos < length) { |
669 | if (_text[pos] == '"') { |
670 | erg += _text[pos++]; |
671 | while (pos < length && _text[pos] != '"') { |
672 | erg += _text[pos++]; |
673 | // Allow escaped double quotes (\") |
674 | if (pos < length && _text[pos] == '\\' && _text[pos+1] == '"') { |
675 | erg += _text[pos++]; |
676 | erg += _text[pos++]; |
677 | } |
678 | } |
679 | if (pos < length) |
680 | erg += _text[pos++]; |
681 | } else if (_text[pos] == '#' || _text[pos] == '$' || _text[pos] == QChar(0xA7)) { |
682 | bool abs1 = false; |
683 | bool abs2 = false; |
684 | bool era1 = false; // if 1st is relative but encoded absolutely |
685 | bool era2 = false; |
686 | |
687 | QChar _t = _text[pos++]; |
688 | if (_t == '$') |
689 | abs1 = true; |
690 | else if (_t == QChar(0xA7)) |
691 | era1 = true; |
692 | |
693 | int col = 0; |
694 | unsigned int oldPos = pos; |
695 | while (pos < length && (_text[pos].isDigit() || _text[pos] == '-')) ++pos; |
696 | if (pos != oldPos) |
697 | col = _text.mid(oldPos, pos - oldPos).toInt(); |
698 | if (!abs1 && !era1) |
699 | col += d->column; |
700 | // Skip '#' or '$' |
701 | |
702 | _t = _text[pos++]; |
703 | if (_t == '$') |
704 | abs2 = true; |
705 | else if (_t == QChar(0xA7)) |
706 | era2 = true; |
707 | |
708 | int row = 0; |
709 | oldPos = pos; |
710 | while (pos < length && (_text[pos].isDigit() || _text[pos] == '-')) ++pos; |
711 | if (pos != oldPos) |
712 | row = _text.mid(oldPos, pos - oldPos).toInt(); |
713 | if (!abs2 && !era2) |
714 | row += d->row; |
715 | // Skip '#' or '$' |
716 | ++pos; |
717 | if (row < 1 || col < 1 || row > KS_rowMax || col > KS_colMax) { |
718 | kDebug(36003) << "Cell::decodeFormula: row or column out of range (col:" << col << " | row:" << row << ')'; |
719 | erg += Value::errorREF().errorMessage(); |
720 | } else { |
721 | if (abs1) |
722 | erg += '$'; |
723 | erg += Cell::columnName(col); //Get column text |
724 | |
725 | if (abs2) |
726 | erg += '$'; |
727 | erg += QString::number(row); |
728 | } |
729 | } else |
730 | erg += _text[pos++]; |
731 | } |
732 | |
733 | return erg; |
734 | } |
735 | |
736 | |
737 | // ---------------------------------------------------------------- |
738 | // Formula handling |
739 | |
740 | |
741 | bool Cell::makeFormula() |
742 | { |
743 | // kDebug(36002) ; |
744 | |
745 | // sanity check |
746 | if (!isFormula()) |
747 | return false; |
748 | |
749 | // parse the formula and check for errors |
750 | if (!formula().isValid()) { |
751 | sheet()->showStatusMessage(i18n("Parsing of formula in cell %1 failed." , fullName())); |
752 | setValue(Value::errorPARSE()); |
753 | return false; |
754 | } |
755 | return true; |
756 | } |
757 | |
758 | int Cell::effectiveAlignX() const |
759 | { |
760 | const Style style = effectiveStyle(); |
761 | int align = style.halign(); |
762 | if (align == Style::HAlignUndefined) { |
763 | //numbers should be right-aligned by default, as well as BiDi text |
764 | if ((style.formatType() == Format::Text) || value().isString()) |
765 | align = (displayText().isRightToLeft()) ? Style::Right : Style::Left; |
766 | else { |
767 | Value val = value(); |
768 | while (val.isArray()) val = val.element(0, 0); |
769 | if (val.isBoolean() || val.isNumber()) |
770 | align = Style::Right; |
771 | else |
772 | align = Style::Left; |
773 | } |
774 | } |
775 | return align; |
776 | } |
777 | |
778 | double Cell::width() const |
779 | { |
780 | const int rightCol = d->column + mergedXCells(); |
781 | double width = 0.0; |
782 | for (int col = d->column; col <= rightCol; ++col) |
783 | width += sheet()->columnFormat(col)->width(); |
784 | return width; |
785 | } |
786 | |
787 | double Cell::height() const |
788 | { |
789 | const int bottomRow = d->row + mergedYCells(); |
790 | return sheet()->rowFormats()->totalRowHeight(d->row, bottomRow); |
791 | } |
792 | |
793 | // parses the text |
794 | void Cell::parseUserInput(const QString& text) |
795 | { |
796 | // kDebug() ; |
797 | |
798 | // empty string? |
799 | if (text.isEmpty()) { |
800 | setValue(Value::empty()); |
801 | setUserInput(text); |
802 | setFormula(Formula::empty()); |
803 | return; |
804 | } |
805 | |
806 | // a formula? |
807 | if (text[0] == '=') { |
808 | Formula formula(sheet(), *this); |
809 | formula.setExpression(text); |
810 | setFormula(formula); |
811 | |
812 | // parse the formula and check for errors |
813 | if (!formula.isValid()) { |
814 | sheet()->showStatusMessage(i18n("Parsing of formula in cell %1 failed." , fullName())); |
815 | setValue(Value::errorPARSE()); |
816 | return; |
817 | } |
818 | return; |
819 | } |
820 | |
821 | // keep the old formula and value for the case, that validation fails |
822 | const Formula oldFormula = formula(); |
823 | const QString oldUserInput = userInput(); |
824 | const Value oldValue = value(); |
825 | |
826 | // here, the new value is not a formula anymore; clear an existing one |
827 | setFormula(Formula()); |
828 | |
829 | Value value; |
830 | if (style().formatType() == Format::Text) |
831 | value = Value(QString(text)); |
832 | else { |
833 | // Parses the text and return the appropriate value. |
834 | value = sheet()->map()->parser()->parse(text); |
835 | |
836 | #if 0 |
837 | // Parsing as time acts like an autoformat: we even change the input text |
838 | // [h]:mm:ss -> might get set by ValueParser |
839 | if (isTime() && (formatType() != Format::Time7)) |
840 | setUserInput(locale()->formatTime(value().asDateTime(sheet()->map()->calculationSettings()).time(), true)); |
841 | #endif |
842 | |
843 | // convert first letter to uppercase ? |
844 | if (sheet()->getFirstLetterUpper() && value.isString() && !text.isEmpty()) { |
845 | QString str = value.asString(); |
846 | value = Value(str[0].toUpper() + str.right(str.length() - 1)); |
847 | } |
848 | } |
849 | // set the new value |
850 | setUserInput(text); |
851 | setValue(value); |
852 | |
853 | // validation |
854 | if (!sheet()->isLoading()) { |
855 | Validity validity = this->validity(); |
856 | if (!validity.testValidity(this)) { |
857 | kDebug(36003) << "Validation failed" ; |
858 | //reapply old value if action == stop |
859 | setFormula(oldFormula); |
860 | setUserInput(oldUserInput); |
861 | setValue(oldValue); |
862 | } |
863 | } |
864 | } |
865 | |
866 | QString Cell::link() const |
867 | { |
868 | return sheet()->cellStorage()->link(d->column, d->row); |
869 | } |
870 | |
871 | void Cell::setLink(const QString& link) |
872 | { |
873 | sheet()->cellStorage()->setLink(d->column, d->row, link); |
874 | |
875 | if (!link.isEmpty() && userInput().isEmpty()) |
876 | parseUserInput(link); |
877 | } |
878 | |
879 | bool Cell::isDate() const |
880 | { |
881 | const Format::Type t = style().formatType(); |
882 | return (Format::isDate(t) || ((t == Format::Generic) && (value().format() == Value::fmt_Date))); |
883 | } |
884 | |
885 | bool Cell::isTime() const |
886 | { |
887 | const Format::Type t = style().formatType(); |
888 | return (Format::isTime(t) || ((t == Format::Generic) && (value().format() == Value::fmt_Time))); |
889 | } |
890 | |
891 | bool Cell::isText() const |
892 | { |
893 | const Format::Type t = style().formatType(); |
894 | return t == Format::Text; |
895 | } |
896 | |
897 | // Return true if this cell is part of a merged cell, but not the |
898 | // master cell. |
899 | |
900 | bool Cell::isPartOfMerged() const |
901 | { |
902 | return sheet()->cellStorage()->isPartOfMerged(d->column, d->row); |
903 | } |
904 | |
905 | Cell Cell::masterCell() const |
906 | { |
907 | return sheet()->cellStorage()->masterCell(d->column, d->row); |
908 | } |
909 | |
910 | // Merge a number of cells, i.e. make this cell obscure a number of |
911 | // other cells. If _x and _y == 0, then the merging is removed. |
912 | void Cell::mergeCells(int _col, int _row, int _x, int _y) |
913 | { |
914 | sheet()->cellStorage()->mergeCells(_col, _row, _x, _y); |
915 | } |
916 | |
917 | bool Cell::doesMergeCells() const |
918 | { |
919 | return sheet()->cellStorage()->doesMergeCells(d->column, d->row); |
920 | } |
921 | |
922 | int Cell::mergedXCells() const |
923 | { |
924 | return sheet()->cellStorage()->mergedXCells(d->column, d->row); |
925 | } |
926 | |
927 | int Cell::mergedYCells() const |
928 | { |
929 | return sheet()->cellStorage()->mergedYCells(d->column, d->row); |
930 | } |
931 | |
932 | bool Cell::isLocked() const |
933 | { |
934 | return sheet()->cellStorage()->isLocked(d->column, d->row); |
935 | } |
936 | |
937 | QRect Cell::lockedCells() const |
938 | { |
939 | return sheet()->cellStorage()->lockedCells(d->column, d->row); |
940 | } |
941 | |
942 | |
943 | // ================================================================ |
944 | // Saving and loading |
945 | |
946 | |
947 | QDomElement Cell::save(QDomDocument& doc, int xOffset, int yOffset, bool era) |
948 | { |
949 | // Save the position of this cell |
950 | QDomElement cell = doc.createElement("cell" ); |
951 | cell.setAttribute("row" , row() - yOffset); |
952 | cell.setAttribute("column" , column() - xOffset); |
953 | |
954 | // |
955 | // Save the formatting information |
956 | // |
957 | QDomElement formatElement(doc.createElement("format" )); |
958 | style().saveXML(doc, formatElement, sheet()->map()->styleManager()); |
959 | if (formatElement.hasChildNodes() || formatElement.attributes().length()) // don't save empty tags |
960 | cell.appendChild(formatElement); |
961 | |
962 | if (doesMergeCells()) { |
963 | if (mergedXCells()) |
964 | formatElement.setAttribute("colspan" , mergedXCells()); |
965 | if (mergedYCells()) |
966 | formatElement.setAttribute("rowspan" , mergedYCells()); |
967 | } |
968 | |
969 | Conditions conditions = this->conditions(); |
970 | if (!conditions.isEmpty()) { |
971 | QDomElement conditionElement = conditions.saveConditions(doc, sheet()->map()->converter()); |
972 | if (!conditionElement.isNull()) |
973 | cell.appendChild(conditionElement); |
974 | } |
975 | |
976 | Validity validity = this->validity(); |
977 | if (!validity.isEmpty()) { |
978 | QDomElement validityElement = validity.saveXML(doc, sheet()->map()->converter()); |
979 | if (!validityElement.isNull()) |
980 | cell.appendChild(validityElement); |
981 | } |
982 | |
983 | const QString = this->comment(); |
984 | if (!comment.isEmpty()) { |
985 | QDomElement = doc.createElement("comment" ); |
986 | commentElement.appendChild(doc.createCDATASection(comment)); |
987 | cell.appendChild(commentElement); |
988 | } |
989 | |
990 | // |
991 | // Save the text |
992 | // |
993 | if (!userInput().isEmpty()) { |
994 | // Formulas need to be encoded to ensure that they |
995 | // are position independent. |
996 | if (isFormula()) { |
997 | QDomElement txt = doc.createElement("text" ); |
998 | // if we are cutting to the clipboard, relative references need to be encoded absolutely |
999 | txt.appendChild(doc.createTextNode(encodeFormula(era))); |
1000 | cell.appendChild(txt); |
1001 | |
1002 | /* we still want to save the results of the formula */ |
1003 | QDomElement formulaResult = doc.createElement("result" ); |
1004 | saveCellResult(doc, formulaResult, displayText()); |
1005 | cell.appendChild(formulaResult); |
1006 | |
1007 | } else if (!link().isEmpty()) { |
1008 | // KSpread pre 1.4 saves link as rich text, marked with first char ' |
1009 | // Have to be saved in some CDATA section because of too many special charatcers. |
1010 | QDomElement txt = doc.createElement("text" ); |
1011 | QString qml = "!<a href=\"" + link() + "\">" + userInput() + "</a>" ; |
1012 | txt.appendChild(doc.createCDATASection(qml)); |
1013 | cell.appendChild(txt); |
1014 | } else { |
1015 | // Save the cell contents (in a locale-independent way) |
1016 | QDomElement txt = doc.createElement("text" ); |
1017 | saveCellResult(doc, txt, userInput()); |
1018 | cell.appendChild(txt); |
1019 | } |
1020 | } |
1021 | if (cell.hasChildNodes() || cell.attributes().length() > 2) // don't save empty tags |
1022 | // (the >2 is due to "row" and "column" attributes) |
1023 | return cell; |
1024 | else |
1025 | return QDomElement(); |
1026 | } |
1027 | |
1028 | bool Cell::saveCellResult(QDomDocument& doc, QDomElement& result, |
1029 | QString str) |
1030 | { |
1031 | QString dataType = "Other" ; // fallback |
1032 | |
1033 | if (value().isNumber()) { |
1034 | if (isDate()) { |
1035 | // serial number of date |
1036 | QDate dd = value().asDateTime(sheet()->map()->calculationSettings()).date(); |
1037 | dataType = "Date" ; |
1038 | str = "%1/%2/%3" ; |
1039 | str = str.arg(dd.year()).arg(dd.month()).arg(dd.day()); |
1040 | } else if (isTime()) { |
1041 | // serial number of time |
1042 | dataType = "Time" ; |
1043 | str = value().asDateTime(sheet()->map()->calculationSettings()).time().toString(); |
1044 | } else { |
1045 | // real number |
1046 | dataType = "Num" ; |
1047 | if (value().isInteger()) |
1048 | str = QString::number(value().asInteger()); |
1049 | else |
1050 | str = QString::number(numToDouble(value().asFloat()), 'g', DBL_DIG); |
1051 | } |
1052 | } |
1053 | |
1054 | if (value().isBoolean()) { |
1055 | dataType = "Bool" ; |
1056 | str = value().asBoolean() ? "true" : "false" ; |
1057 | } |
1058 | |
1059 | if (value().isString()) { |
1060 | dataType = "Str" ; |
1061 | str = value().asString(); |
1062 | } |
1063 | |
1064 | result.setAttribute("dataType" , dataType); |
1065 | |
1066 | const QString displayText = this->displayText(); |
1067 | if (!displayText.isEmpty()) |
1068 | result.setAttribute("outStr" , displayText); |
1069 | result.appendChild(doc.createTextNode(str)); |
1070 | |
1071 | return true; /* really isn't much of a way for this function to fail */ |
1072 | } |
1073 | |
1074 | void Cell::saveOdfAnnotation(KoXmlWriter &xmlwriter) |
1075 | { |
1076 | const QString = this->comment(); |
1077 | if (!comment.isEmpty()) { |
1078 | //<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"> |
1079 | xmlwriter.startElement("office:annotation" ); |
1080 | const QStringList text = comment.split('\n', QString::SkipEmptyParts); |
1081 | for (QStringList::ConstIterator it = text.begin(); it != text.end(); ++it) { |
1082 | xmlwriter.startElement("text:p" ); |
1083 | xmlwriter.addTextNode(*it); |
1084 | xmlwriter.endElement(); |
1085 | } |
1086 | xmlwriter.endElement(); |
1087 | } |
1088 | } |
1089 | |
1090 | QString Cell::saveOdfCellStyle(KoGenStyle ¤tCellStyle, KoGenStyles &mainStyles) |
1091 | { |
1092 | const Conditions conditions = this->conditions(); |
1093 | if (!conditions.isEmpty()) { |
1094 | // this has to be an automatic style |
1095 | currentCellStyle = KoGenStyle(KoGenStyle::TableCellAutoStyle, "table-cell" ); |
1096 | conditions.saveOdfConditions(currentCellStyle, sheet()->map()->converter()); |
1097 | } |
1098 | return style().saveOdf(currentCellStyle, mainStyles, d->sheet->map()->styleManager()); |
1099 | } |
1100 | |
1101 | |
1102 | bool Cell::saveOdf(int row, int column, int &repeated, |
1103 | OdfSavingContext& tableContext) |
1104 | { |
1105 | KoXmlWriter & xmlwriter = tableContext.shapeContext.xmlWriter(); |
1106 | KoGenStyles & mainStyles = tableContext.shapeContext.mainStyles(); |
1107 | |
1108 | // see: OpenDocument, 8.1.3 Table Cell |
1109 | if (!isPartOfMerged()) |
1110 | xmlwriter.startElement("table:table-cell" ); |
1111 | else |
1112 | xmlwriter.startElement("table:covered-table-cell" ); |
1113 | #if 0 |
1114 | //add font style |
1115 | QFont font; |
1116 | Value const value(cell.value()); |
1117 | if (!cell.isDefault()) { |
1118 | font = cell.format()->textFont(i, row); |
1119 | m_styles.addFont(font); |
1120 | |
1121 | if (cell.format()->hasProperty(Style::SComment)) |
1122 | hasComment = true; |
1123 | } |
1124 | #endif |
1125 | // NOTE save the value before the style as long as the Formatter does not work correctly |
1126 | if (link().isEmpty()) |
1127 | saveOdfValue(xmlwriter); |
1128 | |
1129 | const Style cellStyle = style(); |
1130 | |
1131 | // Either there's no column and row default and the style's not the default style, |
1132 | // or the style is different to one of them. The row default takes precedence. |
1133 | if ((!tableContext.rowDefaultStyles.contains(row) && |
1134 | !tableContext.columnDefaultStyles.contains(column) && |
1135 | !(cellStyle.isDefault() && conditions().isEmpty())) || |
1136 | (tableContext.rowDefaultStyles.contains(row) && tableContext.rowDefaultStyles[row] != cellStyle) || |
1137 | (tableContext.columnDefaultStyles.contains(column) && tableContext.columnDefaultStyles[column] != cellStyle)) { |
1138 | KoGenStyle currentCellStyle; // the type determined in saveOdfCellStyle |
1139 | QString styleName = saveOdfCellStyle(currentCellStyle, mainStyles); |
1140 | // skip 'table:style-name' attribute for the default style |
1141 | if (!currentCellStyle.isDefaultStyle()) { |
1142 | if (!styleName.isEmpty()) |
1143 | xmlwriter.addAttribute("table:style-name" , styleName); |
1144 | } |
1145 | } |
1146 | |
1147 | // group empty cells with the same style |
1148 | const QString = this->comment(); |
1149 | if (isEmpty() && comment.isEmpty() && !isPartOfMerged() && !doesMergeCells() && |
1150 | !tableContext.cellHasAnchoredShapes(sheet(), row, column)) { |
1151 | bool refCellIsDefault = isDefault(); |
1152 | int j = column + 1; |
1153 | Cell nextCell = sheet()->cellStorage()->nextInRow(column, row); |
1154 | while (!nextCell.isNull()) { |
1155 | // if |
1156 | // the next cell is not the adjacent one |
1157 | // or |
1158 | // the next cell is not empty |
1159 | if (nextCell.column() != j || (!nextCell.isEmpty() || tableContext.cellHasAnchoredShapes(sheet(), row, column))) { |
1160 | if (refCellIsDefault) { |
1161 | // if the origin cell was a default cell, |
1162 | // we count the default cells |
1163 | repeated = nextCell.column() - j + 1; |
1164 | |
1165 | // check if any of the empty/default cells we skipped contained anchored shapes |
1166 | int shapeColumn = tableContext.nextAnchoredShape(sheet(), row, column); |
1167 | if (shapeColumn) { |
1168 | repeated = qMin(repeated, shapeColumn - column); |
1169 | } |
1170 | } |
1171 | // otherwise we just stop here to process the adjacent |
1172 | // cell in the next iteration of the outer loop |
1173 | // (in Sheet::saveOdfCells) |
1174 | break; |
1175 | } |
1176 | |
1177 | if (nextCell.isPartOfMerged() || nextCell.doesMergeCells() || |
1178 | !nextCell.comment().isEmpty() || tableContext.cellHasAnchoredShapes(sheet(), row, nextCell.column()) || |
1179 | !(nextCell.style() == cellStyle && nextCell.conditions() == conditions())) { |
1180 | break; |
1181 | } |
1182 | ++repeated; |
1183 | // get the next cell and set the index to the adjacent cell |
1184 | nextCell = sheet()->cellStorage()->nextInRow(j++, row); |
1185 | } |
1186 | //kDebug(36003) << "Cell::saveOdf: empty cell in column" << column |
1187 | //<< "repeated" << repeated << "time(s)" << endl; |
1188 | |
1189 | if (repeated > 1) |
1190 | xmlwriter.addAttribute("table:number-columns-repeated" , QString::number(repeated)); |
1191 | } |
1192 | |
1193 | Validity validity = Cell(sheet(), column, row).validity(); |
1194 | if (!validity.isEmpty()) { |
1195 | GenValidationStyle styleVal(&validity, sheet()->map()->converter()); |
1196 | xmlwriter.addAttribute("table:validation-name" , tableContext.valStyle.insert(styleVal)); |
1197 | } |
1198 | if (isFormula()) { |
1199 | //kDebug(36003) <<"Formula found"; |
1200 | QString formula = Odf::encodeFormula(userInput(), locale()); |
1201 | xmlwriter.addAttribute("table:formula" , formula); |
1202 | } |
1203 | |
1204 | if (doesMergeCells()) { |
1205 | int colSpan = mergedXCells() + 1; |
1206 | int rowSpan = mergedYCells() + 1; |
1207 | |
1208 | if (colSpan > 1) |
1209 | xmlwriter.addAttribute("table:number-columns-spanned" , QString::number(colSpan)); |
1210 | |
1211 | if (rowSpan > 1) |
1212 | xmlwriter.addAttribute("table:number-rows-spanned" , QString::number(rowSpan)); |
1213 | } |
1214 | |
1215 | saveOdfAnnotation(xmlwriter); |
1216 | |
1217 | if (!isFormula() && !link().isEmpty()) { |
1218 | //kDebug(36003)<<"Link found"; |
1219 | xmlwriter.startElement("text:p" ); |
1220 | xmlwriter.startElement("text:a" ); |
1221 | const QString url = link(); |
1222 | //Reference cell is started by '#' |
1223 | if (Util::localReferenceAnchor(url)) |
1224 | xmlwriter.addAttribute("xlink:href" , ('#' + url)); |
1225 | else |
1226 | xmlwriter.addAttribute("xlink:href" , url); |
1227 | xmlwriter.addAttribute("xlink:type" , "simple" ); |
1228 | xmlwriter.addTextNode(userInput()); |
1229 | xmlwriter.endElement(); |
1230 | xmlwriter.endElement(); |
1231 | } |
1232 | |
1233 | if (!isEmpty() && link().isEmpty()) { |
1234 | QSharedPointer<QTextDocument> doc = richText(); |
1235 | if (doc) { |
1236 | QTextCharFormat format = style().asCharFormat(); |
1237 | ((KoCharacterStyle *)sheet()->map()->textStyleManager()->defaultParagraphStyle())->copyProperties(format); |
1238 | |
1239 | KoTextWriter writer(tableContext.shapeContext); |
1240 | |
1241 | writer.write(doc.data(), 0); |
1242 | } else { |
1243 | xmlwriter.startElement("text:p" ); |
1244 | xmlwriter.addTextNode(displayText().toUtf8()); |
1245 | xmlwriter.endElement(); |
1246 | } |
1247 | } |
1248 | |
1249 | // flake |
1250 | // Save shapes that are anchored to this cell. |
1251 | // see: OpenDocument, 2.3.1 Text Documents |
1252 | // see: OpenDocument, 9.2 Drawing Shapes |
1253 | if (tableContext.cellHasAnchoredShapes(sheet(), row, column)) { |
1254 | const QList<KoShape*> shapes = tableContext.cellAnchoredShapes(sheet(), row, column); |
1255 | for (int i = 0; i < shapes.count(); ++i) { |
1256 | KoShape* const shape = shapes[i]; |
1257 | const QPointF bottomRight = shape->boundingRect().bottomRight(); |
1258 | qreal endX = 0.0; |
1259 | qreal endY = 0.0; |
1260 | const int scol = sheet()->leftColumn(bottomRight.x(), endX); |
1261 | const int srow = sheet()->topRow(bottomRight.y(), endY); |
1262 | qreal offsetX = sheet()->columnPosition(column); |
1263 | qreal offsetY = sheet()->rowPosition(row); |
1264 | tableContext.shapeContext.addShapeOffset(shape, QTransform::fromTranslate(-offsetX, -offsetY)); |
1265 | shape->setAdditionalAttribute("table:end-cell-address" , Region(QPoint(scol, srow)).saveOdf()); |
1266 | shape->setAdditionalAttribute("table:end-x" , QString::number(bottomRight.x() - endX) + "pt" ); |
1267 | shape->setAdditionalAttribute("table:end-y" , QString::number(bottomRight.y() - endY) + "pt" ); |
1268 | shape->saveOdf(tableContext.shapeContext); |
1269 | shape->removeAdditionalAttribute("table:end-cell-address" ); |
1270 | shape->removeAdditionalAttribute("table:end-x" ); |
1271 | shape->removeAdditionalAttribute("table:end-y" ); |
1272 | tableContext.shapeContext.removeShapeOffset(shape); |
1273 | } |
1274 | } |
1275 | |
1276 | xmlwriter.endElement(); |
1277 | return true; |
1278 | } |
1279 | |
1280 | void Cell::saveOdfValue(KoXmlWriter &xmlWriter) |
1281 | { |
1282 | switch (value().format()) { |
1283 | case Value::fmt_None: break; //NOTHING HERE |
1284 | case Value::fmt_Boolean: { |
1285 | xmlWriter.addAttribute("office:value-type" , "boolean" ); |
1286 | xmlWriter.addAttribute("office:boolean-value" , (value().asBoolean() ? |
1287 | "true" : "false" )); |
1288 | break; |
1289 | } |
1290 | case Value::fmt_Number: { |
1291 | if (isDate()) { |
1292 | xmlWriter.addAttribute("office:value-type" , "date" ); |
1293 | xmlWriter.addAttribute("office:date-value" , |
1294 | value().asDate(sheet()->map()->calculationSettings()).toString(Qt::ISODate)); |
1295 | } else if (isText()) { |
1296 | xmlWriter.addAttribute("office:value-type" , "string" ); |
1297 | if (value().isInteger()) |
1298 | xmlWriter.addAttribute("office:string-value" , QString::number(value().asInteger())); |
1299 | else |
1300 | xmlWriter.addAttribute("office:string-value" , QString::number(numToDouble(value().asFloat()), 'g', DBL_DIG)); |
1301 | } else { |
1302 | xmlWriter.addAttribute("office:value-type" , "float" ); |
1303 | if (value().isInteger()) |
1304 | xmlWriter.addAttribute("office:value" , QString::number(value().asInteger())); |
1305 | else |
1306 | xmlWriter.addAttribute("office:value" , QString::number(numToDouble(value().asFloat()), 'g', DBL_DIG)); |
1307 | } |
1308 | break; |
1309 | } |
1310 | case Value::fmt_Percent: { |
1311 | xmlWriter.addAttribute("office:value-type" , "percentage" ); |
1312 | xmlWriter.addAttribute("office:value" , |
1313 | QString::number((double) numToDouble(value().asFloat()))); |
1314 | break; |
1315 | } |
1316 | case Value::fmt_Money: { |
1317 | xmlWriter.addAttribute("office:value-type" , "currency" ); |
1318 | const Style style = this->style(); |
1319 | if (style.hasAttribute(Style::CurrencyFormat)) { |
1320 | Currency currency = style.currency(); |
1321 | xmlWriter.addAttribute("office:currency" , currency.code()); |
1322 | } |
1323 | xmlWriter.addAttribute("office:value" , QString::number((double) numToDouble(value().asFloat()))); |
1324 | break; |
1325 | } |
1326 | case Value::fmt_DateTime: break; //NOTHING HERE |
1327 | case Value::fmt_Date: { |
1328 | if (isTime()) { |
1329 | xmlWriter.addAttribute("office:value-type" , "time" ); |
1330 | xmlWriter.addAttribute("office:time-value" , |
1331 | value().asTime(sheet()->map()->calculationSettings()).toString("'PT'hh'H'mm'M'ss'S'" )); |
1332 | } else { |
1333 | xmlWriter.addAttribute("office:value-type" , "date" ); |
1334 | xmlWriter.addAttribute("office:date-value" , |
1335 | value().asDate(sheet()->map()->calculationSettings()).toString(Qt::ISODate)); |
1336 | } |
1337 | break; |
1338 | } |
1339 | case Value::fmt_Time: { |
1340 | xmlWriter.addAttribute("office:value-type" , "time" ); |
1341 | xmlWriter.addAttribute("office:time-value" , |
1342 | value().asTime(sheet()->map()->calculationSettings()).toString("'PT'hh'H'mm'M'ss'S'" )); |
1343 | break; |
1344 | } |
1345 | case Value::fmt_String: { |
1346 | xmlWriter.addAttribute("office:value-type" , "string" ); |
1347 | xmlWriter.addAttribute("office:string-value" , value().asString()); |
1348 | break; |
1349 | } |
1350 | }; |
1351 | } |
1352 | |
1353 | bool Cell::loadOdf(const KoXmlElement& element, OdfLoadingContext& tableContext, |
1354 | const Styles& autoStyles, const QString& cellStyleName, |
1355 | QList<ShapeLoadingData>& shapeData) |
1356 | { |
1357 | static const QString sFormula = QString::fromLatin1("formula" ); |
1358 | static const QString sValidationName = QString::fromLatin1("validation-name" ); |
1359 | static const QString sValueType = QString::fromLatin1("value-type" ); |
1360 | static const QString sBoolean = QString::fromLatin1("boolean" ); |
1361 | static const QString sBooleanValue = QString::fromLatin1("boolean-value" ); |
1362 | static const QString sTrue = QString::fromLatin1("true" ); |
1363 | static const QString sFalse = QString::fromLatin1("false" ); |
1364 | static const QString sFloat = QString::fromLatin1("float" ); |
1365 | static const QString sValue = QString::fromLatin1("value" ); |
1366 | static const QString sCurrency = QString::fromLatin1("currency" ); |
1367 | static const QString sPercentage = QString::fromLatin1("percentage" ); |
1368 | static const QString sDate = QString::fromLatin1("date" ); |
1369 | static const QString sDateValue = QString::fromLatin1("date-value" ); |
1370 | static const QString sTime = QString::fromLatin1("time" ); |
1371 | static const QString sTimeValue = QString::fromLatin1("time-value" ); |
1372 | static const QString sString = QString::fromLatin1("string" ); |
1373 | static const QString sStringValue = QString::fromLatin1("string-value" ); |
1374 | static const QString sNumberColumnsSpanned = QString::fromLatin1("number-columns-spanned" ); |
1375 | static const QString sNumberRowsSpanned = QString::fromLatin1("number-rows-spanned" ); |
1376 | static const QString sAnnotation = QString::fromLatin1("annotation" ); |
1377 | static const QString sP = QString::fromLatin1("p" ); |
1378 | |
1379 | static const QStringList formulaNSPrefixes = QStringList() << "oooc:" << "kspr:" << "of:" << "msoxl:" ; |
1380 | |
1381 | //Search and load each paragraph of text. Each paragraph is separated by a line break. |
1382 | loadOdfCellText(element, tableContext, autoStyles, cellStyleName); |
1383 | |
1384 | // |
1385 | // formula |
1386 | // |
1387 | bool isFormula = false; |
1388 | if (element.hasAttributeNS(KoXmlNS::table, sFormula)) { |
1389 | isFormula = true; |
1390 | QString oasisFormula(element.attributeNS(KoXmlNS::table, sFormula, QString())); |
1391 | // kDebug(36003) << "cell:" << name() << "formula :" << oasisFormula; |
1392 | // each spreadsheet application likes to safe formulas with a different namespace |
1393 | // prefix, so remove all of them |
1394 | QString namespacePrefix; |
1395 | foreach(const QString &prefix, formulaNSPrefixes) { |
1396 | if (oasisFormula.startsWith(prefix)) { |
1397 | oasisFormula = oasisFormula.mid(prefix.length()); |
1398 | namespacePrefix = prefix; |
1399 | break; |
1400 | } |
1401 | } |
1402 | oasisFormula = Odf::decodeFormula(oasisFormula, locale(), namespacePrefix); |
1403 | setUserInput(oasisFormula); |
1404 | } else if (!userInput().isEmpty() && userInput().at(0) == '=') //prepend ' to the text to avoid = to be painted |
1405 | setUserInput(userInput().prepend('\'')); |
1406 | |
1407 | // |
1408 | // validation |
1409 | // |
1410 | if (element.hasAttributeNS(KoXmlNS::table, sValidationName)) { |
1411 | const QString validationName = element.attributeNS(KoXmlNS::table, sValidationName, QString()); |
1412 | kDebug(36003) << "cell:" << name() << sValidationName << validationName; |
1413 | Validity validity; |
1414 | validity.loadOdfValidation(this, validationName, tableContext); |
1415 | if (!validity.isEmpty()) |
1416 | setValidity(validity); |
1417 | } |
1418 | |
1419 | // |
1420 | // value type |
1421 | // |
1422 | if (element.hasAttributeNS(KoXmlNS::office, sValueType)) { |
1423 | const QString valuetype = element.attributeNS(KoXmlNS::office, sValueType, QString()); |
1424 | // kDebug(36003) << "cell:" << name() << "value-type:" << valuetype; |
1425 | if (valuetype == sBoolean) { |
1426 | const QString val = element.attributeNS(KoXmlNS::office, sBooleanValue, QString()).toLower(); |
1427 | if ((val == sTrue) || (val == sFalse)) |
1428 | setValue(Value(val == sTrue)); |
1429 | } |
1430 | |
1431 | // integer and floating-point value |
1432 | else if (valuetype == sFloat) { |
1433 | bool ok = false; |
1434 | Value value(element.attributeNS(KoXmlNS::office, sValue, QString()).toDouble(&ok)); |
1435 | if (ok) { |
1436 | value.setFormat(Value::fmt_Number); |
1437 | setValue(value); |
1438 | #if 0 |
1439 | Style style; |
1440 | style.setFormatType(Format::Number); |
1441 | setStyle(style); |
1442 | #endif |
1443 | } |
1444 | // always set the userInput to the actual value read from the cell, and not whatever happens to be set as text, as the textual representation of a value may be less accurate than the value itself |
1445 | if (!isFormula) |
1446 | setUserInput(sheet()->map()->converter()->asString(value).asString()); |
1447 | } |
1448 | |
1449 | // currency value |
1450 | else if (valuetype == sCurrency) { |
1451 | bool ok = false; |
1452 | Value value(element.attributeNS(KoXmlNS::office, sValue, QString()).toDouble(&ok)); |
1453 | if (ok) { |
1454 | value.setFormat(Value::fmt_Money); |
1455 | setValue(value); |
1456 | |
1457 | Currency currency; |
1458 | if (element.hasAttributeNS(KoXmlNS::office, sCurrency)) { |
1459 | currency = Currency(element.attributeNS(KoXmlNS::office, sCurrency, QString())); |
1460 | } |
1461 | /* TODO: somehow make this work again, all setStyle calls here will be overwritten by cell styles later |
1462 | if( style.isEmpty() ) { |
1463 | Style style; |
1464 | style.setCurrency(currency); |
1465 | setStyle(style); |
1466 | } */ |
1467 | } |
1468 | } else if (valuetype == sPercentage) { |
1469 | bool ok = false; |
1470 | Value value(element.attributeNS(KoXmlNS::office, sValue, QString()).toDouble(&ok)); |
1471 | if (ok) { |
1472 | value.setFormat(Value::fmt_Percent); |
1473 | setValue(value); |
1474 | if (!isFormula && userInput().isEmpty()) |
1475 | setUserInput(sheet()->map()->converter()->asString(value).asString()); |
1476 | // FIXME Stefan: Should be handled by Value::Format. Verify and remove! |
1477 | #if 0 |
1478 | Style style; |
1479 | style.setFormatType(Format::Percentage); |
1480 | setStyle(style); |
1481 | #endif |
1482 | } |
1483 | } else if (valuetype == sDate) { |
1484 | QString value = element.attributeNS(KoXmlNS::office, sDateValue, QString()); |
1485 | |
1486 | // "1980-10-15" or "2001-01-01T19:27:41" |
1487 | int year = 0, month = 0, day = 0, hours = 0, minutes = 0, seconds = 0; |
1488 | bool hasTime = false; |
1489 | bool ok = false; |
1490 | |
1491 | int p1 = value.indexOf('-'); |
1492 | if (p1 > 0) { |
1493 | year = value.left(p1).toInt(&ok); |
1494 | if (ok) { |
1495 | int p2 = value.indexOf('-', ++p1); |
1496 | month = value.mid(p1, p2 - p1).toInt(&ok); |
1497 | if (ok) { |
1498 | // the date can optionally have a time attached |
1499 | int p3 = value.indexOf('T', ++p2); |
1500 | if (p3 > 0) { |
1501 | hasTime = true; |
1502 | day = value.mid(p2, p3 - p2).toInt(&ok); |
1503 | if (ok) { |
1504 | int p4 = value.indexOf(':', ++p3); |
1505 | hours = value.mid(p3, p4 - p3).toInt(&ok); |
1506 | if (ok) { |
1507 | int p5 = value.indexOf(':', ++p4); |
1508 | minutes = value.mid(p4, p5 - p4).toInt(&ok); |
1509 | if (ok) |
1510 | seconds = value.right(value.length() - p5 - 1).toInt(&ok); |
1511 | } |
1512 | } |
1513 | } else { |
1514 | day = value.right(value.length() - p2).toInt(&ok); |
1515 | } |
1516 | } |
1517 | } |
1518 | } |
1519 | |
1520 | if (ok) { |
1521 | if (hasTime) |
1522 | setValue(Value(QDateTime(QDate(year, month, day), QTime(hours, minutes, seconds)), sheet()->map()->calculationSettings())); |
1523 | else |
1524 | setValue(Value(QDate(year, month, day), sheet()->map()->calculationSettings())); |
1525 | // FIXME Stefan: Should be handled by Value::Format. Verify and remove! |
1526 | //Sebsauer: Fixed now. Value::Format handles it correct. |
1527 | #if 0 |
1528 | Style s; |
1529 | s.setFormatType(Format::ShortDate); |
1530 | setStyle(s); |
1531 | #endif |
1532 | // kDebug(36003) << "cell:" << name() << "Type: date, value:" << value << "Date:" << year << " -" << month << " -" << day; |
1533 | } |
1534 | } else if (valuetype == sTime) { |
1535 | QString value = element.attributeNS(KoXmlNS::office, sTimeValue, QString()); |
1536 | |
1537 | // "PT15H10M12S" |
1538 | int hours = 0, minutes = 0, seconds = 0; |
1539 | int l = value.length(); |
1540 | QString num; |
1541 | bool ok = false; |
1542 | for (int i = 0; i < l; ++i) { |
1543 | if (value[i].isNumber()) { |
1544 | num += value[i]; |
1545 | continue; |
1546 | } else if (value[i] == 'H') |
1547 | hours = num.toInt(&ok); |
1548 | else if (value[i] == 'M') |
1549 | minutes = num.toInt(&ok); |
1550 | else if (value[i] == 'S') |
1551 | seconds = num.toInt(&ok); |
1552 | else |
1553 | continue; |
1554 | //kDebug(36003) << "Num:" << num; |
1555 | num.clear(); |
1556 | if (!ok) |
1557 | break; |
1558 | } |
1559 | |
1560 | if (ok) { |
1561 | // Value kval( timeToNum( hours, minutes, seconds ) ); |
1562 | // cell.setValue( kval ); |
1563 | setValue(Value(QTime(hours % 24, minutes, seconds), sheet()->map()->calculationSettings())); |
1564 | // FIXME Stefan: Should be handled by Value::Format. Verify and remove! |
1565 | #if 0 |
1566 | Style style; |
1567 | style.setFormatType(Format::Time); |
1568 | setStyle(style); |
1569 | #endif |
1570 | // kDebug(36003) << "cell:" << name() << "Type: time:" << value << "Hours:" << hours << "," << minutes << "," << seconds; |
1571 | } |
1572 | } else if (valuetype == sString) { |
1573 | if (element.hasAttributeNS(KoXmlNS::office, sStringValue)) { |
1574 | QString value = element.attributeNS(KoXmlNS::office, sStringValue, QString()); |
1575 | setValue(Value(value)); |
1576 | } else { |
1577 | // use the paragraph(s) read in before |
1578 | setValue(Value(userInput())); |
1579 | } |
1580 | // FIXME Stefan: Should be handled by Value::Format. Verify and remove! |
1581 | #if 0 |
1582 | Style style; |
1583 | style.setFormatType(Format::Text); |
1584 | setStyle(style); |
1585 | #endif |
1586 | } else { |
1587 | // kDebug(36003) << "cell:" << name() << " Unknown type. Parsing user input."; |
1588 | // Set the value by parsing the user input. |
1589 | parseUserInput(userInput()); |
1590 | } |
1591 | } else { // no value-type attribute |
1592 | // kDebug(36003) << "cell:" << name() << " No value type specified. Parsing user input."; |
1593 | // Set the value by parsing the user input. |
1594 | parseUserInput(userInput()); |
1595 | } |
1596 | |
1597 | // |
1598 | // merged cells ? |
1599 | // |
1600 | int colSpan = 1; |
1601 | int rowSpan = 1; |
1602 | if (element.hasAttributeNS(KoXmlNS::table, sNumberColumnsSpanned)) { |
1603 | bool ok = false; |
1604 | int span = element.attributeNS(KoXmlNS::table, sNumberColumnsSpanned, QString()).toInt(&ok); |
1605 | if (ok) colSpan = span; |
1606 | } |
1607 | if (element.hasAttributeNS(KoXmlNS::table, sNumberRowsSpanned)) { |
1608 | bool ok = false; |
1609 | int span = element.attributeNS(KoXmlNS::table, sNumberRowsSpanned, QString()).toInt(&ok); |
1610 | if (ok) rowSpan = span; |
1611 | } |
1612 | if (colSpan > 1 || rowSpan > 1) |
1613 | mergeCells(d->column, d->row, colSpan - 1, rowSpan - 1); |
1614 | |
1615 | // |
1616 | // cell comment/annotation |
1617 | // |
1618 | KoXmlElement annotationElement = KoXml::namedItemNS(element, KoXmlNS::office, sAnnotation); |
1619 | if (!annotationElement.isNull()) { |
1620 | QString ; |
1621 | KoXmlNode node = annotationElement.firstChild(); |
1622 | while (!node.isNull()) { |
1623 | KoXmlElement = node.toElement(); |
1624 | if (!commentElement.isNull()) |
1625 | if (commentElement.localName() == sP && commentElement.namespaceURI() == KoXmlNS::text) { |
1626 | if (!comment.isEmpty()) comment.append('\n'); |
1627 | comment.append(commentElement.text()); |
1628 | } |
1629 | |
1630 | node = node.nextSibling(); |
1631 | } |
1632 | if (!comment.isEmpty()) |
1633 | setComment(comment); |
1634 | } |
1635 | |
1636 | loadOdfObjects(element, tableContext, shapeData); |
1637 | |
1638 | return true; |
1639 | } |
1640 | |
1641 | // Similar to KoXml::namedItemNS except that children of span tags will be evaluated too. |
1642 | KoXmlElement namedItemNSWithSpan(const KoXmlNode& node, const QString &nsURI, const QString &localName) |
1643 | { |
1644 | KoXmlNode n = node.firstChild(); |
1645 | for (; !n.isNull(); n = n.nextSibling()) { |
1646 | if (n.isElement()) { |
1647 | if (n.localName() == localName && n.namespaceURI() == nsURI) { |
1648 | return n.toElement(); |
1649 | } |
1650 | if (n.localName() == "span" && n.namespaceURI() == nsURI) { |
1651 | KoXmlElement e = KoXml::namedItemNS(n, nsURI, localName); // not recursive |
1652 | if (!e.isNull()) { |
1653 | return e; |
1654 | } |
1655 | } |
1656 | } |
1657 | } |
1658 | return KoXmlElement(); |
1659 | } |
1660 | |
1661 | // recursively goes through all children of parent and returns true if there is any element |
1662 | // in the draw: namespace in this subtree |
1663 | static bool findDrawElements(const KoXmlElement& parent) |
1664 | { |
1665 | KoXmlElement element; |
1666 | forEachElement(element , parent) { |
1667 | if (element.namespaceURI() == KoXmlNS::draw) |
1668 | return true; |
1669 | if (findDrawElements(element)) |
1670 | return true; |
1671 | } |
1672 | return false; |
1673 | } |
1674 | |
1675 | QString loadOdfCellTextNodes(const KoXmlElement& element, int *textFragmentCount, int *lineCount, bool *hasRichText, bool *stripLeadingSpace) |
1676 | { |
1677 | QString cellText; |
1678 | bool countedOwnFragments = false; |
1679 | bool prevWasText = false; |
1680 | for (KoXmlNode n = element.firstChild(); !n.isNull(); n = n.nextSibling()) { |
1681 | if (n.isText()) { |
1682 | prevWasText = true; |
1683 | QString t = KoTextLoader::normalizeWhitespace(n.toText().data(), *stripLeadingSpace); |
1684 | if (!t.isEmpty()) { |
1685 | *stripLeadingSpace = t[t.length() - 1].isSpace(); |
1686 | cellText += t; |
1687 | if (!countedOwnFragments) { |
1688 | // We only count the number of different parent elements which have text. That is |
1689 | // so cause different parent-elements may mean different styles which means |
1690 | // rich-text while the same parent element means the same style so we can easily |
1691 | // put them together into one string. |
1692 | countedOwnFragments = true; |
1693 | ++(*textFragmentCount); |
1694 | } |
1695 | } |
1696 | } else { |
1697 | KoXmlElement e = n.toElement(); |
1698 | if (!e.isNull()) { |
1699 | if (prevWasText && !cellText.isEmpty() && cellText[cellText.length() - 1].isSpace()) { |
1700 | // A trailing space of the cellText collected so far needs to be preserved when |
1701 | // more text-nodes within the same parent follow but if an element like e.g. |
1702 | // text:s follows then a trailing space needs to be removed. |
1703 | cellText.chop(1); |
1704 | } |
1705 | prevWasText = false; |
1706 | |
1707 | // We can optimize some elements like text:s (space), text:tab (tabulator) and |
1708 | // text:line-break (new-line) to not produce rich-text but add the equivalent |
1709 | // for them in plain-text. |
1710 | const bool isTextNs = e.namespaceURI() == KoXmlNS::text; |
1711 | if (isTextNs && e.localName() == "s" ) { |
1712 | const int howmany = qMax(1, e.attributeNS(KoXmlNS::text, "c" , QString()).toInt()); |
1713 | cellText += QString().fill(32, howmany); |
1714 | } else if (isTextNs && e.localName() == "tab" ) { |
1715 | cellText += '\t'; |
1716 | } else if (isTextNs && e.localName() == "line-break" ) { |
1717 | cellText += '\n'; |
1718 | ++(*lineCount); |
1719 | } else if (isTextNs && e.localName() == "span" ) { |
1720 | // Nested span-elements means recursive evaluation. |
1721 | cellText += loadOdfCellTextNodes(e, textFragmentCount, lineCount, hasRichText, stripLeadingSpace); |
1722 | } else if (!isTextNs || |
1723 | ( e.localName() != "annotation" && |
1724 | e.localName() != "bookmark" && |
1725 | e.localName() != "meta" && |
1726 | e.localName() != "tag" )) { |
1727 | // Seems we have an element we cannot easily translate to a string what |
1728 | // means it's all rich-text now. |
1729 | *hasRichText = true; |
1730 | } |
1731 | } |
1732 | } |
1733 | } |
1734 | return cellText; |
1735 | } |
1736 | |
1737 | void Cell::loadOdfCellText(const KoXmlElement& parent, OdfLoadingContext& tableContext, const Styles& autoStyles, const QString& cellStyleName) |
1738 | { |
1739 | //Search and load each paragraph of text. Each paragraph is separated by a line break |
1740 | KoXmlElement textParagraphElement; |
1741 | QString cellText; |
1742 | |
1743 | int lineCount = 0; |
1744 | bool hasRichText = false; |
1745 | bool stripLeadingSpace = true; |
1746 | |
1747 | forEachElement(textParagraphElement , parent) { |
1748 | if (textParagraphElement.localName() == "p" && |
1749 | textParagraphElement.namespaceURI() == KoXmlNS::text) { |
1750 | |
1751 | // the text:a link could be located within a text:span element |
1752 | KoXmlElement textA = namedItemNSWithSpan(textParagraphElement, KoXmlNS::text, "a" ); |
1753 | if (!textA.isNull() && textA.hasAttributeNS(KoXmlNS::xlink, "href" )) { |
1754 | QString link = textA.attributeNS(KoXmlNS::xlink, "href" , QString()); |
1755 | cellText = textA.text(); |
1756 | setUserInput(cellText); |
1757 | hasRichText = false; |
1758 | lineCount = 0; |
1759 | // The value will be set later in loadOdf(). |
1760 | if ((!link.isEmpty()) && (link[0] == '#')) |
1761 | link.remove(0, 1); |
1762 | setLink(link); |
1763 | // Abort here cause we can handle only either a link in a cell or (rich-)text but not both. |
1764 | break; |
1765 | } |
1766 | |
1767 | if (!cellText.isNull()) |
1768 | cellText += '\n'; |
1769 | |
1770 | ++lineCount; |
1771 | int textFragmentCount = 0; |
1772 | |
1773 | // Our text could contain formating for value or result of formula or a mix of |
1774 | // multiple text:span elements with text-nodes and line-break's. |
1775 | cellText += loadOdfCellTextNodes(textParagraphElement, &textFragmentCount, &lineCount, &hasRichText, &stripLeadingSpace); |
1776 | |
1777 | // If we got text from multiple different sources (e.g. from the text:p and a |
1778 | // child text:span) then we have very likely rich-text. |
1779 | if (!hasRichText) |
1780 | hasRichText = textFragmentCount >= 2; |
1781 | } |
1782 | } |
1783 | |
1784 | if (!cellText.isNull()) { |
1785 | if (hasRichText && !findDrawElements(parent)) { |
1786 | // for now we don't support richtext and embedded shapes in the same cell; |
1787 | // this is because they would currently be loaded twice, once by the KoTextLoader |
1788 | // and later properly by the cell itself |
1789 | |
1790 | Style style; style.setDefault(); |
1791 | if (!cellStyleName.isEmpty()) { |
1792 | if (autoStyles.contains(cellStyleName)) |
1793 | style.merge(autoStyles[cellStyleName]); |
1794 | else { |
1795 | const CustomStyle* namedStyle = sheet()->map()->styleManager()->style(cellStyleName); |
1796 | if (namedStyle) |
1797 | style.merge(*namedStyle); |
1798 | } |
1799 | } |
1800 | |
1801 | QTextCharFormat format = style.asCharFormat(); |
1802 | ((KoCharacterStyle *)sheet()->map()->textStyleManager()->defaultParagraphStyle())->copyProperties(format); |
1803 | |
1804 | QSharedPointer<QTextDocument> doc(new QTextDocument); |
1805 | KoTextDocument(doc.data()).setStyleManager(sheet()->map()->textStyleManager()); |
1806 | |
1807 | Q_ASSERT(tableContext.shapeContext); |
1808 | KoTextLoader loader(*tableContext.shapeContext); |
1809 | QTextCursor cursor(doc.data()); |
1810 | loader.loadBody(parent, cursor); |
1811 | |
1812 | setUserInput(doc->toPlainText()); |
1813 | setRichText(doc); |
1814 | } else { |
1815 | setUserInput(cellText); |
1816 | } |
1817 | } |
1818 | |
1819 | // enable word wrapping if multiple lines of text have been found. |
1820 | if (lineCount >= 2) { |
1821 | Style newStyle; |
1822 | newStyle.setWrapText(true); |
1823 | setStyle(newStyle); |
1824 | } |
1825 | } |
1826 | |
1827 | void Cell::loadOdfObjects(const KoXmlElement &parent, OdfLoadingContext& tableContext, QList<ShapeLoadingData>& shapeData) |
1828 | { |
1829 | // Register additional attributes, that identify shapes anchored in cells. |
1830 | // Their dimensions need adjustment after all rows are loaded, |
1831 | // because the position of the end cell is not always known yet. |
1832 | KoShapeLoadingContext::addAdditionalAttributeData(KoShapeLoadingContext::AdditionalAttributeData( |
1833 | KoXmlNS::table, "end-cell-address" , |
1834 | "table:end-cell-address" )); |
1835 | KoShapeLoadingContext::addAdditionalAttributeData(KoShapeLoadingContext::AdditionalAttributeData( |
1836 | KoXmlNS::table, "end-x" , |
1837 | "table:end-x" )); |
1838 | KoShapeLoadingContext::addAdditionalAttributeData(KoShapeLoadingContext::AdditionalAttributeData( |
1839 | KoXmlNS::table, "end-y" , |
1840 | "table:end-y" )); |
1841 | |
1842 | KoXmlElement element; |
1843 | forEachElement(element, parent) { |
1844 | if (element.namespaceURI() != KoXmlNS::draw) |
1845 | continue; |
1846 | |
1847 | if (element.localName() == "a" ) { |
1848 | // It may the case that the object(s) are embedded into a hyperlink so actions are done on |
1849 | // clicking it/them but since we do not supported objects-with-hyperlinks yet we just fetch |
1850 | // the inner elements and use them to at least create and show the objects (see bug 249862). |
1851 | KoXmlElement e; |
1852 | forEachElement(e, element) { |
1853 | if (e.namespaceURI() != KoXmlNS::draw) |
1854 | continue; |
1855 | ShapeLoadingData data = loadOdfObject(e, *tableContext.shapeContext); |
1856 | if (data.shape) { |
1857 | shapeData.append(data); |
1858 | } |
1859 | } |
1860 | } else { |
1861 | ShapeLoadingData data = loadOdfObject(element, *tableContext.shapeContext); |
1862 | if (data.shape) { |
1863 | shapeData.append(data); |
1864 | } |
1865 | } |
1866 | } |
1867 | } |
1868 | |
1869 | ShapeLoadingData Cell::loadOdfObject(const KoXmlElement &element, KoShapeLoadingContext &shapeContext) |
1870 | { |
1871 | ShapeLoadingData data; |
1872 | data.shape = 0; |
1873 | KoShape* shape = KoShapeRegistry::instance()->createShapeFromOdf(element, shapeContext); |
1874 | if (!shape) { |
1875 | kDebug(36003) << "Unable to load shape with localName=" << element.localName(); |
1876 | return data; |
1877 | } |
1878 | |
1879 | d->sheet->addShape(shape); |
1880 | |
1881 | // The position is relative to the upper left sheet corner until now. Move it. |
1882 | QPointF position = shape->position(); |
1883 | // Remember how far we're off from the top-left corner of this cell |
1884 | double offsetX = position.x(); |
1885 | double offsetY = position.y(); |
1886 | for (int col = 1; col < column(); ++col) |
1887 | position += QPointF(d->sheet->columnFormat(col)->width(), 0.0); |
1888 | if (this->row() > 1) |
1889 | position += QPointF(0.0, d->sheet->rowFormats()->totalRowHeight(1, this->row() - 1)); |
1890 | shape->setPosition(position); |
1891 | |
1892 | dynamic_cast<ShapeApplicationData*>(shape->applicationData())->setAnchoredToCell(true); |
1893 | |
1894 | // All three attributes are necessary for cell anchored shapes. |
1895 | // Otherwise, they are anchored in the sheet. |
1896 | if (!shape->hasAdditionalAttribute("table:end-cell-address" ) || |
1897 | !shape->hasAdditionalAttribute("table:end-x" ) || |
1898 | !shape->hasAdditionalAttribute("table:end-y" )) { |
1899 | kDebug(36003) << "Not all attributes found, that are necessary for cell anchoring." ; |
1900 | return data; |
1901 | } |
1902 | |
1903 | Region endCell(Region::loadOdf(shape->additionalAttribute("table:end-cell-address" )), |
1904 | d->sheet->map(), d->sheet); |
1905 | if (!endCell.isValid() || !endCell.isSingular()) |
1906 | return data; |
1907 | |
1908 | QString string = shape->additionalAttribute("table:end-x" ); |
1909 | if (string.isNull()) |
1910 | return data; |
1911 | double endX = KoUnit::parseValue(string); |
1912 | |
1913 | string = shape->additionalAttribute("table:end-y" ); |
1914 | if (string.isNull()) |
1915 | return data; |
1916 | double endY = KoUnit::parseValue(string); |
1917 | |
1918 | data.shape = shape; |
1919 | data.startCell = QPoint(column(), row()); |
1920 | data.offset = QPointF(offsetX, offsetY); |
1921 | data.endCell = endCell; |
1922 | data.endPoint = QPointF(endX, endY); |
1923 | |
1924 | // The column dimensions are already the final ones, but not the row dimensions. |
1925 | // The default height is used for the not yet loaded rows. |
1926 | // TODO Stefan: Honor non-default row heights later! |
1927 | // subtract offset because the accumulated width and height we calculate below starts |
1928 | // at the top-left corner of this cell, but the shape can have an offset to that corner |
1929 | QSizeF size = QSizeF(endX - offsetX, endY - offsetY); |
1930 | for (int col = column(); col < endCell.firstRange().left(); ++col) |
1931 | size += QSizeF(d->sheet->columnFormat(col)->width(), 0.0); |
1932 | if (endCell.firstRange().top() > this->row()) |
1933 | size += QSizeF(0.0, d->sheet->rowFormats()->totalRowHeight(this->row(), endCell.firstRange().top() - 1)); |
1934 | shape->setSize(size); |
1935 | |
1936 | return data; |
1937 | } |
1938 | |
1939 | bool Cell::load(const KoXmlElement & cell, int _xshift, int _yshift, |
1940 | Paste::Mode mode, Paste::Operation op, bool paste) |
1941 | { |
1942 | bool ok; |
1943 | |
1944 | // |
1945 | // First of all determine in which row and column this |
1946 | // cell belongs. |
1947 | // |
1948 | d->row = cell.attribute("row" ).toInt(&ok) + _yshift; |
1949 | if (!ok) return false; |
1950 | d->column = cell.attribute("column" ).toInt(&ok) + _xshift; |
1951 | if (!ok) return false; |
1952 | |
1953 | // Validation |
1954 | if (d->row < 1 || d->row > KS_rowMax) { |
1955 | kDebug(36001) << "Cell::load: Value out of range Cell:row=" << d->row; |
1956 | return false; |
1957 | } |
1958 | if (d->column < 1 || d->column > KS_colMax) { |
1959 | kDebug(36001) << "Cell::load: Value out of range Cell:column=" << d->column; |
1960 | return false; |
1961 | } |
1962 | |
1963 | // |
1964 | // Load formatting information. |
1965 | // |
1966 | KoXmlElement formatElement = cell.namedItem("format" ).toElement(); |
1967 | if (!formatElement.isNull() && |
1968 | ((mode == Paste::Normal) || (mode == Paste::Format) || (mode == Paste::NoBorder))) { |
1969 | int mergedXCells = 0; |
1970 | int mergedYCells = 0; |
1971 | if (formatElement.hasAttribute("colspan" )) { |
1972 | int i = formatElement.attribute("colspan" ).toInt(&ok); |
1973 | if (!ok) return false; |
1974 | // Validation |
1975 | if (i < 0 || i > KS_spanMax) { |
1976 | kDebug(36001) << "Value out of range Cell::colspan=" << i; |
1977 | return false; |
1978 | } |
1979 | if (i) |
1980 | mergedXCells = i; |
1981 | } |
1982 | |
1983 | if (formatElement.hasAttribute("rowspan" )) { |
1984 | int i = formatElement.attribute("rowspan" ).toInt(&ok); |
1985 | if (!ok) return false; |
1986 | // Validation |
1987 | if (i < 0 || i > KS_spanMax) { |
1988 | kDebug(36001) << "Value out of range Cell::rowspan=" << i; |
1989 | return false; |
1990 | } |
1991 | if (i) |
1992 | mergedYCells = i; |
1993 | } |
1994 | |
1995 | if (mergedXCells != 0 || mergedYCells != 0) |
1996 | mergeCells(d->column, d->row, mergedXCells, mergedYCells); |
1997 | |
1998 | Style style; |
1999 | if (!style.loadXML(formatElement, mode)) |
2000 | return false; |
2001 | setStyle(style); |
2002 | } |
2003 | |
2004 | // |
2005 | // Load the condition section of a cell. |
2006 | // |
2007 | KoXmlElement conditionsElement = cell.namedItem("condition" ).toElement(); |
2008 | if (!conditionsElement.isNull()) { |
2009 | Conditions conditions; |
2010 | Map *const map = sheet()->map(); |
2011 | ValueParser *const valueParser = map->parser(); |
2012 | conditions.loadConditions(conditionsElement, valueParser); |
2013 | if (!conditions.isEmpty()) |
2014 | setConditions(conditions); |
2015 | } else if (paste && (mode == Paste::Normal || mode == Paste::NoBorder)) { |
2016 | //clear the conditional formatting |
2017 | setConditions(Conditions()); |
2018 | } |
2019 | |
2020 | KoXmlElement validityElement = cell.namedItem("validity" ).toElement(); |
2021 | if (!validityElement.isNull()) { |
2022 | Validity validity; |
2023 | if (validity.loadXML(this, validityElement)) |
2024 | setValidity(validity); |
2025 | } else if (paste && (mode == Paste::Normal || mode == Paste::NoBorder)) { |
2026 | // clear the validity |
2027 | setValidity(Validity()); |
2028 | } |
2029 | |
2030 | // |
2031 | // Load the comment |
2032 | // |
2033 | KoXmlElement = cell.namedItem("comment" ).toElement(); |
2034 | if (!comment.isNull() && |
2035 | (mode == Paste::Normal || mode == Paste::Comment || mode == Paste::NoBorder)) { |
2036 | QString t = comment.text(); |
2037 | //t = t.trimmed(); |
2038 | setComment(t); |
2039 | } |
2040 | |
2041 | // |
2042 | // The real content of the cell is loaded here. It is stored in |
2043 | // the "text" tag, which contains either a text or a CDATA section. |
2044 | // |
2045 | // TODO: make this suck less. We set data twice, in loadCellData, and |
2046 | // also here. Not good. |
2047 | KoXmlElement text = cell.namedItem("text" ).toElement(); |
2048 | |
2049 | if (!text.isNull() && |
2050 | (mode == Paste::Normal || mode == Paste::Text || mode == Paste::NoBorder || mode == Paste::Result)) { |
2051 | |
2052 | /* older versions mistakenly put the datatype attribute on the cell instead |
2053 | of the text. Just move it over in case we're parsing an old document */ |
2054 | QString dataType; |
2055 | if (cell.hasAttribute("dataType" )) // new docs |
2056 | dataType = cell.attribute("dataType" ); |
2057 | |
2058 | KoXmlElement result = cell.namedItem("result" ).toElement(); |
2059 | QString txt = text.text(); |
2060 | if ((mode == Paste::Result) && (txt[0] == '=')) |
2061 | // paste text of the element, if we want to paste result |
2062 | // and the source cell contains a formula |
2063 | setUserInput(result.text()); |
2064 | else |
2065 | //otherwise copy everything |
2066 | loadCellData(text, op, dataType); |
2067 | |
2068 | if (!result.isNull()) { |
2069 | QString dataType; |
2070 | QString t = result.text(); |
2071 | |
2072 | if (result.hasAttribute("dataType" )) |
2073 | dataType = result.attribute("dataType" ); |
2074 | |
2075 | // boolean ? |
2076 | if (dataType == "Bool" ) { |
2077 | if (t == "false" ) |
2078 | setValue(Value(false)); |
2079 | else if (t == "true" ) |
2080 | setValue(Value(true)); |
2081 | } else if (dataType == "Num" ) { |
2082 | bool ok = false; |
2083 | double dd = t.toDouble(&ok); |
2084 | if (ok) |
2085 | setValue(Value(dd)); |
2086 | } else if (dataType == "Date" ) { |
2087 | bool ok = false; |
2088 | double dd = t.toDouble(&ok); |
2089 | if (ok) { |
2090 | Value value(dd); |
2091 | value.setFormat(Value::fmt_Date); |
2092 | setValue(value); |
2093 | } else { |
2094 | int pos = t.indexOf('/'); |
2095 | int year = t.mid(0, pos).toInt(); |
2096 | int pos1 = t.indexOf('/', pos + 1); |
2097 | int month = t.mid(pos + 1, ((pos1 - 1) - pos)).toInt(); |
2098 | int day = t.right(t.length() - pos1 - 1).toInt(); |
2099 | QDate date(year, month, day); |
2100 | if (date.isValid()) |
2101 | setValue(Value(date, sheet()->map()->calculationSettings())); |
2102 | } |
2103 | } else if (dataType == "Time" ) { |
2104 | bool ok = false; |
2105 | double dd = t.toDouble(&ok); |
2106 | if (ok) { |
2107 | Value value(dd); |
2108 | value.setFormat(Value::fmt_Time); |
2109 | setValue(value); |
2110 | } else { |
2111 | int hours = -1; |
2112 | int minutes = -1; |
2113 | int second = -1; |
2114 | int pos, pos1; |
2115 | pos = t.indexOf(':'); |
2116 | hours = t.mid(0, pos).toInt(); |
2117 | pos1 = t.indexOf(':', pos + 1); |
2118 | minutes = t.mid(pos + 1, ((pos1 - 1) - pos)).toInt(); |
2119 | second = t.right(t.length() - pos1 - 1).toInt(); |
2120 | QTime time(hours, minutes, second); |
2121 | if (time.isValid()) |
2122 | setValue(Value(time, sheet()->map()->calculationSettings())); |
2123 | } |
2124 | } else { |
2125 | setValue(Value(t)); |
2126 | } |
2127 | } |
2128 | } |
2129 | |
2130 | return true; |
2131 | } |
2132 | |
2133 | bool Cell::loadCellData(const KoXmlElement & text, Paste::Operation op, const QString &_dataType) |
2134 | { |
2135 | //TODO: use converter()->asString() to generate userInput() |
2136 | |
2137 | QString t = text.text(); |
2138 | t = t.trimmed(); |
2139 | |
2140 | // A formula like =A1+A2 ? |
2141 | if ((!t.isEmpty()) && (t[0] == '=')) { |
2142 | t = decodeFormula(t); |
2143 | parseUserInput(pasteOperation(t, userInput(), op)); |
2144 | |
2145 | makeFormula(); |
2146 | } |
2147 | // rich text ? |
2148 | else if ((!t.isEmpty()) && (t[0] == '!')) { |
2149 | // KSpread pre 1.4 stores hyperlink as rich text (first char is '!') |
2150 | // extract the link and the correspoding text |
2151 | // This is a rather dirty hack, but enough for KSpread generated XML |
2152 | bool inside_tag = false; |
2153 | QString qml_text; |
2154 | QString tag; |
2155 | QString qml_link; |
2156 | |
2157 | for (int i = 1; i < t.length(); ++i) { |
2158 | QChar ch = t[i]; |
2159 | if (ch == '<') { |
2160 | if (!inside_tag) { |
2161 | inside_tag = true; |
2162 | tag.clear(); |
2163 | } |
2164 | } else if (ch == '>') { |
2165 | if (inside_tag) { |
2166 | inside_tag = false; |
2167 | if (tag.startsWith(QLatin1String("a href=\"" ), Qt::CaseSensitive) && |
2168 | tag.endsWith(QLatin1Char('"'))) { |
2169 | qml_link.remove(0, 8).chop(1); |
2170 | } |
2171 | tag.clear(); |
2172 | } |
2173 | } else { |
2174 | if (!inside_tag) |
2175 | qml_text += ch; |
2176 | else |
2177 | tag += ch; |
2178 | } |
2179 | } |
2180 | |
2181 | if (!qml_link.isEmpty()) |
2182 | setLink(qml_link); |
2183 | setUserInput(qml_text); |
2184 | setValue(Value(qml_text)); |
2185 | } else { |
2186 | bool newStyleLoading = true; |
2187 | QString dataType = _dataType; |
2188 | |
2189 | if (dataType.isNull()) { |
2190 | if (text.hasAttribute("dataType" )) { // new docs |
2191 | dataType = text.attribute("dataType" ); |
2192 | } else { // old docs: do the ugly solution of parsing the text |
2193 | // ...except for date/time |
2194 | if (isDate() && (t.count('/') == 2)) |
2195 | dataType = "Date" ; |
2196 | else if (isTime() && (t.count(':') == 2)) |
2197 | dataType = "Time" ; |
2198 | else { |
2199 | parseUserInput(pasteOperation(t, userInput(), op)); |
2200 | newStyleLoading = false; |
2201 | } |
2202 | } |
2203 | } |
2204 | |
2205 | if (newStyleLoading) { |
2206 | // boolean ? |
2207 | if (dataType == "Bool" ) |
2208 | setValue(Value(t.toLower() == "true" )); |
2209 | |
2210 | // number ? |
2211 | else if (dataType == "Num" ) { |
2212 | bool ok = false; |
2213 | if (t.contains('.')) |
2214 | setValue(Value(t.toDouble(&ok))); // We save in non-localized format |
2215 | else |
2216 | setValue(Value(t.toLongLong(&ok))); |
2217 | if (!ok) { |
2218 | kWarning(36001) << "Couldn't parse '" << t << "' as number." ; |
2219 | } |
2220 | /* We will need to localize the text version of the number */ |
2221 | KLocale* locale = sheet()->map()->calculationSettings()->locale(); |
2222 | |
2223 | /* KLocale::formatNumber requires the precision we want to return. |
2224 | */ |
2225 | int precision = t.length() - t.indexOf('.') - 1; |
2226 | |
2227 | if (style().formatType() == Format::Percentage) { |
2228 | if (value().isInteger()) |
2229 | t = locale->formatNumber(value().asInteger() * 100); |
2230 | else |
2231 | t = locale->formatNumber(numToDouble(value().asFloat() * 100.0), precision); |
2232 | setUserInput(pasteOperation(t, userInput(), op)); |
2233 | setUserInput(userInput() + '%'); |
2234 | } else { |
2235 | if (value().isInteger()) |
2236 | t = locale->formatLong(value().asInteger()); |
2237 | else |
2238 | t = locale->formatNumber(numToDouble(value().asFloat()), precision); |
2239 | setUserInput(pasteOperation(t, userInput(), op)); |
2240 | } |
2241 | } |
2242 | |
2243 | // date ? |
2244 | else if (dataType == "Date" ) { |
2245 | int pos = t.indexOf('/'); |
2246 | int year = t.mid(0, pos).toInt(); |
2247 | int pos1 = t.indexOf('/', pos + 1); |
2248 | int month = t.mid(pos + 1, ((pos1 - 1) - pos)).toInt(); |
2249 | int day = t.right(t.length() - pos1 - 1).toInt(); |
2250 | setValue(Value(QDate(year, month, day), sheet()->map()->calculationSettings())); |
2251 | if (value().asDate(sheet()->map()->calculationSettings()).isValid()) // Should always be the case for new docs |
2252 | setUserInput(locale()->formatDate(value().asDate(sheet()->map()->calculationSettings()), KLocale::ShortDate)); |
2253 | else { // This happens with old docs, when format is set wrongly to date |
2254 | parseUserInput(pasteOperation(t, userInput(), op)); |
2255 | } |
2256 | } |
2257 | |
2258 | // time ? |
2259 | else if (dataType == "Time" ) { |
2260 | int hours = -1; |
2261 | int minutes = -1; |
2262 | int second = -1; |
2263 | int pos, pos1; |
2264 | pos = t.indexOf(':'); |
2265 | hours = t.mid(0, pos).toInt(); |
2266 | pos1 = t.indexOf(':', pos + 1); |
2267 | minutes = t.mid(pos + 1, ((pos1 - 1) - pos)).toInt(); |
2268 | second = t.right(t.length() - pos1 - 1).toInt(); |
2269 | setValue(Value(QTime(hours, minutes, second), sheet()->map()->calculationSettings())); |
2270 | if (value().asTime(sheet()->map()->calculationSettings()).isValid()) // Should always be the case for new docs |
2271 | setUserInput(locale()->formatTime(value().asTime(sheet()->map()->calculationSettings()), true)); |
2272 | else { // This happens with old docs, when format is set wrongly to time |
2273 | parseUserInput(pasteOperation(t, userInput(), op)); |
2274 | } |
2275 | } |
2276 | |
2277 | else { |
2278 | // Set the cell's text |
2279 | setUserInput(pasteOperation(t, userInput(), op)); |
2280 | setValue(Value(userInput())); |
2281 | } |
2282 | } |
2283 | } |
2284 | |
2285 | if (!sheet()->isLoading()) |
2286 | parseUserInput(userInput()); |
2287 | |
2288 | return true; |
2289 | } |
2290 | |
2291 | QTime Cell::toTime(const KoXmlElement &element) |
2292 | { |
2293 | //TODO: can't we use tryParseTime (after modification) instead? |
2294 | QString t = element.text(); |
2295 | t = t.trimmed(); |
2296 | int hours = -1; |
2297 | int minutes = -1; |
2298 | int second = -1; |
2299 | int pos, pos1; |
2300 | pos = t.indexOf(':'); |
2301 | hours = t.mid(0, pos).toInt(); |
2302 | pos1 = t.indexOf(':', pos + 1); |
2303 | minutes = t.mid(pos + 1, ((pos1 - 1) - pos)).toInt(); |
2304 | second = t.right(t.length() - pos1 - 1).toInt(); |
2305 | setValue(Value(QTime(hours, minutes, second), sheet()->map()->calculationSettings())); |
2306 | return value().asTime(sheet()->map()->calculationSettings()); |
2307 | } |
2308 | |
2309 | QDate Cell::toDate(const KoXmlElement &element) |
2310 | { |
2311 | QString t = element.text(); |
2312 | int pos; |
2313 | int pos1; |
2314 | int year = -1; |
2315 | int month = -1; |
2316 | int day = -1; |
2317 | pos = t.indexOf('/'); |
2318 | year = t.mid(0, pos).toInt(); |
2319 | pos1 = t.indexOf('/', pos + 1); |
2320 | month = t.mid(pos + 1, ((pos1 - 1) - pos)).toInt(); |
2321 | day = t.right(t.length() - pos1 - 1).toInt(); |
2322 | setValue(Value(QDate(year, month, day), sheet()->map()->calculationSettings())); |
2323 | return value().asDate(sheet()->map()->calculationSettings()); |
2324 | } |
2325 | |
2326 | QString Cell::pasteOperation(const QString &new_text, const QString &old_text, Paste::Operation op) |
2327 | { |
2328 | if (op == Paste::OverWrite) |
2329 | return new_text; |
2330 | |
2331 | QString tmp_op; |
2332 | QString tmp; |
2333 | QString old; |
2334 | |
2335 | if (!new_text.isEmpty() && new_text[0] == '=') { |
2336 | tmp = new_text.right(new_text.length() - 1); |
2337 | } else { |
2338 | tmp = new_text; |
2339 | } |
2340 | |
2341 | if (old_text.isEmpty() && |
2342 | (op == Paste::Add || op == Paste::Mul || op == Paste::Sub || op == Paste::Div)) { |
2343 | old = "=0" ; |
2344 | } |
2345 | |
2346 | if (!old_text.isEmpty() && old_text[0] == '=') { |
2347 | old = old_text.right(old_text.length() - 1); |
2348 | } else { |
2349 | old = old_text; |
2350 | } |
2351 | |
2352 | bool b1, b2; |
2353 | tmp.toDouble(&b1); |
2354 | old.toDouble(&b2); |
2355 | if (b1 && !b2 && old.length() == 0) { |
2356 | old = '0'; |
2357 | b2 = true; |
2358 | } |
2359 | |
2360 | if (b1 && b2) { |
2361 | switch (op) { |
2362 | case Paste::Add: |
2363 | tmp_op = QString::number(old.toDouble() + tmp.toDouble()); |
2364 | break; |
2365 | case Paste::Mul : |
2366 | tmp_op = QString::number(old.toDouble() * tmp.toDouble()); |
2367 | break; |
2368 | case Paste::Sub: |
2369 | tmp_op = QString::number(old.toDouble() - tmp.toDouble()); |
2370 | break; |
2371 | case Paste::Div: |
2372 | tmp_op = QString::number(old.toDouble() / tmp.toDouble()); |
2373 | break; |
2374 | default: |
2375 | Q_ASSERT(0); |
2376 | } |
2377 | |
2378 | return tmp_op; |
2379 | } else if ((new_text[0] == '=' && old_text[0] == '=') || |
2380 | (b1 && old_text[0] == '=') || (new_text[0] == '=' && b2)) { |
2381 | switch (op) { |
2382 | case Paste::Add : |
2383 | tmp_op = "=(" + old + ")+" + '(' + tmp + ')'; |
2384 | break; |
2385 | case Paste::Mul : |
2386 | tmp_op = "=(" + old + ")*" + '(' + tmp + ')'; |
2387 | break; |
2388 | case Paste::Sub: |
2389 | tmp_op = "=(" + old + ")-" + '(' + tmp + ')'; |
2390 | break; |
2391 | case Paste::Div: |
2392 | tmp_op = "=(" + old + ")/" + '(' + tmp + ')'; |
2393 | break; |
2394 | default : |
2395 | Q_ASSERT(0); |
2396 | } |
2397 | |
2398 | tmp_op = decodeFormula(tmp_op); |
2399 | return tmp_op; |
2400 | } |
2401 | |
2402 | tmp = decodeFormula(new_text); |
2403 | return tmp; |
2404 | } |
2405 | |
2406 | Cell& Cell::operator=(const Cell & other) |
2407 | { |
2408 | d = other.d; |
2409 | return *this; |
2410 | } |
2411 | |
2412 | bool Cell::operator<(const Cell& other) const |
2413 | { |
2414 | if (sheet() != other.sheet()) |
2415 | return sheet() < other.sheet(); // pointers! |
2416 | if (row() < other.row()) |
2417 | return true; |
2418 | return ((row() == other.row()) && (column() < other.column())); |
2419 | } |
2420 | |
2421 | bool Cell::operator==(const Cell& other) const |
2422 | { |
2423 | return (row() == other.row() && column() == other.column() && sheet() == other.sheet()); |
2424 | } |
2425 | |
2426 | bool Cell::operator!() const |
2427 | { |
2428 | return (!d); // isNull() |
2429 | } |
2430 | |
2431 | bool Cell::compareData(const Cell& other) const |
2432 | { |
2433 | if (value() != other.value()) |
2434 | return false; |
2435 | if (formula() != other.formula()) |
2436 | return false; |
2437 | if (link() != other.link()) |
2438 | return false; |
2439 | if (mergedXCells() != other.mergedXCells()) |
2440 | return false; |
2441 | if (mergedYCells() != other.mergedYCells()) |
2442 | return false; |
2443 | if (style() != other.style()) |
2444 | return false; |
2445 | if (comment() != other.comment()) |
2446 | return false; |
2447 | if (conditions() != other.conditions()) |
2448 | return false; |
2449 | if (validity() != other.validity()) |
2450 | return false; |
2451 | return true; |
2452 | } |
2453 | |
2454 | QPoint Cell::cellPosition() const |
2455 | { |
2456 | Q_ASSERT(!isNull()); |
2457 | return QPoint(column(), row()); |
2458 | } |
2459 | |