1/* This file is part of the KDE project
2 Copyright 2010 Marijn Kruisselbrink <mkruisselbrink@kde.org>
3 Copyright (C) 1998, 1999 Torben Weis <weis@kde.org>
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Library General Public
7 License as published by the Free Software Foundation; either
8 version 2 of the License, or (at your option) any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Library General Public License for more details.
14
15 You should have received a copy of the GNU Library General Public License
16 along with this library; see the file COPYING.LIB. If not, write to
17 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 Boston, MA 02110-1301, USA.
19*/
20
21// Local
22#include "Map.h"
23
24#include <stdlib.h>
25#include <time.h>
26
27#include <QTimer>
28
29#include <kcodecs.h>
30#include <kcompletion.h>
31#include <ktemporaryfile.h>
32
33#include <KoGenStyles.h>
34#include <KoStyleStack.h>
35#include <KoGlobal.h>
36#include <KoOasisSettings.h>
37#include <KoOdfLoadingContext.h>
38#include <KoOdfStylesReader.h>
39#include <KoEmbeddedDocumentSaver.h>
40#include <KoShapeSavingContext.h>
41#include <KoXmlNS.h>
42#include <KoXmlWriter.h>
43#include <KoStyleManager.h>
44#include <KoShapeLoadingContext.h>
45#include <KoTextSharedLoadingData.h>
46#include <KoParagraphStyle.h>
47#include <KoShapeRegistry.h>
48#include <KoUpdater.h>
49#include <KoProgressUpdater.h>
50
51#include "ApplicationSettings.h"
52#include "BindingManager.h"
53#include "CalculationSettings.h"
54#include "CellStorage.h"
55#include "Damages.h"
56#include "DependencyManager.h"
57#include "DocBase.h"
58#include "LoadingInfo.h"
59#include "Localization.h"
60#include "NamedAreaManager.h"
61#include "OdfLoadingContext.h"
62#include "OdfSavingContext.h"
63#include "RecalcManager.h"
64#include "RowColumnFormat.h"
65#include "Sheet.h"
66#include "StyleManager.h"
67#include "Validity.h"
68#include "ValueCalc.h"
69#include "ValueConverter.h"
70#include "ValueFormatter.h"
71#include "ValueParser.h"
72
73// database
74#include "database/DatabaseManager.h"
75
76using namespace Calligra::Sheets;
77
78class Map::Private
79{
80public:
81 DocBase* doc;
82
83 /**
84 * List of all sheets in this map.
85 */
86 QList<Sheet*> lstSheets;
87 QList<Sheet*> lstDeletedSheets;
88
89 // used to give every Sheet a unique default name.
90 int tableId;
91
92 // used to determine the loading progress
93 int overallRowCount;
94 int loadedRowsCounter;
95
96 LoadingInfo* loadingInfo;
97 bool readwrite;
98
99 BindingManager* bindingManager;
100 DatabaseManager* databaseManager;
101 DependencyManager* dependencyManager;
102 NamedAreaManager* namedAreaManager;
103 RecalcManager* recalcManager;
104 StyleManager* styleManager;
105 KoStyleManager* textStyleManager;
106
107 ApplicationSettings* applicationSettings;
108 CalculationSettings* calculationSettings;
109 ValueCalc* calc;
110 ValueConverter* converter;
111 ValueFormatter* formatter;
112 ValueParser* parser;
113
114 // default objects
115 ColumnFormat* defaultColumnFormat;
116 RowFormat* defaultRowFormat;
117
118 QList<Damage*> damages;
119 bool isLoading;
120
121 int syntaxVersion;
122
123 KCompletion listCompletion;
124};
125
126
127Map::Map(DocBase* doc, int syntaxVersion)
128 : QObject(doc),
129 d(new Private)
130{
131 setObjectName(QLatin1String("Map")); // necessary for D-Bus
132 d->doc = doc;
133 d->tableId = 1;
134 d->overallRowCount = 0;
135 d->loadedRowsCounter = 0;
136 d->loadingInfo = 0;
137 d->readwrite = true;
138
139 d->bindingManager = new BindingManager(this);
140 d->databaseManager = new DatabaseManager(this);
141 d->dependencyManager = new DependencyManager(this);
142 d->namedAreaManager = new NamedAreaManager(this);
143 d->recalcManager = new RecalcManager(this);
144 d->styleManager = new StyleManager();
145 d->textStyleManager = new KoStyleManager(this);
146 if (doc) {
147 d->textStyleManager->setUndoStack(doc->undoStack());
148 }
149 d->applicationSettings = new ApplicationSettings();
150 d->calculationSettings = new CalculationSettings();
151
152 d->parser = new ValueParser(d->calculationSettings);
153 d->converter = new ValueConverter(d->parser);
154 d->calc = new ValueCalc(d->converter);
155 d->formatter = new ValueFormatter(d->converter);
156
157 d->defaultColumnFormat = new ColumnFormat();
158 d->defaultRowFormat = new RowFormat();
159
160 QFont font(KoGlobal::defaultFont());
161 d->defaultRowFormat->setHeight(font.pointSizeF() + 4);
162 d->defaultColumnFormat->setWidth((font.pointSizeF() + 4) * 5);
163
164 d->isLoading = false;
165
166 // default document properties
167 d->syntaxVersion = syntaxVersion;
168
169 connect(this, SIGNAL(sheetAdded(Sheet*)),
170 d->dependencyManager, SLOT(addSheet(Sheet*)));
171 connect(this, SIGNAL(sheetAdded(Sheet*)),
172 d->recalcManager, SLOT(addSheet(Sheet*)));
173 connect(this, SIGNAL(sheetRemoved(Sheet*)),
174 d->dependencyManager, SLOT(removeSheet(Sheet*)));
175 connect(this, SIGNAL(sheetRemoved(Sheet*)),
176 d->recalcManager, SLOT(removeSheet(Sheet*)));
177 connect(this, SIGNAL(sheetRevived(Sheet*)),
178 d->dependencyManager, SLOT(addSheet(Sheet*)));
179 connect(this, SIGNAL(sheetRevived(Sheet*)),
180 d->recalcManager, SLOT(addSheet(Sheet*)));
181 connect(d->namedAreaManager, SIGNAL(namedAreaModified(QString)),
182 d->dependencyManager, SLOT(namedAreaModified(QString)));
183 connect(this, SIGNAL(damagesFlushed(QList<Damage*>)),
184 this, SLOT(handleDamages(QList<Damage*>)));
185}
186
187Map::~Map()
188{
189 // Because some of the shapes might be using a sheet in this map, delete
190 // all shapes in each sheet before all sheets are deleted together.
191 foreach(Sheet *sheet, d->lstSheets)
192 sheet->deleteShapes();
193 // we have to explicitly delete the Sheets, not let QObject take care of that
194 // as the sheet in its destructor expects the Map to still exist
195 qDeleteAll(d->lstSheets);
196 d->lstSheets.clear();
197
198 deleteLoadingInfo();
199
200 delete d->bindingManager;
201 delete d->databaseManager;
202 delete d->dependencyManager;
203 delete d->namedAreaManager;
204 delete d->recalcManager;
205 delete d->styleManager;
206
207 delete d->parser;
208 delete d->formatter;
209 delete d->converter;
210 delete d->calc;
211 delete d->calculationSettings;
212 delete d->applicationSettings;
213
214 delete d->defaultColumnFormat;
215 delete d->defaultRowFormat;
216
217 delete d;
218}
219
220DocBase* Map::doc() const
221{
222 return d->doc;
223}
224
225void Map::setReadWrite(bool readwrite)
226{
227 d->readwrite = readwrite;
228}
229
230bool Map::isReadWrite() const
231{
232 return d->readwrite;
233}
234
235bool Map::completeLoading(KoStore *store)
236{
237 Q_UNUSED(store);
238
239 QPointer<KoUpdater> dependencyUpdater, recalcUpdater;
240 if (doc() && doc()->progressUpdater()) {
241 dependencyUpdater = doc()->progressUpdater()->startSubtask(1, "Calligra::Sheets::DependencyManager::updateAllDependencies");
242 recalcUpdater = doc()->progressUpdater()->startSubtask(1, "Calligra::Sheets::RecalcManager::recalc");
243 }
244
245 // Initial build of all cell dependencies.
246 d->dependencyManager->updateAllDependencies(this, dependencyUpdater);
247 // Recalc the whole workbook now, since there may be formulas other spreadsheets support,
248 // but KSpread does not.
249 d->recalcManager->recalcMap(recalcUpdater);
250
251 return true;
252}
253
254bool Map::completeSaving(KoStore *store, KoXmlWriter *manifestWriter, KoShapeSavingContext * context)
255{
256 Q_UNUSED(store);
257 Q_UNUSED(manifestWriter);
258 Q_UNUSED(context);
259 return true;
260}
261
262BindingManager* Map::bindingManager() const
263{
264 return d->bindingManager;
265}
266
267DatabaseManager* Map::databaseManager() const
268{
269 return d->databaseManager;
270}
271
272DependencyManager* Map::dependencyManager() const
273{
274 return d->dependencyManager;
275}
276
277NamedAreaManager* Map::namedAreaManager() const
278{
279 return d->namedAreaManager;
280}
281
282RecalcManager* Map::recalcManager() const
283{
284 return d->recalcManager;
285}
286
287StyleManager* Map::styleManager() const
288{
289 return d->styleManager;
290}
291
292KoStyleManager* Map::textStyleManager() const
293{
294 return d->textStyleManager;
295}
296
297ValueParser* Map::parser() const
298{
299 return d->parser;
300}
301
302ValueFormatter* Map::formatter() const
303{
304 return d->formatter;
305}
306
307ValueConverter* Map::converter() const
308{
309 return d->converter;
310}
311
312ValueCalc* Map::calc() const
313{
314 return d->calc;
315}
316
317const ColumnFormat* Map::defaultColumnFormat() const
318{
319 return d->defaultColumnFormat;
320}
321
322const RowFormat* Map::defaultRowFormat() const
323{
324 return d->defaultRowFormat;
325}
326
327void Map::setDefaultColumnWidth(double width)
328{
329 d->defaultColumnFormat->setWidth(width);
330}
331
332void Map::setDefaultRowHeight(double height)
333{
334 d->defaultRowFormat->setHeight(height);
335}
336
337ApplicationSettings* Map::settings() const
338{
339 return d->applicationSettings;
340}
341
342CalculationSettings* Map::calculationSettings() const
343{
344 return d->calculationSettings;
345}
346
347Sheet* Map::createSheet(const QString& name)
348{
349 QString sheetName(i18n("Sheet%1", d->tableId++));
350 if ( !name.isEmpty() )
351 sheetName = name;
352 Sheet* sheet = new Sheet(this, sheetName);
353 connect(sheet, SIGNAL(statusMessage(QString,int)),
354 this, SIGNAL(statusMessage(QString,int)));
355 return sheet;
356}
357
358void Map::addSheet(Sheet *_sheet)
359{
360 d->lstSheets.append(_sheet);
361 emit sheetAdded(_sheet);
362}
363
364Sheet *Map::addNewSheet(const QString& name)
365{
366 Sheet *t = createSheet(name);
367 addSheet(t);
368 return t;
369}
370
371void Map::moveSheet(const QString & _from, const QString & _to, bool _before)
372{
373 Sheet* sheetfrom = findSheet(_from);
374 Sheet* sheetto = findSheet(_to);
375
376 int from = d->lstSheets.indexOf(sheetfrom) ;
377 int to = d->lstSheets.indexOf(sheetto) ;
378 if (!_before)
379 ++to;
380
381 if (to > (int)d->lstSheets.count()) {
382 d->lstSheets.append(sheetfrom);
383 d->lstSheets.removeAt(from);
384 } else if (from < to) {
385 d->lstSheets.insert(to, sheetfrom);
386 d->lstSheets.removeAt(from);
387 } else {
388 d->lstSheets.removeAt(from);
389 d->lstSheets.insert(to, sheetfrom);
390 }
391}
392
393void Map::loadOdfSettings(KoOasisSettings &settings)
394{
395 KoOasisSettings::Items viewSettings = settings.itemSet("view-settings");
396 KoOasisSettings::IndexedMap viewMap = viewSettings.indexedMap("Views");
397 KoOasisSettings::Items firstView = viewMap.entry(0);
398
399 KoOasisSettings::NamedMap sheetsMap = firstView.namedMap("Tables");
400 kDebug() << " loadOdfSettings( KoOasisSettings &settings ) exist :" << !sheetsMap.isNull();
401 if (!sheetsMap.isNull()) {
402 foreach(Sheet* sheet, d->lstSheets) {
403 sheet->loadOdfSettings(sheetsMap);
404 }
405 }
406
407 QString activeSheet = firstView.parseConfigItemString("ActiveTable");
408 kDebug() << " loadOdfSettings( KoOasisSettings &settings ) activeSheet :" << activeSheet;
409
410 if (!activeSheet.isEmpty()) {
411 // Used by View's constructor
412 loadingInfo()->setInitialActiveSheet(findSheet(activeSheet));
413 }
414}
415
416bool Map::saveOdf(KoXmlWriter & xmlWriter, KoShapeSavingContext & savingContext)
417{
418 // Saving the custom cell styles including the default cell style.
419 d->styleManager->saveOdf(savingContext.mainStyles());
420
421 // Saving the default column style
422 KoGenStyle defaultColumnStyle(KoGenStyle::TableColumnStyle, "table-column");
423 defaultColumnStyle.addPropertyPt("style:column-width", d->defaultColumnFormat->width());
424 defaultColumnStyle.setDefaultStyle(true);
425 savingContext.mainStyles().insert(defaultColumnStyle, "Default", KoGenStyles::DontAddNumberToName);
426
427 // Saving the default row style
428 KoGenStyle defaultRowStyle(KoGenStyle::TableRowStyle, "table-row");
429 defaultRowStyle.addPropertyPt("style:row-height", d->defaultRowFormat->height());
430 defaultRowStyle.setDefaultStyle(true);
431 savingContext.mainStyles().insert(defaultRowStyle, "Default", KoGenStyles::DontAddNumberToName);
432
433 d->calculationSettings->saveOdf(xmlWriter); // table::calculation-settings
434
435 QByteArray password;
436 this->password(password);
437 if (!password.isNull()) {
438 xmlWriter.addAttribute("table:structure-protected", "true");
439 QByteArray str = KCodecs::base64Encode(password);
440 // FIXME Stefan: see OpenDocument spec, ch. 17.3 Encryption
441 xmlWriter.addAttribute("table:protection-key", QString(str.data()));
442 }
443
444 OdfSavingContext tableContext(savingContext);
445
446 foreach(Sheet* sheet, d->lstSheets) {
447 sheet->saveOdf(tableContext);
448 }
449
450 tableContext.valStyle.writeStyle(xmlWriter);
451
452 d->namedAreaManager->saveOdf(savingContext.xmlWriter());
453 d->databaseManager->saveOdf(savingContext.xmlWriter());
454 return true;
455}
456
457QDomElement Map::save(QDomDocument& doc)
458{
459 QDomElement spread = doc.documentElement();
460
461 QDomElement locale = static_cast<Localization*>(d->calculationSettings->locale())->save(doc);
462 spread.appendChild(locale);
463
464 QDomElement areaname = d->namedAreaManager->saveXML(doc);
465 spread.appendChild(areaname);
466
467 QDomElement defaults = doc.createElement("defaults");
468 defaults.setAttribute("row-height", d->defaultRowFormat->height());
469 defaults.setAttribute("col-width", d->defaultColumnFormat->width());
470 spread.appendChild(defaults);
471
472 QDomElement s = d->styleManager->save(doc);
473 spread.appendChild(s);
474
475 QDomElement mymap = doc.createElement("map");
476
477 QByteArray password;
478 this->password(password);
479 if (!password.isNull()) {
480 if (password.size() > 0) {
481 QByteArray str = KCodecs::base64Encode(password);
482 mymap.setAttribute("protected", QString(str.data()));
483 } else {
484 mymap.setAttribute("protected", "");
485 }
486 }
487
488 foreach(Sheet* sheet, d->lstSheets) {
489 QDomElement e = sheet->saveXML(doc);
490 if (e.isNull())
491 return e;
492 mymap.appendChild(e);
493 }
494 return mymap;
495}
496
497static void fixupStyle(KoCharacterStyle* style)
498{
499 style->removeHardCodedDefaults();
500
501 QTextCharFormat format;
502 style->applyStyle(format);
503 switch (style->underlineStyle()) {
504 case KoCharacterStyle::NoLineStyle:
505 format.setUnderlineStyle(QTextCharFormat::NoUnderline); break;
506 case KoCharacterStyle::SolidLine:
507 format.setUnderlineStyle(QTextCharFormat::SingleUnderline); break;
508 case KoCharacterStyle::DottedLine:
509 format.setUnderlineStyle(QTextCharFormat::DotLine); break;
510 case KoCharacterStyle::DashLine:
511 format.setUnderlineStyle(QTextCharFormat::DashUnderline); break;
512 case KoCharacterStyle::DotDashLine:
513 format.setUnderlineStyle(QTextCharFormat::DashDotLine); break;
514 case KoCharacterStyle::DotDotDashLine:
515 format.setUnderlineStyle(QTextCharFormat::DashDotDotLine); break;
516 case KoCharacterStyle::LongDashLine:
517 format.setUnderlineStyle(QTextCharFormat::DashUnderline); break;
518 case KoCharacterStyle::WaveLine:
519 format.setUnderlineStyle(QTextCharFormat::WaveUnderline); break;
520 }
521 style->copyProperties(format);
522}
523
524bool Map::loadOdf(const KoXmlElement& body, KoOdfLoadingContext& odfContext)
525{
526 d->isLoading = true;
527 loadingInfo()->setFileFormat(LoadingInfo::OpenDocument);
528
529 //load in first
530 d->styleManager->loadOdfStyleTemplate(odfContext.stylesReader(), this);
531
532 OdfLoadingContext tableContext(odfContext);
533 tableContext.validities = Validity::preloadValidities(body); // table:content-validations
534
535 // load text styles for rich-text content and TOS
536 KoShapeLoadingContext shapeContext(tableContext.odfContext, resourceManager());
537 tableContext.shapeContext = &shapeContext;
538 KoTextSharedLoadingData * sharedData = new KoTextSharedLoadingData();
539 sharedData->loadOdfStyles(shapeContext, textStyleManager());
540
541 fixupStyle((KoCharacterStyle*)textStyleManager()->defaultParagraphStyle());
542 foreach (KoCharacterStyle* style, sharedData->characterStyles(true)) {
543 fixupStyle(style);
544 }
545 foreach (KoCharacterStyle* style, sharedData->characterStyles(false)) {
546 fixupStyle(style);
547 }
548 shapeContext.addSharedData(KOTEXT_SHARED_LOADING_ID, sharedData);
549
550 QVariant variant;
551 variant.setValue(textStyleManager());
552 resourceManager()->setResource(KoText::StyleManager, variant);
553
554
555 // load default column style
556 const KoXmlElement* defaultColumnStyle = odfContext.stylesReader().defaultStyle("table-column");
557 if (defaultColumnStyle) {
558// kDebug() <<"style:default-style style:family=\"table-column\"";
559 KoStyleStack styleStack;
560 styleStack.push(*defaultColumnStyle);
561 styleStack.setTypeProperties("table-column");
562 if (styleStack.hasProperty(KoXmlNS::style, "column-width")) {
563 const double width = KoUnit::parseValue(styleStack.property(KoXmlNS::style, "column-width"), -1.0);
564 if (width != -1.0) {
565// kDebug() <<"\tstyle:column-width:" << width;
566 d->defaultColumnFormat->setWidth(width);
567 }
568 }
569 }
570
571 // load default row style
572 const KoXmlElement* defaultRowStyle = odfContext.stylesReader().defaultStyle("table-row");
573 if (defaultRowStyle) {
574// kDebug() <<"style:default-style style:family=\"table-row\"";
575 KoStyleStack styleStack;
576 styleStack.push(*defaultRowStyle);
577 styleStack.setTypeProperties("table-row");
578 if (styleStack.hasProperty(KoXmlNS::style, "row-height")) {
579 const double height = KoUnit::parseValue(styleStack.property(KoXmlNS::style, "row-height"), -1.0);
580 if (height != -1.0) {
581// kDebug() <<"\tstyle:row-height:" << height;
582 d->defaultRowFormat->setHeight(height);
583 }
584 }
585 }
586
587 d->calculationSettings->loadOdf(body); // table::calculation-settings
588 if (body.hasAttributeNS(KoXmlNS::table, "structure-protected")) {
589 loadOdfProtection(body);
590 }
591
592 KoXmlNode sheetNode = KoXml::namedItemNS(body, KoXmlNS::table, "table");
593
594 if (sheetNode.isNull()) {
595 // We need at least one sheet !
596 doc()->setErrorMessage(i18n("This document has no sheets (tables)."));
597 d->isLoading = false;
598 return false;
599 }
600
601 d->overallRowCount = 0;
602 while (!sheetNode.isNull()) {
603 KoXmlElement sheetElement = sheetNode.toElement();
604 if (!sheetElement.isNull()) {
605 //kDebug()<<" Map::loadOdf tableElement is not null";
606 //kDebug()<<"tableElement.nodeName() :"<<sheetElement.nodeName();
607
608 // make it slightly faster
609 KoXml::load(sheetElement);
610
611 if (sheetElement.nodeName() == "table:table") {
612 if (!sheetElement.attributeNS(KoXmlNS::table, "name", QString()).isEmpty()) {
613 const QString sheetName = sheetElement.attributeNS(KoXmlNS::table, "name", QString());
614 Sheet* sheet = addNewSheet(sheetName);
615 sheet->setSheetName(sheetName, true);
616 d->overallRowCount += KoXml::childNodesCount(sheetElement);
617 }
618 }
619 }
620
621 // reduce memory usage
622 KoXml::unload(sheetElement);
623 sheetNode = sheetNode.nextSibling();
624 }
625
626 //pre-load auto styles
627 QHash<QString, Conditions> conditionalStyles;
628 Styles autoStyles = d->styleManager->loadOdfAutoStyles(odfContext.stylesReader(),
629 conditionalStyles, parser());
630
631 // load the sheet
632 sheetNode = body.firstChild();
633 while (!sheetNode.isNull()) {
634 KoXmlElement sheetElement = sheetNode.toElement();
635 if (!sheetElement.isNull()) {
636 // make it slightly faster
637 KoXml::load(sheetElement);
638
639 //kDebug()<<"tableElement.nodeName() bis :"<<sheetElement.nodeName();
640 if (sheetElement.nodeName() == "table:table") {
641 if (!sheetElement.attributeNS(KoXmlNS::table, "name", QString()).isEmpty()) {
642 QString name = sheetElement.attributeNS(KoXmlNS::table, "name", QString());
643 Sheet* sheet = findSheet(name);
644 if (sheet) {
645 sheet->loadOdf(sheetElement, tableContext, autoStyles, conditionalStyles);
646 }
647 }
648 }
649 }
650
651 // reduce memory usage
652 KoXml::unload(sheetElement);
653 sheetNode = sheetNode.nextSibling();
654 }
655
656 // make sure always at least one sheet exists
657 if (count() == 0) {
658 addNewSheet();
659 }
660
661 //delete any styles which were not used
662 d->styleManager->releaseUnusedAutoStyles(autoStyles);
663
664 // Load databases. This needs the sheets to be loaded.
665 d->databaseManager->loadOdf(body); // table:database-ranges
666 d->namedAreaManager->loadOdf(body); // table:named-expressions
667
668 d->isLoading = false;
669 return true;
670}
671
672
673bool Map::loadXML(const KoXmlElement& mymap)
674{
675 d->isLoading = true;
676 loadingInfo()->setFileFormat(LoadingInfo::NativeFormat);
677 const QString activeSheet = mymap.attribute("activeTable");
678 const QPoint marker(mymap.attribute("markerColumn").toInt(), mymap.attribute("markerRow").toInt());
679 loadingInfo()->setCursorPosition(findSheet(activeSheet), marker);
680 const QPointF offset(mymap.attribute("xOffset").toDouble(), mymap.attribute("yOffset").toDouble());
681 loadingInfo()->setScrollingOffset(findSheet(activeSheet), offset);
682
683 KoXmlNode n = mymap.firstChild();
684 if (n.isNull()) {
685 // We need at least one sheet !
686 doc()->setErrorMessage(i18n("This document has no sheets (tables)."));
687 d->isLoading = false;
688 return false;
689 }
690 while (!n.isNull()) {
691 KoXmlElement e = n.toElement();
692 if (!e.isNull() && e.tagName() == "table") {
693 Sheet *t = addNewSheet();
694 if (!t->loadXML(e)) {
695 d->isLoading = false;
696 return false;
697 }
698 }
699 n = n.nextSibling();
700 }
701
702 loadXmlProtection(mymap);
703
704 if (!activeSheet.isEmpty()) {
705 // Used by View's constructor
706 loadingInfo()->setInitialActiveSheet(findSheet(activeSheet));
707 }
708
709 d->isLoading = false;
710 return true;
711}
712
713Sheet* Map::findSheet(const QString & _name) const
714{
715 foreach(Sheet* sheet, d->lstSheets) {
716 if (_name.toLower() == sheet->sheetName().toLower())
717 return sheet;
718 }
719 return 0;
720}
721
722Sheet * Map::nextSheet(Sheet * currentSheet) const
723{
724 if (currentSheet == d->lstSheets.last())
725 return currentSheet;
726 int index = 0;
727 foreach(Sheet* sheet, d->lstSheets) {
728 if (sheet == currentSheet)
729 return d->lstSheets.value(++index);
730 ++index;
731 }
732 return 0;
733}
734
735Sheet * Map::previousSheet(Sheet * currentSheet) const
736{
737 if (currentSheet == d->lstSheets.first())
738 return currentSheet;
739 int index = 0;
740 foreach(Sheet* sheet, d->lstSheets) {
741 if (sheet == currentSheet)
742 return d->lstSheets.value(--index);
743 ++index;
744 }
745 return 0;
746}
747
748bool Map::loadChildren(KoStore * _store)
749{
750 foreach(Sheet* sheet, d->lstSheets) {
751 if (!sheet->loadChildren(_store))
752 return false;
753 }
754 return true;
755}
756
757void Map::removeSheet(Sheet* sheet)
758{
759 d->lstSheets.removeAll(sheet);
760 d->lstDeletedSheets.append(sheet);
761 d->namedAreaManager->remove(sheet);
762 emit sheetRemoved(sheet);
763}
764
765void Map::reviveSheet(Sheet* sheet)
766{
767 d->lstDeletedSheets.removeAll(sheet);
768 d->lstSheets.append(sheet);
769 emit sheetRevived(sheet);
770}
771
772// FIXME cache this for faster operation
773QStringList Map::visibleSheets() const
774{
775 QStringList result;
776 foreach(Sheet* sheet, d->lstSheets) {
777 if (!sheet->isHidden())
778 result.append(sheet->sheetName());
779 }
780 return result;
781}
782
783// FIXME cache this for faster operation
784QStringList Map::hiddenSheets() const
785{
786 QStringList result;
787 foreach(Sheet* sheet, d->lstSheets) {
788 if (sheet->isHidden())
789 result.append(sheet->sheetName());
790 }
791 return result;
792}
793
794Sheet* Map::sheet(int index) const
795{
796 return d->lstSheets.value(index);
797}
798
799int Map::indexOf(Sheet* sheet) const
800{
801 return d->lstSheets.indexOf(sheet);
802}
803
804QList<Sheet*>& Map::sheetList() const
805{
806 return d->lstSheets;
807}
808
809int Map::count() const
810{
811 return d->lstSheets.count();
812}
813
814int Map::increaseLoadedRowsCounter(int number)
815{
816 d->loadedRowsCounter += number;
817 if (d->overallRowCount) {
818 return 100 * d->loadedRowsCounter / d->overallRowCount;
819 }
820 return -1;
821}
822
823bool Map::isLoading() const
824{
825 // The KoDocument state is necessary to avoid damages while importing a file (through a filter).
826 return d->isLoading || (d->doc && d->doc->isLoading());
827}
828
829int Map::syntaxVersion() const
830{
831 return d->syntaxVersion;
832}
833
834void Map::setSyntaxVersion(int version)
835{
836 d->syntaxVersion = version;
837}
838
839LoadingInfo* Map::loadingInfo() const
840{
841 if (!d->loadingInfo) {
842 d->loadingInfo = new LoadingInfo();
843 }
844 return d->loadingInfo;
845}
846
847void Map::deleteLoadingInfo()
848{
849 delete d->loadingInfo;
850 d->loadingInfo = 0;
851}
852
853KCompletion& Map::stringCompletion()
854{
855 return d->listCompletion;
856}
857
858void Map::addStringCompletion(const QString &stringCompletion)
859{
860 if (d->listCompletion.items().contains(stringCompletion) == 0) {
861 d->listCompletion.addItem(stringCompletion);
862 }
863}
864
865void Map::addDamage(Damage* damage)
866{
867 // Do not create a new Damage, if we are in loading process. Check for it before
868 // calling this function. This prevents unnecessary memory allocations (new).
869 // see FIXME in Sheet::setSheetName().
870// Q_ASSERT(!isLoading());
871 Q_CHECK_PTR(damage);
872
873#ifndef NDEBUG
874 if (damage->type() == Damage::Cell) {
875 kDebug(36007) << "Adding\t" << *static_cast<CellDamage*>(damage);
876 } else if (damage->type() == Damage::Sheet) {
877 kDebug(36007) << "Adding\t" << *static_cast<SheetDamage*>(damage);
878 } else if (damage->type() == Damage::Selection) {
879 kDebug(36007) << "Adding\t" << *static_cast<SelectionDamage*>(damage);
880 } else {
881 kDebug(36007) << "Adding\t" << *damage;
882 }
883#endif
884
885 d->damages.append(damage);
886
887 if (d->damages.count() == 1) {
888 QTimer::singleShot(0, this, SLOT(flushDamages()));
889 }
890}
891
892void Map::flushDamages()
893{
894 // Copy the damages to process. This allows new damages while processing.
895 QList<Damage*> damages = d->damages;
896 d->damages.clear();
897 emit damagesFlushed(damages);
898 qDeleteAll(damages);
899}
900
901void Map::handleDamages(const QList<Damage*>& damages)
902{
903 Region bindingChangedRegion;
904 Region formulaChangedRegion;
905 Region namedAreaChangedRegion;
906 Region valueChangedRegion;
907 WorkbookDamage::Changes workbookChanges = WorkbookDamage::None;
908
909 QList<Damage*>::ConstIterator end(damages.end());
910 for (QList<Damage*>::ConstIterator it = damages.begin(); it != end; ++it) {
911 Damage* damage = *it;
912
913 if (damage->type() == Damage::Cell) {
914 CellDamage* cellDamage = static_cast<CellDamage*>(damage);
915 kDebug(36007) << "Processing\t" << *cellDamage;
916 Sheet* const damagedSheet = cellDamage->sheet();
917 const Region& region = cellDamage->region();
918 const CellDamage::Changes changes = cellDamage->changes();
919
920 // TODO Stefan: Detach the style cache from the CellView cache.
921 if ((changes.testFlag(CellDamage::Appearance))) {
922 // Rebuild the style storage cache.
923 damagedSheet->cellStorage()->invalidateStyleCache(); // FIXME more fine-grained
924 }
925 if ((cellDamage->changes() & CellDamage::Binding) &&
926 !workbookChanges.testFlag(WorkbookDamage::Value)) {
927 bindingChangedRegion.add(region, damagedSheet);
928 }
929 if ((cellDamage->changes() & CellDamage::Formula) &&
930 !workbookChanges.testFlag(WorkbookDamage::Formula)) {
931 formulaChangedRegion.add(region, damagedSheet);
932 }
933 if ((cellDamage->changes() & CellDamage::NamedArea) &&
934 !workbookChanges.testFlag(WorkbookDamage::Formula)) {
935 namedAreaChangedRegion.add(region, damagedSheet);
936 }
937 if ((cellDamage->changes() & CellDamage::Value) &&
938 !workbookChanges.testFlag(WorkbookDamage::Value)) {
939 valueChangedRegion.add(region, damagedSheet);
940 }
941 continue;
942 }
943
944 if (damage->type() == Damage::Sheet) {
945 SheetDamage* sheetDamage = static_cast<SheetDamage*>(damage);
946 kDebug(36007) << "Processing\t" << *sheetDamage;
947// Sheet* damagedSheet = sheetDamage->sheet();
948
949 if (sheetDamage->changes() & SheetDamage::PropertiesChanged) {
950 }
951 continue;
952 }
953
954 if (damage->type() == Damage::Workbook) {
955 WorkbookDamage* workbookDamage = static_cast<WorkbookDamage*>(damage);
956 kDebug(36007) << "Processing\t" << *damage;
957
958 workbookChanges |= workbookDamage->changes();
959 if (workbookDamage->changes() & WorkbookDamage::Formula) {
960 formulaChangedRegion.clear();
961 }
962 if (workbookDamage->changes() & WorkbookDamage::Value) {
963 valueChangedRegion.clear();
964 }
965 continue;
966 }
967// kDebug(36007) <<"Unhandled\t" << *damage;
968 }
969
970 // Update the named areas.
971 if (!namedAreaChangedRegion.isEmpty()) {
972 d->namedAreaManager->regionChanged(namedAreaChangedRegion);
973 }
974 // First, update the dependencies.
975 if (!formulaChangedRegion.isEmpty()) {
976 d->dependencyManager->regionChanged(formulaChangedRegion);
977 }
978 // Tell the RecalcManager which cells have had a value change.
979 if (!valueChangedRegion.isEmpty()) {
980 d->recalcManager->regionChanged(valueChangedRegion);
981 }
982 if (workbookChanges.testFlag(WorkbookDamage::Formula)) {
983 d->namedAreaManager->updateAllNamedAreas();
984 d->dependencyManager->updateAllDependencies(this);
985 }
986 if (workbookChanges.testFlag(WorkbookDamage::Value)) {
987 d->recalcManager->recalcMap();
988 d->bindingManager->updateAllBindings();
989 }
990 // Update the bindings
991 if (!bindingChangedRegion.isEmpty()) {
992 d->bindingManager->regionChanged(bindingChangedRegion);
993 }
994}
995
996void Map::addCommand(KUndo2Command *command)
997{
998 emit commandAdded(command);
999}
1000
1001KoDocumentResourceManager* Map::resourceManager() const
1002{
1003 if (!doc()) return 0;
1004 return doc()->resourceManager();
1005}
1006
1007#include "Map.moc"
1008