File indexing completed on 2024-05-12 16:35:44

0001 /* This file is part of the KDE project
0002    Copyright 2007 Stefan Nikolaus <stefan.nikolaus@kdemail.net>
0003    Copyright 2007 Thorsten Zachmann <zachmann@kde.org>
0004    Copyright 2005-2006 Inge Wallin <inge@lysator.liu.se>
0005    Copyright 2004 Ariya Hidayat <ariya@kde.org>
0006    Copyright 2002-2003 Norbert Andres <nandres@web.de>
0007    Copyright 2000-2002 Laurent Montel <montel@kde.org>
0008    Copyright 2002 John Dailey <dailey@vt.edu>
0009    Copyright 2002 Phillip Mueller <philipp.mueller@gmx.de>
0010    Copyright 2000 Werner Trobin <trobin@kde.org>
0011    Copyright 1999-2000 Simon Hausmann <hausmann@kde.org>
0012    Copyright 1999 David Faure <faure@kde.org>
0013    Copyright 1998-2000 Torben Weis <weis@kde.org>
0014 
0015    This library is free software; you can redistribute it and/or
0016    modify it under the terms of the GNU Library General Public
0017    License as published by the Free Software Foundation; either
0018    version 2 of the License, or (at your option) any later version.
0019 
0020    This library is distributed in the hope that it will be useful,
0021    but WITHOUT ANY WARRANTY; without even the implied warranty of
0022    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0023    Library General Public License for more details.
0024 
0025    You should have received a copy of the GNU Library General Public License
0026    along with this library; see the file COPYING.LIB.  If not, write to
0027    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0028    Boston, MA 02110-1301, USA.
0029 */
0030 
0031 // Local
0032 #include "Doc.h"
0033 #include "../DocBase_p.h"
0034 
0035 #include <unistd.h>
0036 #include <assert.h>
0037 #include <sys/types.h>
0038 #include <sys/stat.h>
0039 #include <dirent.h>
0040 #include <pwd.h>
0041 
0042 #include <QApplication>
0043 #include <QFont>
0044 #include <QTimer>
0045 #include <QList>
0046 #include <QPainter>
0047 #include <QGraphicsItem>
0048 
0049 #include <kconfig.h>
0050 #include <kconfiggroup.h>
0051 #include <kmessagebox.h>
0052 
0053 #include <KoComponentData.h>
0054 #include <KoDocumentInfo.h>
0055 #include <KoMainWindow.h>
0056 #include <KoOasisSettings.h>
0057 #include <KoDocumentResourceManager.h>
0058 #include <KoShapeConfigFactoryBase.h>
0059 #include <KoShapeFactoryBase.h>
0060 #include <KoShapeRegistry.h>
0061 #include <KoXmlNS.h>
0062 #include <KoXmlWriter.h>
0063 #include <KoZoomHandler.h>
0064 #include <KoShapeSavingContext.h>
0065 #include <KoUpdater.h>
0066 #include <KoProgressUpdater.h>
0067 #include <KoView.h>
0068 #include <KoUnit.h>
0069 
0070 #include "SheetsDebug.h"
0071 #include "BindingManager.h"
0072 #include "CalculationSettings.h"
0073 #include "Canvas.h"
0074 #include "CanvasItem.h"
0075 #include "DependencyManager.h"
0076 #include "Factory.h"
0077 #include "Formula.h"
0078 #include "Function.h"
0079 #include "FunctionModuleRegistry.h"
0080 #include "HeaderFooter.h"
0081 #include "LoadingInfo.h"
0082 #include "Localization.h"
0083 #include "Map.h"
0084 #include "NamedAreaManager.h"
0085 #include "PrintSettings.h"
0086 #include "RecalcManager.h"
0087 #include "Sheet.h"
0088 #include "SheetPrint.h"
0089 #include "StyleManager.h"
0090 #include "Util.h"
0091 #include "View.h"
0092 #include "SheetAccessModel.h"
0093 #include "BindingModel.h"
0094 
0095 // D-Bus
0096 #ifndef QT_NO_DBUS
0097 #include "interfaces/MapAdaptor.h"
0098 #include "interfaces/SheetAdaptor.h"
0099 #include <QDBusConnection>
0100 #endif
0101 
0102 // chart shape
0103 #include "plugins/chartshape/ChartShape.h"
0104 #include "chart/ChartDialog.h"
0105 
0106 // ui
0107 #include "ui/Selection.h"
0108 #include "ui/SheetView.h"
0109 
0110 using namespace std;
0111 using namespace Calligra::Sheets;
0112 
0113 class Q_DECL_HIDDEN Doc::Private
0114 {
0115 public:
0116     Map *map;
0117     static QList<Doc*> s_docs;
0118     static int s_docId;
0119 
0120     // document properties
0121     bool configLoadFromFile       : 1;
0122     QStringList spellListIgnoreAll;
0123     SavedDocParts savedDocParts;
0124     SheetAccessModel *sheetAccessModel;
0125     KoDocumentResourceManager *resourceManager;
0126 };
0127 
0128 // Make sure an appropriate DTD is available in www/calligra/DTD if changing this value
0129 static const char CURRENT_DTD_VERSION[] = "1.2";
0130 
0131 /*****************************************************************************
0132  *
0133  * Doc
0134  *
0135  *****************************************************************************/
0136 
0137 QList<Doc*> Doc::Private::s_docs;
0138 int Doc::Private::s_docId = 0;
0139 
0140 Doc::Doc(KoPart *part)
0141         : DocBase(part)
0142         , dd(new Private)
0143 {
0144     Q_ASSERT(part);
0145     connect(d->map, SIGNAL(sheetAdded(Sheet*)), this, SLOT(sheetAdded(Sheet*)));
0146 
0147 #ifndef QT_NO_DBUS
0148     new MapAdaptor(d->map);
0149     QDBusConnection::sessionBus().registerObject('/' + objectName() + '/' + d->map->objectName(), d->map);
0150 #endif
0151 
0152     // Init chart shape factory with Calligra Sheets' specific configuration panels.
0153     KoShapeFactoryBase *chartShape = KoShapeRegistry::instance()->value(ChartShapeId);
0154     if (chartShape) {
0155         QList<KoShapeConfigFactoryBase*> panels = ChartDialog::panels(d->map);
0156         chartShape->setOptionPanels(panels);
0157     } else {
0158         warnSheets << "chart shape factory not found";
0159     }
0160 
0161     connect(d->map, SIGNAL(commandAdded(KUndo2Command*)),
0162             this, SLOT(addCommand(KUndo2Command*)));
0163 
0164     // Load the function modules.
0165     FunctionModuleRegistry::instance()->loadFunctionModules();
0166 }
0167 
0168 Doc::~Doc()
0169 {
0170     //don't save config when words is embedded into konqueror
0171     saveConfig();
0172 
0173     delete dd;
0174 }
0175 
0176 void Doc::initEmpty()
0177 {
0178     KSharedConfigPtr config = Factory::global().config();
0179     const int page = config->group("Parameters").readEntry("NbPage", 1);
0180 
0181     for (int i = 0; i < page; ++i)
0182         map()->addNewSheet();
0183 
0184     resetURL();
0185     initConfig();
0186     map()->styleManager()->createBuiltinStyles();
0187 
0188     KoDocument::initEmpty();
0189 }
0190 
0191 void Doc::saveConfig()
0192 {
0193     KSharedConfigPtr config = Factory::global().config();
0194     Q_UNUSED(config);
0195 }
0196 
0197 void Doc::initConfig()
0198 {
0199     KSharedConfigPtr config = Factory::global().config();
0200 
0201     const int page = config->group("Tables Page Layout").readEntry("Default unit page", 0);
0202     setUnit(KoUnit::fromListForUi(page, KoUnit::HidePixel));
0203 }
0204 
0205 int Doc::supportedSpecialFormats() const
0206 {
0207     return KoDocument::supportedSpecialFormats();
0208 }
0209 
0210 bool Doc::completeSaving(KoStore* _store)
0211 {
0212     Q_UNUSED(_store);
0213     return true;
0214 }
0215 
0216 
0217 QDomDocument Doc::saveXML()
0218 {
0219     /* don't pull focus away from the editor if this is just a background
0220        autosave */
0221     if (!isAutosaving()) {/* FIXME
0222         foreach(KoView* view, views())
0223         static_cast<View *>(view)->selection()->emitCloseEditor(true);
0224         */
0225         emit closeEditor(true);
0226     }
0227 
0228     QDomDocument doc = KoDocument::createDomDocument("tables", "spreadsheet", CURRENT_DTD_VERSION);
0229     QDomElement spread = doc.documentElement();
0230     spread.setAttribute("editor", "Calligra Sheets");
0231     spread.setAttribute("mime", "application/x-kspread");
0232     spread.setAttribute("syntaxVersion", QString::number(CURRENT_SYNTAX_VERSION));
0233 
0234     if (!d->spellListIgnoreAll.isEmpty()) {
0235         QDomElement spellCheckIgnore = doc.createElement("SPELLCHECKIGNORELIST");
0236         spread.appendChild(spellCheckIgnore);
0237         for (QStringList::ConstIterator it = d->spellListIgnoreAll.constBegin(); it != d->spellListIgnoreAll.constEnd(); ++it) {
0238             QDomElement spellElem = doc.createElement("SPELLCHECKIGNOREWORD");
0239             spellCheckIgnore.appendChild(spellElem);
0240             spellElem.setAttribute("word", *it);
0241         }
0242     }
0243 
0244     SavedDocParts::const_iterator iter = d->savedDocParts.constBegin();
0245     SavedDocParts::const_iterator end  = d->savedDocParts.constEnd();
0246     while (iter != end) {
0247         // save data we loaded in the beginning and which has no owner back to file
0248         spread.appendChild(iter.value().documentElement());
0249         ++iter;
0250     }
0251 
0252     QDomElement e = map()->save(doc);
0253 /*FIXME
0254         // Save visual info for the first view, such as active sheet and active cell
0255         // It looks like a hack, but reopening a document creates only one view anyway (David)
0256         View *const view = static_cast<View*>(views().first());
0257         Canvas *const canvas = view->canvasWidget();
0258         e.setAttribute("activeTable",  canvas->activeSheet()->sheetName());
0259         e.setAttribute("markerColumn", QString::number(view->selection()->marker().x()));
0260         e.setAttribute("markerRow",    QString::number(view->selection()->marker().y()));
0261         e.setAttribute("xOffset",      QString::number(canvas->xOffset()));
0262         e.setAttribute("yOffset",      QString::number(canvas->yOffset()));
0263 */
0264     spread.appendChild(e);
0265 
0266     setModified(false);
0267 
0268     return doc;
0269 }
0270 
0271 bool Doc::loadChildren(KoStore* _store)
0272 {
0273     return map()->loadChildren(_store);
0274 }
0275 
0276 
0277 bool Doc::loadXML(const KoXmlDocument& doc, KoStore*)
0278 {
0279     QPointer<KoUpdater> updater;
0280     if (progressUpdater()) {
0281         updater = progressUpdater()->startSubtask(1, "KSpread::Doc::loadXML");
0282         updater->setProgress(0);
0283     }
0284 
0285     d->spellListIgnoreAll.clear();
0286     // <spreadsheet>
0287     KoXmlElement spread = doc.documentElement();
0288 
0289     if (spread.attribute("mime") != "application/x-kspread" && spread.attribute("mime") != "application/vnd.kde.kspread") {
0290         setErrorMessage(i18n("Invalid document. Expected mimetype application/x-kspread or application/vnd.kde.kspread, got %1" , spread.attribute("mime")));
0291         return false;
0292     }
0293 
0294     bool ok = false;
0295     int version = spread.attribute("syntaxVersion").toInt(&ok);
0296     map()->setSyntaxVersion(ok ? version : 0);
0297     if (map()->syntaxVersion() > CURRENT_SYNTAX_VERSION) {
0298         int ret = KMessageBox::warningContinueCancel(
0299                       0, i18n("This document was created with a newer version of Calligra Sheets (syntax version: %1)\n"
0300                               "When you open it with this version of Calligra Sheets, some information may be lost.", map()->syntaxVersion()),
0301                       i18n("File Format Mismatch"), KStandardGuiItem::cont());
0302         if (ret == KMessageBox::Cancel) {
0303             setErrorMessage("USER_CANCELED");
0304             return false;
0305         }
0306     }
0307 
0308     // <locale>
0309     KoXmlElement loc = spread.namedItem("locale").toElement();
0310     if (!loc.isNull())
0311         static_cast<Localization*>(map()->calculationSettings()->locale())->load(loc);
0312 
0313     if (updater) updater->setProgress(5);
0314 
0315     KoXmlElement defaults = spread.namedItem("defaults").toElement();
0316     if (!defaults.isNull()) {
0317         double dim = defaults.attribute("row-height").toDouble(&ok);
0318         if (!ok)
0319             return false;
0320         map()->setDefaultRowHeight(dim);
0321 
0322         dim = defaults.attribute("col-width").toDouble(&ok);
0323 
0324         if (!ok)
0325             return false;
0326 
0327         map()->setDefaultColumnWidth(dim);
0328     }
0329 
0330     KoXmlElement ignoreAll = spread.namedItem("SPELLCHECKIGNORELIST").toElement();
0331     if (!ignoreAll.isNull()) {
0332         KoXmlElement spellWord = spread.namedItem("SPELLCHECKIGNORELIST").toElement();
0333 
0334         spellWord = spellWord.firstChild().toElement();
0335         while (!spellWord.isNull()) {
0336             if (spellWord.tagName() == "SPELLCHECKIGNOREWORD") {
0337                 d->spellListIgnoreAll.append(spellWord.attribute("word"));
0338             }
0339             spellWord = spellWord.nextSibling().toElement();
0340         }
0341     }
0342 
0343     if (updater) updater->setProgress(40);
0344     // In case of reload (e.g. from konqueror)
0345     qDeleteAll(map()->sheetList());
0346     map()->sheetList().clear();
0347 
0348     KoXmlElement styles = spread.namedItem("styles").toElement();
0349     if (!styles.isNull()) {
0350         if (!map()->styleManager()->loadXML(styles)) {
0351             setErrorMessage(i18n("Styles cannot be loaded."));
0352             return false;
0353         }
0354     }
0355 
0356     // <map>
0357     KoXmlElement mymap = spread.namedItem("map").toElement();
0358     if (mymap.isNull()) {
0359         setErrorMessage(i18n("Invalid document. No map tag."));
0360         return false;
0361     }
0362     if (!map()->loadXML(mymap)) {
0363         return false;
0364     }
0365 
0366     // named areas
0367     const KoXmlElement areaname = spread.namedItem("areaname").toElement();
0368     if (!areaname.isNull())
0369         map()->namedAreaManager()->loadXML(areaname);
0370 
0371     //Backwards compatibility with older versions for paper layout
0372     if (map()->syntaxVersion() < 1) {
0373         KoXmlElement paper = spread.namedItem("paper").toElement();
0374         if (!paper.isNull()) {
0375             loadPaper(paper);
0376         }
0377     }
0378 
0379     if (updater) updater->setProgress(85);
0380 
0381     KoXmlElement element(spread.firstChild().toElement());
0382     while (!element.isNull()) {
0383         QString tagName(element.tagName());
0384 
0385         if (tagName != "locale" && tagName != "map" && tagName != "styles"
0386                 && tagName != "SPELLCHECKIGNORELIST" && tagName != "areaname"
0387                 && tagName != "paper") {
0388             // belongs to a plugin, load it and save it for later use
0389             QDomDocument doc;
0390             KoXml::asQDomElement(doc, element);
0391             d->savedDocParts[ tagName ] = doc;
0392         }
0393 
0394         element = element.nextSibling().toElement();
0395     }
0396 
0397     if (updater) updater->setProgress(90);
0398     initConfig();
0399     if (updater) updater->setProgress(100);
0400 
0401     return true;
0402 }
0403 
0404 void Doc::loadPaper(KoXmlElement const & paper)
0405 {
0406     KoPageLayout pageLayout;
0407     pageLayout.format = KoPageFormat::formatFromString(paper.attribute("format"));
0408     pageLayout.orientation = (paper.attribute("orientation")  == "Portrait")
0409                              ? KoPageFormat::Portrait : KoPageFormat::Landscape;
0410 
0411     // <borders>
0412     KoXmlElement borders = paper.namedItem("borders").toElement();
0413     if (!borders.isNull()) {
0414         pageLayout.leftMargin   = MM_TO_POINT(borders.attribute("left").toFloat());
0415         pageLayout.rightMargin  = MM_TO_POINT(borders.attribute("right").toFloat());
0416         pageLayout.topMargin    = MM_TO_POINT(borders.attribute("top").toFloat());
0417         pageLayout.bottomMargin = MM_TO_POINT(borders.attribute("bottom").toFloat());
0418     }
0419 
0420     //apply to all sheet
0421     foreach(Sheet* sheet, map()->sheetList()) {
0422         sheet->printSettings()->setPageLayout(pageLayout);
0423     }
0424 
0425     QString hleft, hright, hcenter;
0426     QString fleft, fright, fcenter;
0427     // <head>
0428     KoXmlElement head = paper.namedItem("head").toElement();
0429     if (!head.isNull()) {
0430         KoXmlElement left = head.namedItem("left").toElement();
0431         if (!left.isNull())
0432             hleft = left.text();
0433         KoXmlElement center = head.namedItem("center").toElement();
0434         if (!center.isNull())
0435             hcenter = center.text();
0436         KoXmlElement right = head.namedItem("right").toElement();
0437         if (!right.isNull())
0438             hright = right.text();
0439     }
0440     // <foot>
0441     KoXmlElement foot = paper.namedItem("foot").toElement();
0442     if (!foot.isNull()) {
0443         KoXmlElement left = foot.namedItem("left").toElement();
0444         if (!left.isNull())
0445             fleft = left.text();
0446         KoXmlElement center = foot.namedItem("center").toElement();
0447         if (!center.isNull())
0448             fcenter = center.text();
0449         KoXmlElement right = foot.namedItem("right").toElement();
0450         if (!right.isNull())
0451             fright = right.text();
0452     }
0453     //The macro "<sheet>" formerly was typed as "<table>"
0454     hleft.replace("<table>", "<sheet>");
0455     hcenter.replace("<table>", "<sheet>");
0456     hright.replace("<table>", "<sheet>");
0457     fleft.replace("<table>", "<sheet>");
0458     fcenter.replace("<table>", "<sheet>");
0459     fright.replace("<table>", "<sheet>");
0460 
0461     foreach(Sheet* sheet, map()->sheetList()) {
0462         sheet->print()->headerFooter()->setHeadFootLine(hleft, hcenter, hright,
0463                 fleft, fcenter, fright);
0464     }
0465 }
0466 
0467 bool Doc::completeLoading(KoStore* store)
0468 {
0469     debugSheets << "------------------------ COMPLETING --------------------";
0470 
0471     setModified(false);
0472     bool ok = map()->completeLoading(store);
0473 
0474     debugSheets << "------------------------ COMPLETION DONE --------------------";
0475     return ok;
0476 }
0477 
0478 
0479 bool Doc::docData(QString const & xmlTag, QDomDocument & data)
0480 {
0481     SavedDocParts::iterator iter = d->savedDocParts.find(xmlTag);
0482     if (iter == d->savedDocParts.end())
0483         return false;
0484     data = iter.value();
0485     d->savedDocParts.erase(iter);
0486     return true;
0487 }
0488 
0489 void Doc::addIgnoreWordAllList(const QStringList & _lst)
0490 {
0491     d->spellListIgnoreAll = _lst;
0492 }
0493 
0494 QStringList Doc::spellListIgnoreAll() const
0495 {
0496     return d->spellListIgnoreAll;
0497 }
0498 
0499 void Doc::paintContent(QPainter& painter, const QRect& rect)
0500 {
0501     paintContent(painter, rect, 0);
0502 }
0503 
0504 void Doc::paintContent(QPainter& painter, const QRect& rect, Sheet* _sheet)
0505 {
0506     if (rect.isEmpty()) {
0507         return;
0508     }
0509     Sheet *const sheet = _sheet ? _sheet : d->map->sheet(0);
0510 
0511     const KoPageLayout pageLayout = sheet->printSettings()->pageLayout();
0512     QPixmap thumbnail(pageLayout.width, pageLayout.height);
0513     thumbnail.fill(Qt::white);
0514 
0515     SheetView sheetView(sheet);
0516 
0517     const qreal zoom = sheet->printSettings()->zoom();
0518     KoZoomHandler zoomHandler;
0519     zoomHandler.setZoom(zoom);
0520     sheetView.setViewConverter(&zoomHandler);
0521 
0522     sheetView.setPaintCellRange(sheet->print()->cellRange(1)); // first page
0523 
0524     QPainter pixmapPainter(&thumbnail);
0525     pixmapPainter.setClipRect(QRect(QPoint(0, 0), thumbnail.size()));
0526     sheetView.paintCells(pixmapPainter, QRect(0, 0, pageLayout.width, pageLayout.height), QPointF(0,0));
0527 
0528     // The pixmap gets scaled to fit the rectangle.
0529     painter.drawPixmap(rect & QRect(0, 0, 100, 100), thumbnail);
0530 }
0531 
0532 void Doc::updateAllViews()
0533 {
0534     emit updateView();
0535 }
0536 
0537 void Doc::addIgnoreWordAll(const QString & word)
0538 {
0539     if (d->spellListIgnoreAll.indexOf(word) == -1)
0540         d->spellListIgnoreAll.append(word);
0541 }
0542 
0543 void Doc::clearIgnoreWordAll()
0544 {
0545     d->spellListIgnoreAll.clear();
0546 }
0547 
0548 void Doc::loadConfigFromFile()
0549 {
0550     d->configLoadFromFile = true;
0551 }
0552 
0553 bool Doc::configLoadFromFile() const
0554 {
0555     return d->configLoadFromFile;
0556 }
0557 
0558 void Doc::sheetAdded(Sheet* sheet)
0559 {
0560 #ifndef QT_NO_DBUS
0561     new SheetAdaptor(sheet);
0562     QString dbusPath('/' + sheet->map()->objectName() + '/' + sheet->objectName());
0563     if (sheet->parent() && !sheet->parent()->objectName().isEmpty()) {
0564         dbusPath.prepend('/' + sheet->parent()->objectName());
0565     }
0566     QDBusConnection::sessionBus().registerObject(dbusPath, sheet);
0567 #endif
0568 }
0569 
0570 bool Doc::saveOdf(SavingContext &documentContext)
0571 {
0572     /* don't pull focus away from the editor if this is just a background
0573        autosave */
0574     if (!isAutosaving()) {
0575         /*FIXME
0576         foreach(KoView* view, views())
0577             static_cast<View *>(view)->selection()->emitCloseEditor(true);
0578         */
0579         emit closeEditor(true);
0580     }
0581 
0582     return DocBase::saveOdf(documentContext);
0583 }