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 }