Warning, file /office/calligra/libs/main/KoDocument.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* This file is part of the KDE project 0002 * Copyright (C) 1998, 1999 Torben Weis <weis@kde.org> 0003 * Copyright (C) 2000-2005 David Faure <faure@kde.org> 0004 * Copyright (C) 2007-2008 Thorsten Zachmann <zachmann@kde.org> 0005 * Copyright (C) 2010-2012 Boudewijn Rempt <boud@kogmbh.com> 0006 * Copyright (C) 2011 Inge Wallin <ingwa@kogmbh.com> 0007 * 0008 * This library is free software; you can redistribute it and/or 0009 * modify it under the terms of the GNU Library General Public 0010 * License as published by the Free Software Foundation; either 0011 * version 2 of the License, or (at your option) any later version. 0012 * 0013 * This library is distributed in the hope that it will be useful, 0014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0016 * Library General Public License for more details. 0017 * 0018 * You should have received a copy of the GNU Library General Public License 0019 * along with this library; see the file COPYING.LIB. If not, write to 0020 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0021 * Boston, MA 02110-1301, USA. 0022 */ 0023 0024 #include "KoDocument.h" 0025 0026 #include "KoMainWindow.h" // XXX: remove 0027 #include <kmessagebox.h> // XXX: remove 0028 #include <KNotification> // XXX: remove 0029 0030 #include "KoComponentData.h" 0031 #include "KoPart.h" 0032 #include "KoEmbeddedDocumentSaver.h" 0033 #include "KoFilterManager.h" 0034 #include "KoFileDialog.h" 0035 #include "KoDocumentInfo.h" 0036 #include "KoView.h" 0037 0038 #include "KoOdfReadStore.h" 0039 #include "KoOdfWriteStore.h" 0040 #include "KoXmlNS.h" 0041 0042 #include <KoProgressProxy.h> 0043 #include <KoProgressUpdater.h> 0044 #include <KoUpdater.h> 0045 #include <KoDocumentRdfBase.h> 0046 #include <KoDpi.h> 0047 #include <KoUnit.h> 0048 #include <KoXmlWriter.h> 0049 #include <KoDocumentInfoDlg.h> 0050 #include <KoPageLayout.h> 0051 #include <KoGridData.h> 0052 #include <KoGuidesData.h> 0053 0054 #include <kfileitem.h> 0055 #include <KoNetAccess.h> 0056 #include <klocalizedstring.h> 0057 #include <MainDebug.h> 0058 #include <kconfiggroup.h> 0059 #include <kio/job.h> 0060 #include <kdirnotify.h> 0061 #include <KBackup> 0062 0063 #include <QMimeDatabase> 0064 #include <QTemporaryFile> 0065 #include <QApplication> 0066 #include <QtGlobal> 0067 #include <QBuffer> 0068 #include <QDir> 0069 #include <QFileInfo> 0070 #include <QPainter> 0071 #include <QTimer> 0072 #ifndef QT_NO_DBUS 0073 #include <KJobWidgets> 0074 #include <QDBusConnection> 0075 #endif 0076 0077 // Define the protocol used here for embedded documents' URL 0078 // This used to "store" but QUrl didn't like it, 0079 // so let's simply make it "tar" ! 0080 #define STORE_PROTOCOL "tar" 0081 // The internal path is a hack to make QUrl happy and for document children 0082 #define INTERNAL_PROTOCOL "intern" 0083 #define INTERNAL_PREFIX "intern:/" 0084 // Warning, keep it sync in koStore.cc 0085 #include <kactioncollection.h> 0086 #include "KoUndoStackAction.h" 0087 0088 #include <unistd.h> 0089 0090 using namespace std; 0091 0092 /********************************************************** 0093 * 0094 * KoDocument 0095 * 0096 **********************************************************/ 0097 0098 namespace { 0099 0100 class DocumentProgressProxy : public KoProgressProxy { 0101 public: 0102 KoMainWindow *m_mainWindow; 0103 DocumentProgressProxy(KoMainWindow *mainWindow) 0104 : m_mainWindow(mainWindow) 0105 { 0106 } 0107 0108 ~DocumentProgressProxy() override { 0109 // signal that the job is done 0110 setValue(-1); 0111 } 0112 0113 int maximum() const override { 0114 return 100; 0115 } 0116 0117 void setValue(int value) override { 0118 if (m_mainWindow) { 0119 m_mainWindow->slotProgress(value); 0120 } 0121 } 0122 0123 void setRange(int /*minimum*/, int /*maximum*/) override { 0124 0125 } 0126 0127 void setFormat(const QString &/*format*/) override { 0128 0129 } 0130 }; 0131 } 0132 0133 0134 //static 0135 QString KoDocument::newObjectName() 0136 { 0137 static int s_docIFNumber = 0; 0138 QString name; name.setNum(s_docIFNumber++); name.prepend("document_"); 0139 return name; 0140 } 0141 0142 class Q_DECL_HIDDEN KoDocument::Private 0143 { 0144 public: 0145 Private(KoDocument *document, KoPart *part) : 0146 document(document), 0147 parentPart(part), 0148 docInfo(0), 0149 docRdf(0), 0150 progressUpdater(0), 0151 progressProxy(0), 0152 profileStream(0), 0153 filterManager(0), 0154 specialOutputFlag(0), // default is native format 0155 isImporting(false), 0156 isExporting(false), 0157 password(QString()), 0158 modifiedAfterAutosave(false), 0159 autosaving(false), 0160 shouldCheckAutoSaveFile(true), 0161 autoErrorHandlingEnabled(true), 0162 backupFile(true), 0163 backupPath(QString()), 0164 doNotSaveExtDoc(false), 0165 isLoading(false), 0166 undoStack(0), 0167 modified(false), 0168 readwrite(true), 0169 alwaysAllowSaving(false), 0170 disregardAutosaveFailure(false) 0171 { 0172 m_job = 0; 0173 m_statJob = 0; 0174 m_uploadJob = 0; 0175 m_saveOk = false; 0176 m_waitForSave = false; 0177 m_duringSaveAs = false; 0178 m_bTemp = false; 0179 m_bAutoDetectedMime = false; 0180 0181 confirmNonNativeSave[0] = true; 0182 confirmNonNativeSave[1] = true; 0183 if (QLocale().measurementSystem() == QLocale::ImperialSystem) { 0184 unit = KoUnit::Inch; 0185 } else { 0186 unit = KoUnit::Centimeter; 0187 } 0188 } 0189 0190 KoDocument *document; 0191 KoPart *const parentPart; 0192 0193 KoDocumentInfo *docInfo; 0194 KoDocumentRdfBase *docRdf; 0195 0196 KoProgressUpdater *progressUpdater; 0197 KoProgressProxy *progressProxy; 0198 QTextStream *profileStream; 0199 QTime profileReferenceTime; 0200 0201 KoUnit unit; 0202 0203 KoFilterManager *filterManager; // The filter-manager to use when loading/saving [for the options] 0204 0205 QByteArray mimeType; // The actual mimetype of the document 0206 QByteArray outputMimeType; // The mimetype to use when saving 0207 bool confirmNonNativeSave [2]; // used to pop up a dialog when saving for the 0208 // first time if the file is in a foreign format 0209 // (Save/Save As, Export) 0210 int specialOutputFlag; // See KoFileDialog in koMainWindow.cc 0211 bool isImporting; 0212 bool isExporting; // File --> Import/Export vs File --> Open/Save 0213 QString password; // The password used to encrypt an encrypted document 0214 0215 QTimer autoSaveTimer; 0216 QString lastErrorMessage; // see openFile() 0217 int autoSaveDelay; // in seconds, 0 to disable. 0218 bool modifiedAfterAutosave; 0219 bool autosaving; 0220 bool shouldCheckAutoSaveFile; // usually true 0221 bool autoErrorHandlingEnabled; // usually true 0222 bool backupFile; 0223 QString backupPath; 0224 bool doNotSaveExtDoc; // makes it possible to save only internally stored child documents 0225 bool isLoading; // True while loading (openUrl is async) 0226 0227 QList<KoVersionInfo> versionInfo; 0228 0229 KUndo2Stack *undoStack; 0230 0231 KoGridData gridData; 0232 KoGuidesData guidesData; 0233 0234 bool isEmpty; 0235 0236 KoPageLayout pageLayout; 0237 0238 KIO::FileCopyJob * m_job; 0239 KIO::StatJob * m_statJob; 0240 KIO::FileCopyJob * m_uploadJob; 0241 QUrl m_originalURL; // for saveAs 0242 QString m_originalFilePath; // for saveAs 0243 bool m_saveOk : 1; 0244 bool m_waitForSave : 1; 0245 bool m_duringSaveAs : 1; 0246 bool m_bTemp: 1; // If @p true, @p m_file is a temporary file that needs to be deleted later. 0247 bool m_bAutoDetectedMime : 1; // whether the mimetype in the arguments was detected by the part itself 0248 QUrl m_url; // Remote (or local) url - the one displayed to the user. 0249 QString m_file; // Local file - the only one the part implementation should deal with. 0250 QEventLoop m_eventLoop; 0251 0252 bool modified; 0253 bool readwrite; 0254 bool alwaysAllowSaving; 0255 bool disregardAutosaveFailure; 0256 0257 bool openFile() 0258 { 0259 DocumentProgressProxy *progressProxy = 0; 0260 if (!document->progressProxy()) { 0261 KoMainWindow *mainWindow = 0; 0262 if (parentPart->mainWindows().count() > 0) { 0263 mainWindow = parentPart->mainWindows()[0]; 0264 } 0265 progressProxy = new DocumentProgressProxy(mainWindow); 0266 document->setProgressProxy(progressProxy); 0267 } 0268 document->setUrl(m_url); 0269 0270 bool ok = document->openFile(); 0271 0272 if (progressProxy) { 0273 document->setProgressProxy(0); 0274 delete progressProxy; 0275 } 0276 return ok; 0277 } 0278 0279 bool openLocalFile() 0280 { 0281 m_bTemp = false; 0282 // set the mimetype only if it was not already set (for example, by the host application) 0283 if (mimeType.isEmpty()) { 0284 // get the mimetype of the file 0285 // using findByUrl() to avoid another string -> url conversion 0286 QMimeType mime = QMimeDatabase().mimeTypeForUrl(m_url); 0287 if (mime.isValid()) { 0288 mimeType = mime.name().toLatin1(); 0289 m_bAutoDetectedMime = true; 0290 } 0291 } 0292 const bool ret = openFile(); 0293 if (ret) { 0294 emit document->completed(); 0295 } else { 0296 emit document->canceled(QString()); 0297 } 0298 return ret; 0299 } 0300 0301 void openRemoteFile() 0302 { 0303 m_bTemp = true; 0304 // Use same extension as remote file. This is important for mimetype-determination (e.g. koffice) 0305 QString fileName = m_url.fileName(); 0306 QFileInfo fileInfo(fileName); 0307 QString ext = fileInfo.completeSuffix(); 0308 QString extension; 0309 if (!ext.isEmpty() && m_url.query().isNull()) // not if the URL has a query, e.g. cgi.pl?something 0310 extension = '.'+ext; // keep the '.' 0311 QTemporaryFile tempFile(QDir::tempPath() + "/" + qAppName() + QLatin1String("_XXXXXX") + extension); 0312 tempFile.setAutoRemove(false); 0313 tempFile.open(); 0314 m_file = tempFile.fileName(); 0315 0316 const QUrl destURL = QUrl::fromLocalFile( m_file ); 0317 KIO::JobFlags flags = KIO::DefaultFlags; 0318 flags |= KIO::Overwrite; 0319 m_job = KIO::file_copy(m_url, destURL, 0600, flags); 0320 #ifndef QT_NO_DBUS 0321 KJobWidgets::setWindow(m_job, 0); 0322 if (m_job->ui()) { 0323 KJobWidgets::setWindow(m_job, parentPart->currentMainwindow()); 0324 } 0325 #endif 0326 QObject::connect(m_job, SIGNAL(result(KJob*)), document, SLOT(_k_slotJobFinished(KJob*))); 0327 QObject::connect(m_job, SIGNAL(mimetype(KIO::Job*,QString)), document, SLOT(_k_slotGotMimeType(KIO::Job*,QString))); 0328 } 0329 0330 // Set m_file correctly for m_url 0331 void prepareSaving() 0332 { 0333 // Local file 0334 if ( m_url.isLocalFile() ) 0335 { 0336 if ( m_bTemp ) // get rid of a possible temp file first 0337 { // (happens if previous url was remote) 0338 QFile::remove( m_file ); 0339 m_bTemp = false; 0340 } 0341 m_file = m_url.toLocalFile(); 0342 } 0343 else 0344 { // Remote file 0345 // We haven't saved yet, or we did but locally - provide a temp file 0346 if ( m_file.isEmpty() || !m_bTemp ) 0347 { 0348 QTemporaryFile tempFile; 0349 tempFile.setAutoRemove(false); 0350 tempFile.open(); 0351 m_file = tempFile.fileName(); 0352 m_bTemp = true; 0353 } 0354 // otherwise, we already had a temp file 0355 } 0356 } 0357 0358 0359 void _k_slotJobFinished( KJob * job ) 0360 { 0361 Q_ASSERT( job == m_job ); 0362 m_job = 0; 0363 if (job->error()) 0364 emit document->canceled( job->errorString() ); 0365 else { 0366 if ( openFile() ) { 0367 emit document->completed(); 0368 } 0369 else { 0370 emit document->canceled(QString()); 0371 } 0372 } 0373 } 0374 0375 void _k_slotStatJobFinished(KJob * job) 0376 { 0377 Q_ASSERT(job == m_statJob); 0378 m_statJob = 0; 0379 0380 // this could maybe confuse some apps? So for now we'll just fallback to KIO::get 0381 // and error again. Well, maybe this even helps with wrong stat results. 0382 if (!job->error()) { 0383 const QUrl localUrl = static_cast<KIO::StatJob*>(job)->mostLocalUrl(); 0384 if (localUrl.isLocalFile()) { 0385 m_file = localUrl.toLocalFile(); 0386 openLocalFile(); 0387 return; 0388 } 0389 } 0390 openRemoteFile(); 0391 } 0392 0393 0394 void _k_slotGotMimeType(KIO::Job *job, const QString &mime) 0395 { 0396 // kDebug(1000) << mime; 0397 Q_ASSERT(job == m_job); Q_UNUSED(job); 0398 // set the mimetype only if it was not already set (for example, by the host application) 0399 if (mimeType.isEmpty()) { 0400 mimeType = mime.toLatin1(); 0401 m_bAutoDetectedMime = true; 0402 } 0403 } 0404 0405 void _k_slotUploadFinished( KJob * ) 0406 { 0407 if (m_uploadJob->error()) 0408 { 0409 QFile::remove(m_uploadJob->srcUrl().toLocalFile()); 0410 m_uploadJob = 0; 0411 if (m_duringSaveAs) { 0412 document->setUrl(m_originalURL); 0413 m_file = m_originalFilePath; 0414 } 0415 } 0416 else 0417 { 0418 ::org::kde::KDirNotify::emitFilesAdded(QUrl::fromLocalFile(m_url.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path())); 0419 0420 m_uploadJob = 0; 0421 document->setModified( false ); 0422 emit document->completed(); 0423 m_saveOk = true; 0424 } 0425 m_duringSaveAs = false; 0426 m_originalURL = QUrl(); 0427 m_originalFilePath.clear(); 0428 if (m_waitForSave) { 0429 m_eventLoop.quit(); 0430 } 0431 } 0432 0433 0434 }; 0435 0436 KoDocument::KoDocument(KoPart *parent, KUndo2Stack *undoStack) 0437 : d(new Private(this, parent)) 0438 { 0439 Q_ASSERT(parent); 0440 0441 d->isEmpty = true; 0442 d->filterManager = new KoFilterManager(this, d->progressUpdater); 0443 0444 connect(&d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); 0445 setAutoSave(defaultAutoSave()); 0446 0447 setObjectName(newObjectName()); 0448 d->docInfo = new KoDocumentInfo(this); 0449 0450 d->pageLayout.width = 0; 0451 d->pageLayout.height = 0; 0452 d->pageLayout.topMargin = 0; 0453 d->pageLayout.bottomMargin = 0; 0454 d->pageLayout.leftMargin = 0; 0455 d->pageLayout.rightMargin = 0; 0456 0457 d->undoStack = undoStack; 0458 d->undoStack->setParent(this); 0459 0460 KConfigGroup cfgGrp(d->parentPart->componentData().config(), "Undo"); 0461 d->undoStack->setUndoLimit(cfgGrp.readEntry("UndoLimit", 1000)); 0462 0463 connect(d->undoStack, SIGNAL(indexChanged(int)), this, SLOT(slotUndoStackIndexChanged(int))); 0464 0465 } 0466 0467 KoDocument::~KoDocument() 0468 { 0469 d->autoSaveTimer.disconnect(this); 0470 d->autoSaveTimer.stop(); 0471 d->parentPart->deleteLater(); 0472 0473 delete d->filterManager; 0474 delete d; 0475 } 0476 0477 0478 KoPart *KoDocument::documentPart() const 0479 { 0480 return d->parentPart; 0481 } 0482 0483 bool KoDocument::exportDocument(const QUrl &_url) 0484 { 0485 bool ret; 0486 0487 d->isExporting = true; 0488 0489 // 0490 // Preserve a lot of state here because we need to restore it in order to 0491 // be able to fake a File --> Export. Can't do this in saveFile() because, 0492 // for a start, KParts has already set url and m_file and because we need 0493 // to restore the modified flag etc. and don't want to put a load on anyone 0494 // reimplementing saveFile() (Note: importDocument() and exportDocument() 0495 // will remain non-virtual). 0496 // 0497 QUrl oldURL = url(); 0498 QString oldFile = localFilePath(); 0499 0500 bool wasModified = isModified(); 0501 QByteArray oldMimeType = mimeType(); 0502 0503 // save... 0504 ret = saveAs(_url); 0505 0506 0507 // 0508 // This is sooooo hacky :( 0509 // Hopefully we will restore enough state. 0510 // 0511 debugMain << "Restoring KoDocument state to before export"; 0512 0513 // always restore url & m_file because KParts has changed them 0514 // (regardless of failure or success) 0515 setUrl(oldURL); 0516 setLocalFilePath(oldFile); 0517 0518 // on successful export we need to restore modified etc. too 0519 // on failed export, mimetype/modified hasn't changed anyway 0520 if (ret) { 0521 setModified(wasModified); 0522 d->mimeType = oldMimeType; 0523 } 0524 0525 0526 d->isExporting = false; 0527 0528 return ret; 0529 } 0530 0531 bool KoDocument::saveFile() 0532 { 0533 debugMain << "doc=" << url().url(); 0534 0535 // Save it to be able to restore it after a failed save 0536 const bool wasModified = isModified(); 0537 0538 // The output format is set by koMainWindow, and by openFile 0539 QByteArray outputMimeType = d->outputMimeType; 0540 if (outputMimeType.isEmpty()) { 0541 outputMimeType = d->outputMimeType = nativeFormatMimeType(); 0542 debugMain << "Empty output mime type, saving to" << outputMimeType; 0543 } 0544 0545 QApplication::setOverrideCursor(Qt::WaitCursor); 0546 0547 if (backupFile()) { 0548 if (url().isLocalFile()) 0549 KBackup::backupFile(url().toLocalFile(), d->backupPath); 0550 else { 0551 KIO::UDSEntry entry; 0552 if (KIO::NetAccess::stat(url(), 0553 entry, 0554 d->parentPart->currentMainwindow())) { // this file exists => backup 0555 emit statusBarMessage(i18n("Making backup...")); 0556 QUrl backup; 0557 if (d->backupPath.isEmpty()) 0558 backup = url(); 0559 else 0560 backup = QUrl::fromLocalFile(d->backupPath + '/' + url().fileName()); 0561 backup.setPath(backup.path() + QString::fromLatin1("~")); 0562 KFileItem item(entry, url()); 0563 Q_ASSERT(item.name() == url().fileName()); 0564 KIO::FileCopyJob *job = KIO::file_copy(url(), backup, item.permissions(), KIO::Overwrite | KIO::HideProgressInfo); 0565 job->exec(); 0566 } 0567 } 0568 } 0569 0570 emit statusBarMessage(i18n("Saving...")); 0571 qApp->processEvents(); 0572 bool ret = false; 0573 bool suppressErrorDialog = false; 0574 if (!isNativeFormat(outputMimeType)) { 0575 debugMain << "Saving to format" << outputMimeType << "in" << localFilePath(); 0576 // Not native format : save using export filter 0577 KoFilter::ConversionStatus status = d->filterManager->exportDocument(localFilePath(), outputMimeType); 0578 ret = status == KoFilter::OK; 0579 suppressErrorDialog = (status == KoFilter::UserCancelled || status == KoFilter::BadConversionGraph); 0580 } else { 0581 // Native format => normal save 0582 Q_ASSERT(!localFilePath().isEmpty()); 0583 ret = saveNativeFormat(localFilePath()); 0584 } 0585 0586 if (ret) { 0587 d->undoStack->setClean(); 0588 removeAutoSaveFiles(); 0589 // Restart the autosave timer 0590 // (we don't want to autosave again 2 seconds after a real save) 0591 setAutoSave(d->autoSaveDelay); 0592 } 0593 0594 QApplication::restoreOverrideCursor(); 0595 if (!ret) { 0596 if (!suppressErrorDialog) { 0597 if (errorMessage().isEmpty()) { 0598 KMessageBox::error(0, i18n("Could not save\n%1", localFilePath())); 0599 } else if (errorMessage() != "USER_CANCELED") { 0600 KMessageBox::error(0, i18n("Could not save %1\nReason: %2", localFilePath(), errorMessage())); 0601 } 0602 0603 } 0604 0605 // couldn't save file so this new URL is invalid 0606 // FIXME: we should restore the current document's true URL instead of 0607 // setting it to nothing otherwise anything that depends on the URL 0608 // being correct will not work (i.e. the document will be called 0609 // "Untitled" which may not be true) 0610 // 0611 // Update: now the URL is restored in KoMainWindow but really, this 0612 // should still be fixed in KoDocument/KParts (ditto for file). 0613 // We still resetURL() here since we may or may not have been called 0614 // by KoMainWindow - Clarence 0615 resetURL(); 0616 0617 // As we did not save, restore the "was modified" status 0618 setModified(wasModified); 0619 } 0620 0621 if (ret) { 0622 d->mimeType = outputMimeType; 0623 setConfirmNonNativeSave(isExporting(), false); 0624 } 0625 emit clearStatusBarMessage(); 0626 0627 if (ret) { 0628 KNotification *notify = new KNotification("DocumentSaved"); 0629 notify->setText(i18n("Document <i>%1</i> saved", url().url())); 0630 notify->addContext("url", url().url()); 0631 QTimer::singleShot(0, notify, SLOT(sendEvent())); 0632 } 0633 0634 return ret; 0635 } 0636 0637 0638 QByteArray KoDocument::mimeType() const 0639 { 0640 return d->mimeType; 0641 } 0642 0643 void KoDocument::setMimeType(const QByteArray & mimeType) 0644 { 0645 d->mimeType = mimeType; 0646 } 0647 0648 void KoDocument::setOutputMimeType(const QByteArray & mimeType, int specialOutputFlag) 0649 { 0650 d->outputMimeType = mimeType; 0651 d->specialOutputFlag = specialOutputFlag; 0652 } 0653 0654 QByteArray KoDocument::outputMimeType() const 0655 { 0656 return d->outputMimeType; 0657 } 0658 0659 int KoDocument::specialOutputFlag() const 0660 { 0661 return d->specialOutputFlag; 0662 } 0663 0664 bool KoDocument::confirmNonNativeSave(const bool exporting) const 0665 { 0666 // "exporting ? 1 : 0" is different from "exporting" because a bool is 0667 // usually implemented like an "int", not "unsigned : 1" 0668 return d->confirmNonNativeSave [ exporting ? 1 : 0 ]; 0669 } 0670 0671 void KoDocument::setConfirmNonNativeSave(const bool exporting, const bool on) 0672 { 0673 d->confirmNonNativeSave [ exporting ? 1 : 0] = on; 0674 } 0675 0676 bool KoDocument::saveInBatchMode() const 0677 { 0678 return d->filterManager->getBatchMode(); 0679 } 0680 0681 void KoDocument::setSaveInBatchMode(const bool batchMode) 0682 { 0683 d->filterManager->setBatchMode(batchMode); 0684 } 0685 0686 bool KoDocument::isImporting() const 0687 { 0688 return d->isImporting; 0689 } 0690 0691 bool KoDocument::isExporting() const 0692 { 0693 return d->isExporting; 0694 } 0695 0696 void KoDocument::setCheckAutoSaveFile(bool b) 0697 { 0698 d->shouldCheckAutoSaveFile = b; 0699 } 0700 0701 void KoDocument::setAutoErrorHandlingEnabled(bool b) 0702 { 0703 d->autoErrorHandlingEnabled = b; 0704 } 0705 0706 bool KoDocument::isAutoErrorHandlingEnabled() const 0707 { 0708 return d->autoErrorHandlingEnabled; 0709 } 0710 0711 void KoDocument::slotAutoSave() 0712 { 0713 if (d->modified && d->modifiedAfterAutosave && !d->isLoading) { 0714 // Give a warning when trying to autosave an encrypted file when no password is known (should not happen) 0715 if (d->specialOutputFlag == SaveEncrypted && d->password.isNull()) { 0716 // That advice should also fix this error from occurring again 0717 emit statusBarMessage(i18n("The password of this encrypted document is not known. Autosave aborted! Please save your work manually.")); 0718 } else { 0719 connect(this, SIGNAL(sigProgress(int)), d->parentPart->currentMainwindow(), SLOT(slotProgress(int))); 0720 emit statusBarMessage(i18n("Autosaving...")); 0721 d->autosaving = true; 0722 bool ret = saveNativeFormat(autoSaveFile(localFilePath())); 0723 setModified(true); 0724 if (ret) { 0725 d->modifiedAfterAutosave = false; 0726 d->autoSaveTimer.stop(); // until the next change 0727 } 0728 d->autosaving = false; 0729 emit clearStatusBarMessage(); 0730 disconnect(this, SIGNAL(sigProgress(int)), d->parentPart->currentMainwindow(), SLOT(slotProgress(int))); 0731 if (!ret && !d->disregardAutosaveFailure) { 0732 emit statusBarMessage(i18n("Error during autosave! Partition full?")); 0733 } 0734 } 0735 } 0736 } 0737 0738 void KoDocument::setReadWrite(bool readwrite) 0739 { 0740 d->readwrite = readwrite; 0741 setAutoSave(d->autoSaveDelay); 0742 0743 0744 // XXX: this doesn't belong in KoDocument 0745 foreach(KoView *view, d->parentPart->views()) { 0746 view->updateReadWrite(readwrite); 0747 } 0748 0749 foreach(KoMainWindow *mainWindow, d->parentPart->mainWindows()) { 0750 mainWindow->setReadWrite(readwrite); 0751 } 0752 0753 } 0754 0755 void KoDocument::setAutoSave(int delay) 0756 { 0757 d->autoSaveDelay = delay; 0758 if (isReadWrite() && d->autoSaveDelay > 0) 0759 d->autoSaveTimer.start(d->autoSaveDelay * 1000); 0760 else 0761 d->autoSaveTimer.stop(); 0762 } 0763 0764 KoDocumentInfo *KoDocument::documentInfo() const 0765 { 0766 return d->docInfo; 0767 } 0768 0769 KoDocumentRdfBase *KoDocument::documentRdf() const 0770 { 0771 return d->docRdf; 0772 } 0773 0774 void KoDocument::setDocumentRdf(KoDocumentRdfBase *rdfDocument) 0775 { 0776 delete d->docRdf; 0777 d->docRdf = rdfDocument; 0778 } 0779 0780 bool KoDocument::isModified() const 0781 { 0782 return d->modified; 0783 } 0784 0785 bool KoDocument::saveNativeFormat(const QString & file) 0786 { 0787 d->lastErrorMessage.clear(); 0788 0789 KoStore::Backend backend = KoStore::Auto; 0790 if (d->specialOutputFlag == SaveAsDirectoryStore) { 0791 backend = KoStore::Directory; 0792 debugMain << "Saving as uncompressed XML, using directory store."; 0793 } 0794 #ifdef QCA2 0795 else if (d->specialOutputFlag == SaveEncrypted) { 0796 backend = KoStore::Encrypted; 0797 debugMain << "Saving using encrypted backend."; 0798 } 0799 #endif 0800 else if (d->specialOutputFlag == SaveAsFlatXML) { 0801 debugMain << "Saving as a flat XML file."; 0802 QFile f(file); 0803 if (f.open(QIODevice::WriteOnly | QIODevice::Text)) { 0804 bool success = saveToStream(&f); 0805 f.close(); 0806 return success; 0807 } else 0808 return false; 0809 } 0810 0811 debugMain << "KoDocument::saveNativeFormat nativeFormatMimeType=" << nativeFormatMimeType(); 0812 // OLD: bool oasis = d->specialOutputFlag == SaveAsOASIS; 0813 // OLD: QCString mimeType = oasis ? nativeOasisMimeType() : nativeFormatMimeType(); 0814 QByteArray mimeType = d->outputMimeType; 0815 debugMain << "KoDocument::savingTo mimeType=" << mimeType; 0816 QByteArray nativeOasisMime = nativeOasisMimeType(); 0817 bool oasis = !mimeType.isEmpty() && (mimeType == nativeOasisMime || mimeType == nativeOasisMime + "-template" || mimeType.startsWith("application/vnd.oasis.opendocument")); 0818 0819 // TODO: use std::auto_ptr or create store on stack [needs API fixing], 0820 // to remove all the 'delete store' in all the branches 0821 KoStore *store = KoStore::createStore(file, KoStore::Write, mimeType, backend); 0822 if (d->specialOutputFlag == SaveEncrypted && !d->password.isNull()) 0823 store->setPassword(d->password); 0824 if (store->bad()) { 0825 d->lastErrorMessage = i18n("Could not create the file for saving"); // more details needed? 0826 delete store; 0827 return false; 0828 } 0829 if (oasis) { 0830 return saveNativeFormatODF(store, mimeType); 0831 } else { 0832 return saveNativeFormatCalligra(store); 0833 } 0834 } 0835 0836 bool KoDocument::saveNativeFormatODF(KoStore *store, const QByteArray &mimeType) 0837 { 0838 debugMain << "Saving to OASIS format"; 0839 // Tell KoStore not to touch the file names 0840 0841 KoOdfWriteStore odfStore(store); 0842 KoXmlWriter *manifestWriter = odfStore.manifestWriter(mimeType); 0843 KoEmbeddedDocumentSaver embeddedSaver; 0844 SavingContext documentContext(odfStore, embeddedSaver); 0845 0846 if (!saveOdf(documentContext)) { 0847 debugMain << "saveOdf failed"; 0848 odfStore.closeManifestWriter(false); 0849 delete store; 0850 return false; 0851 } 0852 0853 // Save embedded objects 0854 if (!embeddedSaver.saveEmbeddedDocuments(documentContext)) { 0855 debugMain << "save embedded documents failed"; 0856 odfStore.closeManifestWriter(false); 0857 delete store; 0858 return false; 0859 } 0860 0861 if (store->open("meta.xml")) { 0862 if (!d->docInfo->saveOasis(store) || !store->close()) { 0863 odfStore.closeManifestWriter(false); 0864 delete store; 0865 return false; 0866 } 0867 manifestWriter->addManifestEntry("meta.xml", "text/xml"); 0868 } else { 0869 d->lastErrorMessage = i18n("Not able to write '%1'. Partition full?", QString("meta.xml")); 0870 odfStore.closeManifestWriter(false); 0871 delete store; 0872 return false; 0873 } 0874 0875 if (d->docRdf && !d->docRdf->saveOasis(store, manifestWriter)) { 0876 d->lastErrorMessage = i18n("Not able to write RDF metadata. Partition full?"); 0877 odfStore.closeManifestWriter(false); 0878 delete store; 0879 return false; 0880 } 0881 0882 if (store->open("Thumbnails/thumbnail.png")) { 0883 if (!saveOasisPreview(store, manifestWriter) || !store->close()) { 0884 d->lastErrorMessage = i18n("Error while trying to write '%1'. Partition full?", QString("Thumbnails/thumbnail.png")); 0885 odfStore.closeManifestWriter(false); 0886 delete store; 0887 return false; 0888 } 0889 // No manifest entry! 0890 } else { 0891 d->lastErrorMessage = i18n("Not able to write '%1'. Partition full?", QString("Thumbnails/thumbnail.png")); 0892 odfStore.closeManifestWriter(false); 0893 delete store; 0894 return false; 0895 } 0896 0897 if (!d->versionInfo.isEmpty()) { 0898 if (store->open("VersionList.xml")) { 0899 KoStoreDevice dev(store); 0900 KoXmlWriter *xmlWriter = KoOdfWriteStore::createOasisXmlWriter(&dev, 0901 "VL:version-list"); 0902 for (int i = 0; i < d->versionInfo.size(); ++i) { 0903 KoVersionInfo *version = &d->versionInfo[i]; 0904 xmlWriter->startElement("VL:version-entry"); 0905 xmlWriter->addAttribute("VL:title", version->title); 0906 xmlWriter->addAttribute("VL:comment", version->comment); 0907 xmlWriter->addAttribute("VL:creator", version->saved_by); 0908 xmlWriter->addAttribute("dc:date-time", version->date.toString(Qt::ISODate)); 0909 xmlWriter->endElement(); 0910 } 0911 xmlWriter->endElement(); // root element 0912 xmlWriter->endDocument(); 0913 delete xmlWriter; 0914 store->close(); 0915 manifestWriter->addManifestEntry("VersionList.xml", "text/xml"); 0916 0917 for (int i = 0; i < d->versionInfo.size(); ++i) { 0918 KoVersionInfo *version = &d->versionInfo[i]; 0919 store->addDataToFile(version->data, "Versions/" + version->title); 0920 } 0921 } else { 0922 d->lastErrorMessage = i18n("Not able to write '%1'. Partition full?", QString("VersionList.xml")); 0923 odfStore.closeManifestWriter(false); 0924 delete store; 0925 return false; 0926 } 0927 } 0928 0929 // Write out manifest file 0930 if (!odfStore.closeManifestWriter()) { 0931 d->lastErrorMessage = i18n("Error while trying to write '%1'. Partition full?", QString("META-INF/manifest.xml")); 0932 delete store; 0933 return false; 0934 } 0935 0936 // Remember the given password, if necessary 0937 if (store->isEncrypted() && !d->isExporting) 0938 d->password = store->password(); 0939 0940 delete store; 0941 0942 return true; 0943 } 0944 0945 bool KoDocument::saveNativeFormatCalligra(KoStore *store) 0946 { 0947 debugMain << "Saving root"; 0948 if (store->open("root")) { 0949 KoStoreDevice dev(store); 0950 if (!saveToStream(&dev) || !store->close()) { 0951 debugMain << "saveToStream failed"; 0952 delete store; 0953 return false; 0954 } 0955 } else { 0956 d->lastErrorMessage = i18n("Not able to write '%1'. Partition full?", QString("maindoc.xml")); 0957 delete store; 0958 return false; 0959 } 0960 if (store->open("documentinfo.xml")) { 0961 QDomDocument doc = KoDocument::createDomDocument("document-info" 0962 /*DTD name*/, "document-info" /*tag name*/, "1.1"); 0963 0964 doc = d->docInfo->save(doc); 0965 KoStoreDevice dev(store); 0966 0967 QByteArray s = doc.toByteArray(); // this is already Utf8! 0968 (void)dev.write(s.data(), s.size()); 0969 (void)store->close(); 0970 } 0971 0972 if (store->open("preview.png")) { 0973 // ### TODO: missing error checking (The partition could be full!) 0974 savePreview(store); 0975 (void)store->close(); 0976 } 0977 0978 if (!completeSaving(store)) { 0979 delete store; 0980 return false; 0981 } 0982 debugMain << "Saving done of url:" << url().url(); 0983 if (!store->finalize()) { 0984 delete store; 0985 return false; 0986 } 0987 // Success 0988 delete store; 0989 return true; 0990 } 0991 0992 bool KoDocument::saveToStream(QIODevice *dev) 0993 { 0994 QDomDocument doc = saveXML(); 0995 // Save to buffer 0996 QByteArray s = doc.toByteArray(); // utf8 already 0997 dev->open(QIODevice::WriteOnly); 0998 int nwritten = dev->write(s.data(), s.size()); 0999 if (nwritten != (int)s.size()) 1000 warnMain << "wrote " << nwritten << "- expected" << s.size(); 1001 return nwritten == (int)s.size(); 1002 } 1003 1004 QString KoDocument::checkImageMimeTypes(const QString &mimeType, const QUrl &url) const 1005 { 1006 if (!url.isLocalFile()) return mimeType; 1007 1008 if (url.toLocalFile().endsWith(".kpp")) return "image/png"; 1009 1010 QStringList imageMimeTypes; 1011 imageMimeTypes << "image/jpeg" 1012 << "image/x-psd" << "image/photoshop" << "image/x-photoshop" << "image/x-vnd.adobe.photoshop" << "image/vnd.adobe.photoshop" 1013 << "image/x-portable-pixmap" << "image/x-portable-graymap" << "image/x-portable-bitmap" 1014 << "application/illustrator" 1015 << "application/photoshop" 1016 << "application/pdf" 1017 << "image/x-exr" 1018 << "image/x-xcf" 1019 << "image/x-eps" 1020 << "image/png" 1021 << "image/bmp" << "image/x-xpixmap" << "image/gif" << "image/x-xbitmap" 1022 << "image/tiff" 1023 << "image/jp2"; 1024 1025 if (!imageMimeTypes.contains(mimeType)) return mimeType; 1026 1027 QFile f(url.toLocalFile()); 1028 f.open(QIODevice::ReadOnly); 1029 QByteArray ba = f.read(qMin(f.size(), (qint64)512)); // should be enough for images 1030 QMimeType mime = QMimeDatabase().mimeTypeForData(ba); 1031 f.close(); 1032 1033 return mime.name(); 1034 } 1035 1036 // Called for embedded documents 1037 bool KoDocument::saveToStore(KoStore *_store, const QString & _path) 1038 { 1039 debugMain << "Saving document to store" << _path; 1040 1041 _store->pushDirectory(); 1042 // Use the path as the internal url 1043 if (_path.startsWith(STORE_PROTOCOL)) 1044 setUrl(QUrl(_path)); 1045 else // ugly hack to pass a relative URI 1046 setUrl(QUrl(INTERNAL_PREFIX + _path)); 1047 1048 // In the current directory we're the king :-) 1049 if (_store->open("root")) { 1050 KoStoreDevice dev(_store); 1051 if (!saveToStream(&dev)) { 1052 _store->close(); 1053 return false; 1054 } 1055 if (!_store->close()) 1056 return false; 1057 } 1058 1059 if (!completeSaving(_store)) 1060 return false; 1061 1062 // Now that we're done leave the directory again 1063 _store->popDirectory(); 1064 1065 debugMain << "Saved document to store"; 1066 1067 return true; 1068 } 1069 1070 bool KoDocument::saveOasisPreview(KoStore *store, KoXmlWriter *manifestWriter) 1071 { 1072 const QPixmap pix = generatePreview(QSize(128, 128)); 1073 if (pix.isNull()) 1074 return true; //no thumbnail to save, but the process succeeded 1075 1076 QImage preview(pix.toImage().convertToFormat(QImage::Format_ARGB32, Qt::ColorOnly)); 1077 1078 if (preview.isNull()) 1079 return false; //thumbnail to save, but the process failed 1080 1081 // ### TODO: freedesktop.org Thumbnail specification (date...) 1082 KoStoreDevice io(store); 1083 if (!io.open(QIODevice::WriteOnly)) 1084 return false; 1085 if (! preview.save(&io, "PNG", 0)) 1086 return false; 1087 io.close(); 1088 manifestWriter->addManifestEntry("Thumbnails/thumbnail.png", "image/png"); 1089 return true; 1090 } 1091 1092 bool KoDocument::savePreview(KoStore *store) 1093 { 1094 QPixmap pix = generatePreview(QSize(256, 256)); 1095 const QImage preview(pix.toImage().convertToFormat(QImage::Format_ARGB32, Qt::ColorOnly)); 1096 KoStoreDevice io(store); 1097 if (!io.open(QIODevice::WriteOnly)) 1098 return false; 1099 if (! preview.save(&io, "PNG")) // ### TODO What is -9 in quality terms? 1100 return false; 1101 io.close(); 1102 return true; 1103 } 1104 1105 QPixmap KoDocument::generatePreview(const QSize& size) 1106 { 1107 qreal docWidth, docHeight; 1108 int pixmapSize = qMax(size.width(), size.height()); 1109 1110 if (d->pageLayout.width > 1.0) { 1111 docWidth = d->pageLayout.width / 72 * KoDpi::dpiX(); 1112 docHeight = d->pageLayout.height / 72 * KoDpi::dpiY(); 1113 } else { 1114 // If we don't have a page layout, just draw the top left hand corner 1115 docWidth = 500.0; 1116 docHeight = 500.0; 1117 } 1118 1119 qreal ratio = docWidth / docHeight; 1120 1121 int previewWidth, previewHeight; 1122 if (ratio > 1.0) { 1123 previewWidth = (int) pixmapSize; 1124 previewHeight = (int)(pixmapSize / ratio); 1125 } else { 1126 previewWidth = (int)(pixmapSize * ratio); 1127 previewHeight = (int) pixmapSize; 1128 } 1129 1130 QPixmap pix((int)docWidth, (int)docHeight); 1131 1132 pix.fill(QColor(245, 245, 245)); 1133 1134 QRect rc(0, 0, pix.width(), pix.height()); 1135 1136 QPainter p; 1137 p.begin(&pix); 1138 paintContent(p, rc); 1139 p.end(); 1140 1141 return pix.scaled(QSize(previewWidth, previewHeight), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); 1142 } 1143 1144 QString KoDocument::autoSaveFile(const QString & path) const 1145 { 1146 QString retval; 1147 1148 // Using the extension allows to avoid relying on the mime magic when opening 1149 QMimeType mime = QMimeDatabase().mimeTypeForName(nativeFormatMimeType()); 1150 if (! mime.isValid()) { 1151 qFatal("It seems your installation is broken/incomplete because we failed to load the native mimetype \"%s\".", nativeFormatMimeType().constData()); 1152 } 1153 const QString extension = mime.preferredSuffix(); 1154 1155 if (path.isEmpty()) { 1156 // Never saved? 1157 #ifdef Q_OS_WIN 1158 // On Windows, use the temp location (https://bugs.kde.org/show_bug.cgi?id=314921) 1159 retval = QString("%1/.%2-%3-%4-autosave%5").arg(QDir::tempPath()).arg(d->parentPart->componentData().componentName()).arg(QApplication::applicationPid()).arg(objectName()).arg(extension); 1160 #else 1161 // On Linux, use a temp file in $HOME then. Mark it with the pid so two instances don't overwrite each other's autosave file 1162 retval = QString("%1/.%2-%3-%4-autosave%5").arg(QDir::homePath()).arg(d->parentPart->componentData().componentName()).arg(QApplication::applicationPid()).arg(objectName()).arg(extension); 1163 #endif 1164 } else { 1165 QUrl url = QUrl::fromLocalFile(path); 1166 Q_ASSERT(url.isLocalFile()); 1167 QString dir = QFileInfo(url.toLocalFile()).absolutePath(); 1168 QString filename = url.fileName(); 1169 retval = QString("%1.%2-autosave%3").arg(dir).arg(filename).arg(extension); 1170 } 1171 return retval; 1172 } 1173 1174 void KoDocument::setDisregardAutosaveFailure(bool disregardFailure) 1175 { 1176 d->disregardAutosaveFailure = disregardFailure; 1177 } 1178 1179 bool KoDocument::importDocument(const QUrl &_url) 1180 { 1181 bool ret; 1182 1183 debugMain << Q_FUNC_INFO << "url=" << _url.url(); 1184 d->isImporting = true; 1185 1186 // open... 1187 ret = openUrl(_url); 1188 1189 // reset url & m_file (kindly? set by KoParts::openUrl()) to simulate a 1190 // File --> Import 1191 if (ret) { 1192 debugMain << "success, resetting url"; 1193 resetURL(); 1194 setTitleModified(); 1195 } 1196 1197 d->isImporting = false; 1198 1199 return ret; 1200 } 1201 1202 1203 bool KoDocument::openUrl(const QUrl &_url) 1204 { 1205 debugMain << Q_FUNC_INFO << "url=" << _url.url(); 1206 d->lastErrorMessage.clear(); 1207 1208 // Reimplemented, to add a check for autosave files and to improve error reporting 1209 if (!_url.isValid()) { 1210 d->lastErrorMessage = i18n("Malformed URL\n%1", _url.url()); // ## used anywhere ? 1211 return false; 1212 } 1213 1214 abortLoad(); 1215 1216 QUrl url(_url); 1217 bool autosaveOpened = false; 1218 d->isLoading = true; 1219 if (url.isLocalFile() && d->shouldCheckAutoSaveFile) { 1220 QString file = url.toLocalFile(); 1221 QString asf = autoSaveFile(file); 1222 if (QFile::exists(asf)) { 1223 //debugMain <<"asf=" << asf; 1224 // ## TODO compare timestamps ? 1225 int res = KMessageBox::warningYesNoCancel(0, 1226 i18n("An autosaved file exists for this document.\nDo you want to open it instead?")); 1227 switch (res) { 1228 case KMessageBox::Yes : 1229 url.setPath(asf); 1230 autosaveOpened = true; 1231 break; 1232 case KMessageBox::No : 1233 QFile::remove(asf); 1234 break; 1235 default: // Cancel 1236 d->isLoading = false; 1237 return false; 1238 } 1239 } 1240 } 1241 1242 bool ret = openUrlInternal(url); 1243 1244 if (autosaveOpened) { 1245 resetURL(); // Force save to act like 'Save As' 1246 setReadWrite(true); // enable save button 1247 setModified(true); 1248 } 1249 else { 1250 d->parentPart->addRecentURLToAllMainWindows(_url); 1251 1252 if (ret) { 1253 // Detect readonly local-files; remote files are assumed to be writable, unless we add a KIO::stat here (async). 1254 KFileItem file(url, mimeType(), KFileItem::Unknown); 1255 setReadWrite(file.isWritable()); 1256 } 1257 } 1258 return ret; 1259 } 1260 1261 // It seems that people have started to save .docx files as .doc and 1262 // similar for xls and ppt. So let's make a small replacement table 1263 // here and see if we can open the files anyway. 1264 static const struct MimetypeReplacement { 1265 const char *typeFromName; // If the mime type from the name is this... 1266 const char *typeFromContents; // ...and findByFileContents() reports this type... 1267 const char *useThisType; // ...then use this type for real. 1268 } replacementMimetypes[] = { 1269 // doc / docx 1270 { 1271 "application/msword", 1272 "application/vnd.openxmlformats-officedocument.wordprocessingml.document", 1273 "application/vnd.openxmlformats-officedocument.wordprocessingml.document" 1274 }, 1275 { 1276 "application/msword", 1277 "application/zip", 1278 "application/vnd.openxmlformats-officedocument.wordprocessingml.document" 1279 }, 1280 { 1281 "application/vnd.openxmlformats-officedocument.wordprocessingml.document", 1282 "application/msword", 1283 "application/msword" 1284 }, 1285 { 1286 "application/vnd.openxmlformats-officedocument.wordprocessingml.document", 1287 "application/x-ole-storage", 1288 "application/msword" 1289 }, 1290 1291 // xls / xlsx 1292 { 1293 "application/vnd.ms-excel", 1294 "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", 1295 "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" 1296 }, 1297 { 1298 "application/vnd.ms-excel", 1299 "application/zip", 1300 "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" 1301 }, 1302 { 1303 "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", 1304 "application/vnd.ms-excel", 1305 "application/vnd.ms-excel" 1306 }, 1307 { 1308 "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", 1309 "application/x-ole-storage", 1310 "application/vnd.ms-excel" 1311 }, 1312 1313 // ppt / pptx 1314 { 1315 "application/vnd.ms-powerpoint", 1316 "application/vnd.openxmlformats-officedocument.presentationml.presentation", 1317 "application/vnd.openxmlformats-officedocument.presentationml.presentation" 1318 }, 1319 { 1320 "application/vnd.ms-powerpoint", 1321 "application/zip", 1322 "application/vnd.openxmlformats-officedocument.presentationml.presentation" 1323 }, 1324 { 1325 "application/vnd.openxmlformats-officedocument.presentationml.presentation", 1326 "application/vnd.ms-powerpoint", 1327 "application/vnd.ms-powerpoint" 1328 }, 1329 { 1330 "application/vnd.openxmlformats-officedocument.presentationml.presentation", 1331 "application/x-ole-storage", 1332 "application/vnd.ms-powerpoint" 1333 } 1334 }; 1335 1336 bool KoDocument::openFile() 1337 { 1338 //debugMain <<"for" << localFilePath(); 1339 if (!QFile::exists(localFilePath())) { 1340 QApplication::restoreOverrideCursor(); 1341 if (d->autoErrorHandlingEnabled) 1342 // Maybe offer to create a new document with that name ? 1343 KMessageBox::error(0, i18n("The file %1 does not exist.", localFilePath())); 1344 d->isLoading = false; 1345 return false; 1346 } 1347 1348 QApplication::setOverrideCursor(Qt::WaitCursor); 1349 1350 d->specialOutputFlag = 0; 1351 QByteArray _native_format = nativeFormatMimeType(); 1352 1353 QUrl u = QUrl::fromLocalFile(localFilePath()); 1354 QString typeName = mimeType(); 1355 1356 if (typeName.isEmpty()) { 1357 typeName = QMimeDatabase().mimeTypeForUrl(u).name(); 1358 } 1359 1360 // for images, always check content. 1361 typeName = checkImageMimeTypes(typeName, u); 1362 1363 // Sometimes it seems that arguments().mimeType() contains a much 1364 // too generic mime type. In that case, let's try some educated 1365 // guesses based on what we know about file extension. 1366 // 1367 // FIXME: Should we just ignore this and always call 1368 // KMimeType::findByUrl()? David Faure says that it's 1369 // impossible for findByUrl() to fail to initiate the 1370 // mimetype for "*.doc" to application/msword. This hints 1371 // that we should do that. But why does it happen like 1372 // this at all? 1373 if (typeName == "application/zip") { 1374 QString filename = u.fileName(); 1375 1376 // None of doc, xls or ppt are really zip files. But docx, 1377 // xlsx and pptx are. This miscategorization seems to only 1378 // crop up when there is a, say, docx file saved as doc. The 1379 // conversion to the docx mimetype will happen below. 1380 if (filename.endsWith(".doc")) 1381 typeName = "application/msword"; 1382 else if (filename.endsWith(".xls")) 1383 typeName = "application/vnd.ms-excel"; 1384 else if (filename.endsWith(".ppt")) 1385 typeName = "application/vnd.ms-powerpoint"; 1386 1387 // Potentially more guesses here... 1388 } else if (typeName == "application/x-ole-storage") { 1389 QString filename = u.fileName(); 1390 1391 // None of docx, xlsx or pptx are really OLE files. But doc, 1392 // xls and ppt are. This miscategorization seems to only crop 1393 // up when there is a, say, doc file saved as docx. The 1394 // conversion to the doc mimetype will happen below. 1395 if (filename.endsWith(".docx")) 1396 typeName = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; 1397 else if (filename.endsWith(".xlsx")) 1398 typeName = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; 1399 else if (filename.endsWith(".pptx")) 1400 typeName = "application/vnd.openxmlformats-officedocument.presentationml.presentation"; 1401 1402 // Potentially more guesses here... 1403 } 1404 //debugMain << "mimetypes 3:" << typeName; 1405 1406 // In some cases docx files are saved as doc and similar. We have 1407 // a small hardcoded table for those cases. Check if this is 1408 // applicable here. 1409 for (uint i = 0; i < sizeof(replacementMimetypes) / sizeof(struct MimetypeReplacement); ++i) { 1410 const MimetypeReplacement *replacement = &replacementMimetypes[i]; 1411 1412 if (typeName == replacement->typeFromName) { 1413 //debugMain << "found potential replacement target:" << typeName; 1414 // QT5TODO: this needs a new look with the different behaviour of QMimeDatabase 1415 QString typeFromContents = QMimeDatabase().mimeTypeForUrl(u).name(); 1416 //debugMain << "found potential replacement:" << typeFromContents; 1417 if (typeFromContents == replacement->typeFromContents) { 1418 typeName = replacement->useThisType; 1419 //debugMain << "So really use this:" << typeName; 1420 break; 1421 } 1422 } 1423 } 1424 //debugMain << "mimetypes 4:" << typeName; 1425 1426 // Allow to open backup files, don't keep the mimetype application/x-trash. 1427 if (typeName == "application/x-trash") { 1428 QString path = u.path(); 1429 QMimeDatabase db; 1430 QMimeType mime = db.mimeTypeForName(typeName); 1431 const QStringList patterns = mime.isValid() ? mime.globPatterns() : QStringList(); 1432 // Find the extension that makes it a backup file, and remove it 1433 for (QStringList::ConstIterator it = patterns.begin(); it != patterns.end(); ++it) { 1434 QString ext = *it; 1435 if (!ext.isEmpty() && ext[0] == '*') { 1436 ext.remove(0, 1); 1437 if (path.endsWith(ext)) { 1438 path.chop(ext.length()); 1439 break; 1440 } 1441 } 1442 } 1443 typeName = db.mimeTypeForFile(path, QMimeDatabase::MatchExtension).name(); 1444 } 1445 1446 // Special case for flat XML files (e.g. using directory store) 1447 if (u.fileName() == "maindoc.xml" || u.fileName() == "content.xml" || typeName == "inode/directory") { 1448 typeName = _native_format; // Hmm, what if it's from another app? ### Check mimetype 1449 d->specialOutputFlag = SaveAsDirectoryStore; 1450 debugMain << "loading" << u.fileName() << ", using directory store for" << localFilePath() << "; typeName=" << typeName; 1451 } 1452 debugMain << localFilePath() << "type:" << typeName; 1453 1454 QString importedFile = localFilePath(); 1455 1456 // create the main progress monitoring object for loading, this can 1457 // contain subtasks for filtering and loading 1458 KoProgressProxy *progressProxy = 0; 1459 if (d->progressProxy) { 1460 progressProxy = d->progressProxy; 1461 } 1462 1463 d->progressUpdater = new KoProgressUpdater(progressProxy, 1464 KoProgressUpdater::Unthreaded, 1465 d->profileStream); 1466 1467 d->progressUpdater->setReferenceTime(d->profileReferenceTime); 1468 d->progressUpdater->start(100, i18n("Opening Document")); 1469 1470 setupOpenFileSubProgress(); 1471 1472 if (!isNativeFormat(typeName.toLatin1())) { 1473 KoFilter::ConversionStatus status; 1474 importedFile = d->filterManager->importDocument(localFilePath(), typeName, status); 1475 if (status != KoFilter::OK) { 1476 QApplication::restoreOverrideCursor(); 1477 1478 QString msg; 1479 switch (status) { 1480 case KoFilter::OK: break; 1481 1482 case KoFilter::FilterCreationError: 1483 msg = i18n("Could not create the filter plugin"); break; 1484 1485 case KoFilter::CreationError: 1486 msg = i18n("Could not create the output document"); break; 1487 1488 case KoFilter::FileNotFound: 1489 msg = i18n("File not found"); break; 1490 1491 case KoFilter::StorageCreationError: 1492 msg = i18n("Cannot create storage"); break; 1493 1494 case KoFilter::BadMimeType: 1495 msg = i18n("Bad MIME type"); break; 1496 1497 case KoFilter::EmbeddedDocError: 1498 msg = i18n("Error in embedded document"); break; 1499 1500 case KoFilter::WrongFormat: 1501 msg = i18n("Format not recognized"); break; 1502 1503 case KoFilter::NotImplemented: 1504 msg = i18n("Not implemented"); break; 1505 1506 case KoFilter::ParsingError: 1507 msg = i18n("Parsing error"); break; 1508 1509 case KoFilter::PasswordProtected: 1510 msg = i18n("Document is password protected"); break; 1511 1512 case KoFilter::InvalidFormat: 1513 msg = i18n("Invalid file format"); break; 1514 1515 case KoFilter::InternalError: 1516 case KoFilter::UnexpectedEOF: 1517 case KoFilter::UnexpectedOpcode: 1518 case KoFilter::StupidError: // ?? what is this ?? 1519 case KoFilter::UsageError: 1520 msg = i18n("Internal error"); break; 1521 1522 case KoFilter::OutOfMemory: 1523 msg = i18n("Out of memory"); break; 1524 1525 case KoFilter::FilterEntryNull: 1526 msg = i18n("Empty Filter Plugin"); break; 1527 1528 case KoFilter::NoDocumentCreated: 1529 msg = i18n("Trying to load into the wrong kind of document"); break; 1530 1531 case KoFilter::DownloadFailed: 1532 msg = i18n("Failed to download remote file"); break; 1533 1534 case KoFilter::UserCancelled: 1535 case KoFilter::BadConversionGraph: 1536 // intentionally we do not prompt the error message here 1537 break; 1538 1539 default: msg = i18n("Unknown error"); break; 1540 } 1541 1542 if (d->autoErrorHandlingEnabled && !msg.isEmpty()) { 1543 QString errorMsg(i18n("Could not open %2.\nReason: %1.\n%3", msg, prettyPathOrUrl(), errorMessage())); 1544 KMessageBox::error(0, errorMsg); 1545 } 1546 1547 d->isLoading = false; 1548 delete d->progressUpdater; 1549 d->progressUpdater = 0; 1550 return false; 1551 } 1552 d->isEmpty = false; 1553 debugMain << "importedFile" << importedFile << "status:" << static_cast<int>(status); 1554 } 1555 1556 QApplication::restoreOverrideCursor(); 1557 1558 bool ok = true; 1559 1560 if (!importedFile.isEmpty()) { // Something to load (tmp or native file) ? 1561 // The filter, if any, has been applied. It's all native format now. 1562 if (!loadNativeFormat(importedFile)) { 1563 ok = false; 1564 if (d->autoErrorHandlingEnabled) { 1565 showLoadingErrorDialog(); 1566 } 1567 } 1568 } 1569 1570 if (importedFile != localFilePath()) { 1571 // We opened a temporary file (result of an import filter) 1572 // Set document URL to empty - we don't want to save in /tmp ! 1573 // But only if in readwrite mode (no saving problem otherwise) 1574 // -- 1575 // But this isn't true at all. If this is the result of an 1576 // import, then importedFile=temporary_file.kwd and 1577 // file/m_url=foreignformat.ext so m_url is correct! 1578 // So don't resetURL() or else the caption won't be set when 1579 // foreign files are opened (an annoying bug). 1580 // - Clarence 1581 // 1582 #if 0 1583 if (isReadWrite()) 1584 resetURL(); 1585 #endif 1586 1587 // remove temp file - uncomment this to debug import filters 1588 if (!importedFile.isEmpty()) { 1589 #ifndef NDEBUG 1590 if (!getenv("CALLIGRA_DEBUG_FILTERS")) 1591 #endif 1592 QFile::remove(importedFile); 1593 } 1594 } 1595 1596 if (ok) { 1597 setMimeTypeAfterLoading(typeName); 1598 1599 KNotification *notify = new KNotification("DocumentLoaded"); 1600 notify->setText(i18n("Document <i>%1</i> loaded", url().url())); 1601 notify->addContext("url", url().url()); 1602 QTimer::singleShot(0, notify, SLOT(sendEvent())); 1603 d->parentPart->deleteOpenPane(); 1604 } 1605 1606 if (progressUpdater()) { 1607 QPointer<KoUpdater> updater 1608 = progressUpdater()->startSubtask(1, "clear undo stack"); 1609 updater->setProgress(0); 1610 undoStack()->clear(); 1611 updater->setProgress(100); 1612 } 1613 delete d->progressUpdater; 1614 d->progressUpdater = 0; 1615 1616 d->isLoading = false; 1617 1618 return ok; 1619 } 1620 1621 KoProgressUpdater *KoDocument::progressUpdater() const 1622 { 1623 return d->progressUpdater; 1624 } 1625 1626 void KoDocument::setProgressProxy(KoProgressProxy *progressProxy) 1627 { 1628 d->progressProxy = progressProxy; 1629 } 1630 1631 KoProgressProxy* KoDocument::progressProxy() const 1632 { 1633 if (!d->progressProxy) { 1634 KoMainWindow *mainWindow = 0; 1635 if (d->parentPart->mainwindowCount() > 0) { 1636 mainWindow = d->parentPart->mainWindows()[0]; 1637 } 1638 d->progressProxy = new DocumentProgressProxy(mainWindow); 1639 } 1640 return d->progressProxy; 1641 } 1642 1643 // shared between openFile and koMainWindow's "create new empty document" code 1644 void KoDocument::setMimeTypeAfterLoading(const QString& mimeType) 1645 { 1646 d->mimeType = mimeType.toLatin1(); 1647 1648 d->outputMimeType = d->mimeType; 1649 1650 const bool needConfirm = !isNativeFormat(d->mimeType); 1651 setConfirmNonNativeSave(false, needConfirm); 1652 setConfirmNonNativeSave(true, needConfirm); 1653 } 1654 1655 // The caller must call store->close() if loadAndParse returns true. 1656 bool KoDocument::oldLoadAndParse(KoStore *store, const QString& filename, KoXmlDocument& doc) 1657 { 1658 //debugMain <<"Trying to open" << filename; 1659 1660 if (!store->open(filename)) { 1661 warnMain << "Entry " << filename << " not found!"; 1662 d->lastErrorMessage = i18n("Could not find %1", filename); 1663 return false; 1664 } 1665 // Error variables for QDomDocument::setContent 1666 QString errorMsg; 1667 int errorLine, errorColumn; 1668 bool ok = doc.setContent(store->device(), &errorMsg, &errorLine, &errorColumn); 1669 store->close(); 1670 if (!ok) { 1671 errorMain << "Parsing error in " << filename << "! Aborting!" << endl 1672 << " In line: " << errorLine << ", column: " << errorColumn << endl 1673 << " Error message: " << errorMsg << endl; 1674 d->lastErrorMessage = i18n("Parsing error in %1 at line %2, column %3\nError message: %4" 1675 , filename , errorLine, errorColumn , 1676 QCoreApplication::translate("QXml", errorMsg.toUtf8(), 0, 1677 QCoreApplication::UnicodeUTF8)); 1678 return false; 1679 } 1680 debugMain << "File" << filename << " loaded and parsed"; 1681 return true; 1682 } 1683 1684 bool KoDocument::loadNativeFormat(const QString & file_) 1685 { 1686 QString file = file_; 1687 QFileInfo fileInfo(file); 1688 if (!fileInfo.exists()) { // check duplicated from openUrl, but this is useful for templates 1689 d->lastErrorMessage = i18n("The file %1 does not exist.", file); 1690 return false; 1691 } 1692 if (!fileInfo.isFile()) { 1693 file += "/content.xml"; 1694 QFileInfo fileInfo2(file); 1695 if (!fileInfo2.exists() || !fileInfo2.isFile()) { 1696 d->lastErrorMessage = i18n("%1 is not a file." , file_); 1697 return false; 1698 } 1699 } 1700 1701 QApplication::setOverrideCursor(Qt::WaitCursor); 1702 1703 debugMain << file; 1704 1705 QFile in; 1706 bool isRawXML = false; 1707 if (d->specialOutputFlag != SaveAsDirectoryStore) { // Don't try to open a directory ;) 1708 in.setFileName(file); 1709 if (!in.open(QIODevice::ReadOnly)) { 1710 QApplication::restoreOverrideCursor(); 1711 d->lastErrorMessage = i18n("Could not open the file for reading (check read permissions)."); 1712 return false; 1713 } 1714 1715 char buf[6]; 1716 buf[5] = 0; 1717 int pos = 0; 1718 do { 1719 if (in.read(buf + pos , 1) < 1) { 1720 QApplication::restoreOverrideCursor(); 1721 in.close(); 1722 d->lastErrorMessage = i18n("Could not read the beginning of the file."); 1723 return false; 1724 } 1725 1726 if (QChar(buf[pos]).isSpace()) 1727 continue; 1728 pos++; 1729 } while (pos < 5); 1730 isRawXML = (qstrnicmp(buf, "<?xml", 5) == 0); 1731 if (! isRawXML) 1732 // also check for broken MathML files, which seem to be rather common 1733 isRawXML = (qstrnicmp(buf, "<math", 5) == 0); // file begins with <math ? 1734 //debugMain <<"PATTERN=" << buf; 1735 } 1736 // Is it plain XML? 1737 if (isRawXML) { 1738 in.seek(0); 1739 QString errorMsg; 1740 int errorLine; 1741 int errorColumn; 1742 KoXmlDocument doc = KoXmlDocument(true); 1743 bool res; 1744 if (doc.setContent(&in, &errorMsg, &errorLine, &errorColumn)) { 1745 res = loadXML(doc, 0); 1746 if (res) 1747 res = completeLoading(0); 1748 } else { 1749 errorMain << "Parsing Error! Aborting! (in KoDocument::loadNativeFormat (QFile))" << endl 1750 << " Line: " << errorLine << " Column: " << errorColumn << endl 1751 << " Message: " << errorMsg << endl; 1752 d->lastErrorMessage = i18n("parsing error in the main document at line %1, column %2\nError message: %3", errorLine, errorColumn, i18n(errorMsg.toUtf8())); 1753 res = false; 1754 } 1755 1756 QApplication::restoreOverrideCursor(); 1757 in.close(); 1758 d->isEmpty = false; 1759 return res; 1760 } else { // It's a calligra store (tar.gz, zip, directory, etc.) 1761 in.close(); 1762 1763 return loadNativeFormatFromStore(file); 1764 } 1765 } 1766 1767 bool KoDocument::loadNativeFormatFromStore(const QString& file) 1768 { 1769 KoStore::Backend backend = (d->specialOutputFlag == SaveAsDirectoryStore) ? KoStore::Directory : KoStore::Auto; 1770 KoStore *store = KoStore::createStore(file, KoStore::Read, "", backend); 1771 1772 if (store->bad()) { 1773 d->lastErrorMessage = i18n("Not a valid Calligra file: %1", file); 1774 delete store; 1775 QApplication::restoreOverrideCursor(); 1776 return false; 1777 } 1778 1779 // Remember that the file was encrypted 1780 if (d->specialOutputFlag == 0 && store->isEncrypted() && !d->isImporting) 1781 d->specialOutputFlag = SaveEncrypted; 1782 1783 const bool success = loadNativeFormatFromStoreInternal(store); 1784 1785 // Retrieve the password after loading the file, only then is it guaranteed to exist 1786 if (success && store->isEncrypted() && !d->isImporting) 1787 d->password = store->password(); 1788 1789 delete store; 1790 1791 return success; 1792 } 1793 1794 bool KoDocument::loadNativeFormatFromStore(QByteArray &data) 1795 { 1796 bool succes; 1797 KoStore::Backend backend = (d->specialOutputFlag == SaveAsDirectoryStore) ? KoStore::Directory : KoStore::Auto; 1798 QBuffer buffer(&data); 1799 KoStore *store = KoStore::createStore(&buffer, KoStore::Read, "", backend); 1800 1801 if (store->bad()) { 1802 delete store; 1803 return false; 1804 } 1805 1806 // Remember that the file was encrypted 1807 if (d->specialOutputFlag == 0 && store->isEncrypted() && !d->isImporting) 1808 d->specialOutputFlag = SaveEncrypted; 1809 1810 succes = loadNativeFormatFromStoreInternal(store); 1811 1812 // Retrieve the password after loading the file, only then is it guaranteed to exist 1813 if (succes && store->isEncrypted() && !d->isImporting) 1814 d->password = store->password(); 1815 1816 delete store; 1817 1818 return succes; 1819 } 1820 1821 bool KoDocument::loadNativeFormatFromStoreInternal(KoStore *store) 1822 { 1823 bool oasis = true; 1824 1825 if (oasis && store->hasFile("manifest.rdf") && d->docRdf) { 1826 d->docRdf->loadOasis(store); 1827 } 1828 1829 // OASIS/OOo file format? 1830 if (store->hasFile("content.xml")) { 1831 1832 1833 // We could check the 'mimetype' file, but let's skip that and be tolerant. 1834 1835 if (!loadOasisFromStore(store)) { 1836 QApplication::restoreOverrideCursor(); 1837 return false; 1838 } 1839 1840 } else if (store->hasFile("root") || store->hasFile("maindoc.xml")) { // Fallback to "old" file format (maindoc.xml) 1841 1842 1843 1844 oasis = false; 1845 1846 KoXmlDocument doc = KoXmlDocument(true); 1847 1848 bool ok = oldLoadAndParse(store, "root", doc); 1849 if (ok) 1850 ok = loadXML(doc, store); 1851 if (!ok) { 1852 QApplication::restoreOverrideCursor(); 1853 return false; 1854 } 1855 1856 } else { 1857 errorMain << "ERROR: No maindoc.xml" << endl; 1858 d->lastErrorMessage = i18n("Invalid document: no file 'maindoc.xml'."); 1859 QApplication::restoreOverrideCursor(); 1860 return false; 1861 } 1862 1863 if (oasis && store->hasFile("meta.xml")) { 1864 KoXmlDocument metaDoc; 1865 KoOdfReadStore oasisStore(store); 1866 if (oasisStore.loadAndParse("meta.xml", metaDoc, d->lastErrorMessage)) { 1867 d->docInfo->loadOasis(metaDoc); 1868 } 1869 } else if (!oasis && store->hasFile("documentinfo.xml")) { 1870 KoXmlDocument doc = KoXmlDocument(true); 1871 if (oldLoadAndParse(store, "documentinfo.xml", doc)) { 1872 d->docInfo->load(doc); 1873 } 1874 } else { 1875 //kDebug( 30003 ) <<"cannot open document info"; 1876 delete d->docInfo; 1877 d->docInfo = new KoDocumentInfo(this); 1878 } 1879 1880 if (oasis && store->hasFile("VersionList.xml")) { 1881 KNotification *notify = new KNotification("DocumentHasVersions"); 1882 notify->setText(i18n("Document <i>%1</i> contains several versions. Go to File->Versions to open an old version.", store->urlOfStore().url())); 1883 notify->addContext("url", store->urlOfStore().url()); 1884 QTimer::singleShot(0, notify, SLOT(sendEvent())); 1885 1886 KoXmlDocument versionInfo; 1887 KoOdfReadStore oasisStore(store); 1888 if (oasisStore.loadAndParse("VersionList.xml", versionInfo, d->lastErrorMessage)) { 1889 KoXmlNode list = KoXml::namedItemNS(versionInfo, KoXmlNS::VL, "version-list"); 1890 KoXmlElement e; 1891 forEachElement(e, list) { 1892 if (e.localName() == "version-entry" && e.namespaceURI() == KoXmlNS::VL) { 1893 KoVersionInfo version; 1894 version.comment = e.attribute("comment"); 1895 version.title = e.attribute("title"); 1896 version.saved_by = e.attribute("creator"); 1897 version.date = QDateTime::fromString(e.attribute("date-time"), Qt::ISODate); 1898 store->extractFile("Versions/" + version.title, version.data); 1899 d->versionInfo.append(version); 1900 } 1901 } 1902 } 1903 } 1904 1905 bool res = completeLoading(store); 1906 QApplication::restoreOverrideCursor(); 1907 d->isEmpty = false; 1908 return res; 1909 } 1910 1911 // For embedded documents 1912 bool KoDocument::loadFromStore(KoStore *_store, const QString& url) 1913 { 1914 if (_store->open(url)) { 1915 KoXmlDocument doc = KoXmlDocument(true); 1916 doc.setContent(_store->device()); 1917 if (!loadXML(doc, _store)) { 1918 _store->close(); 1919 return false; 1920 } 1921 _store->close(); 1922 } else { 1923 qWarning() << "couldn't open " << url; 1924 } 1925 1926 _store->pushDirectory(); 1927 // Store as document URL 1928 if (url.startsWith(STORE_PROTOCOL)) { 1929 setUrl(QUrl::fromUserInput(url)); 1930 } else { 1931 setUrl(QUrl(INTERNAL_PREFIX + url)); 1932 _store->enterDirectory(url); 1933 } 1934 1935 bool result = completeLoading(_store); 1936 1937 // Restore the "old" path 1938 _store->popDirectory(); 1939 1940 return result; 1941 } 1942 1943 bool KoDocument::loadOasisFromStore(KoStore *store) 1944 { 1945 KoOdfReadStore odfStore(store); 1946 if (! odfStore.loadAndParse(d->lastErrorMessage)) { 1947 return false; 1948 } 1949 return loadOdf(odfStore); 1950 } 1951 1952 bool KoDocument::addVersion(const QString& comment) 1953 { 1954 debugMain << "Saving the new version...."; 1955 1956 KoStore::Backend backend = KoStore::Auto; 1957 if (d->specialOutputFlag != 0) 1958 return false; 1959 1960 QByteArray mimeType = d->outputMimeType; 1961 QByteArray nativeOasisMime = nativeOasisMimeType(); 1962 bool oasis = !mimeType.isEmpty() && (mimeType == nativeOasisMime || mimeType == nativeOasisMime + "-template"); 1963 1964 if (!oasis) 1965 return false; 1966 1967 // TODO: use std::auto_ptr or create store on stack [needs API fixing], 1968 // to remove all the 'delete store' in all the branches 1969 QByteArray data; 1970 QBuffer buffer(&data); 1971 KoStore *store = KoStore::createStore(&buffer/*file*/, KoStore::Write, mimeType, backend); 1972 if (store->bad()) { 1973 delete store; 1974 return false; 1975 } 1976 1977 debugMain << "Saving to OASIS format"; 1978 KoOdfWriteStore odfStore(store); 1979 1980 KoXmlWriter *manifestWriter = odfStore.manifestWriter(mimeType); 1981 Q_UNUSED(manifestWriter); // XXX why? 1982 1983 KoEmbeddedDocumentSaver embeddedSaver; 1984 SavingContext documentContext(odfStore, embeddedSaver); 1985 1986 if (!saveOdf(documentContext)) { 1987 debugMain << "saveOdf failed"; 1988 delete store; 1989 return false; 1990 } 1991 1992 // Save embedded objects 1993 if (!embeddedSaver.saveEmbeddedDocuments(documentContext)) { 1994 debugMain << "save embedded documents failed"; 1995 delete store; 1996 return false; 1997 } 1998 1999 // Write out manifest file 2000 if (!odfStore.closeManifestWriter()) { 2001 d->lastErrorMessage = i18n("Error while trying to write '%1'. Partition full?", QString("META-INF/manifest.xml")); 2002 delete store; 2003 return false; 2004 } 2005 2006 if (!store->finalize()) { 2007 delete store; 2008 return false; 2009 } 2010 delete store; 2011 2012 KoVersionInfo version; 2013 version.comment = comment; 2014 version.title = "Version" + QString::number(d->versionInfo.count() + 1); 2015 version.saved_by = documentInfo()->authorInfo("creator"); 2016 version.date = QDateTime::currentDateTime(); 2017 version.data = data; 2018 d->versionInfo.append(version); 2019 2020 save(); //finally save the document + the new version 2021 return true; 2022 } 2023 2024 bool KoDocument::isStoredExtern() const 2025 { 2026 return !storeInternal() && hasExternURL(); 2027 } 2028 2029 2030 void KoDocument::setModified() 2031 { 2032 d->modified = true; 2033 } 2034 2035 void KoDocument::setModified(bool mod) 2036 { 2037 if (isAutosaving()) // ignore setModified calls due to autosaving 2038 return; 2039 2040 if ( !d->readwrite && mod ) { 2041 qCritical(/*1000*/) << "Can't set a read-only document to 'modified' !" << endl; 2042 return; 2043 } 2044 2045 //debugMain<<" url:" << url.path(); 2046 //debugMain<<" mod="<<mod<<" MParts mod="<<KoParts::ReadWritePart::isModified()<<" isModified="<<isModified(); 2047 2048 if (mod && !d->modifiedAfterAutosave) { 2049 // First change since last autosave -> start the autosave timer 2050 setAutoSave(d->autoSaveDelay); 2051 } 2052 d->modifiedAfterAutosave = mod; 2053 2054 if (mod == isModified()) 2055 return; 2056 2057 d->modified = mod; 2058 2059 if (mod) { 2060 d->isEmpty = false; 2061 documentInfo()->updateParameters(); 2062 } 2063 2064 // This influences the title 2065 setTitleModified(); 2066 emit modified(mod); 2067 } 2068 2069 bool KoDocument::alwaysAllowSaving() const 2070 { 2071 return d->alwaysAllowSaving; 2072 } 2073 2074 void KoDocument::setAlwaysAllowSaving(bool allow) 2075 { 2076 d->alwaysAllowSaving = allow; 2077 } 2078 2079 int KoDocument::queryCloseDia() 2080 { 2081 //debugMain; 2082 2083 QString name; 2084 if (documentInfo()) { 2085 name = documentInfo()->aboutInfo("title"); 2086 } 2087 if (name.isEmpty()) 2088 name = url().fileName(); 2089 2090 if (name.isEmpty()) 2091 name = i18n("Untitled"); 2092 2093 int res = KMessageBox::warningYesNoCancel(0, 2094 i18n("<p>The document <b>'%1'</b> has been modified.</p><p>Do you want to save it?</p>", name)); 2095 2096 switch (res) { 2097 case KMessageBox::Yes : 2098 save(); // NOTE: External files always in native format. ###TODO: Handle non-native format 2099 setModified(false); // Now when queryClose() is called by closeEvent it won't do anything. 2100 break; 2101 case KMessageBox::No : 2102 removeAutoSaveFiles(); 2103 setModified(false); // Now when queryClose() is called by closeEvent it won't do anything. 2104 break; 2105 default : // case KMessageBox::Cancel : 2106 return res; // cancels the rest of the files 2107 } 2108 return res; 2109 } 2110 2111 QString KoDocument::prettyPathOrUrl() const 2112 { 2113 QString _url(url().toDisplayString()); 2114 #ifdef Q_WS_WIN 2115 if (url().isLocalFile()) { 2116 _url = QDir::convertSeparators(_url); 2117 } 2118 #endif 2119 return _url; 2120 } 2121 2122 // Get caption from document info (title(), in about page) 2123 QString KoDocument::caption() const 2124 { 2125 QString c; 2126 if (documentInfo()) { 2127 c = documentInfo()->aboutInfo("title"); 2128 } 2129 const QString _url(url().fileName()); 2130 if (!c.isEmpty() && !_url.isEmpty()) { 2131 c = QString("%1 - %2").arg(c).arg(_url); 2132 } 2133 else if (c.isEmpty()) { 2134 c = _url; // Fall back to document URL 2135 } 2136 return c; 2137 } 2138 2139 void KoDocument::setTitleModified() 2140 { 2141 emit titleModified(caption(), isModified()); 2142 } 2143 2144 bool KoDocument::completeLoading(KoStore*) 2145 { 2146 return true; 2147 } 2148 2149 bool KoDocument::completeSaving(KoStore*) 2150 { 2151 return true; 2152 } 2153 2154 QDomDocument KoDocument::createDomDocument(const QString& tagName, const QString& version) const 2155 { 2156 return createDomDocument(d->parentPart->componentData().componentName(), tagName, version); 2157 } 2158 2159 //static 2160 QDomDocument KoDocument::createDomDocument(const QString& appName, const QString& tagName, const QString& version) 2161 { 2162 QDomImplementation impl; 2163 QString url = QString("http://www.calligra.org/DTD/%1-%2.dtd").arg(appName).arg(version); 2164 QDomDocumentType dtype = impl.createDocumentType(tagName, 2165 QString("-//KDE//DTD %1 %2//EN").arg(appName).arg(version), 2166 url); 2167 // The namespace URN doesn't need to include the version number. 2168 QString namespaceURN = QString("http://www.calligra.org/DTD/%1").arg(appName); 2169 QDomDocument doc = impl.createDocument(namespaceURN, tagName, dtype); 2170 doc.insertBefore(doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\""), doc.documentElement()); 2171 return doc; 2172 } 2173 2174 QDomDocument KoDocument::saveXML() 2175 { 2176 errorMain << "not implemented" << endl; 2177 d->lastErrorMessage = i18n("Internal error: saveXML not implemented"); 2178 return QDomDocument(); 2179 } 2180 2181 bool KoDocument::isNativeFormat(const QByteArray& mimetype) const 2182 { 2183 if (mimetype == nativeFormatMimeType()) 2184 return true; 2185 return extraNativeMimeTypes().contains(mimetype); 2186 } 2187 2188 int KoDocument::supportedSpecialFormats() const 2189 { 2190 // Apps which support special output flags can add reimplement and add to this. 2191 // E.g. this is how did "saving in the 1.1 format". 2192 // SaveAsDirectoryStore is a given since it's implemented by KoDocument itself. 2193 // SaveEncrypted is implemented in KoDocument as well, if QCA2 was found. 2194 #ifdef QCA2 2195 return SaveAsDirectoryStore | SaveEncrypted; 2196 #else 2197 return SaveAsDirectoryStore; 2198 #endif 2199 } 2200 2201 void KoDocument::setErrorMessage(const QString& errMsg) 2202 { 2203 d->lastErrorMessage = errMsg; 2204 } 2205 2206 QString KoDocument::errorMessage() const 2207 { 2208 return d->lastErrorMessage; 2209 } 2210 2211 void KoDocument::showLoadingErrorDialog() 2212 { 2213 if (errorMessage().isEmpty()) { 2214 KMessageBox::error(0, i18n("Could not open\n%1", localFilePath())); 2215 } 2216 else if (errorMessage() != "USER_CANCELED") { 2217 KMessageBox::error(0, i18n("Could not open %1\nReason: %2", localFilePath(), errorMessage())); 2218 } 2219 } 2220 2221 bool KoDocument::isAutosaving() const 2222 { 2223 return d->autosaving; 2224 } 2225 2226 bool KoDocument::isLoading() const 2227 { 2228 return d->isLoading; 2229 } 2230 2231 void KoDocument::removeAutoSaveFiles() 2232 { 2233 // Eliminate any auto-save file 2234 QString asf = autoSaveFile(localFilePath()); // the one in the current dir 2235 if (QFile::exists(asf)) 2236 QFile::remove(asf); 2237 asf = autoSaveFile(QString()); // and the one in $HOME 2238 if (QFile::exists(asf)) 2239 QFile::remove(asf); 2240 } 2241 2242 void KoDocument::setBackupFile(bool _b) 2243 { 2244 d->backupFile = _b; 2245 } 2246 2247 bool KoDocument::backupFile()const 2248 { 2249 return d->backupFile; 2250 } 2251 2252 2253 void KoDocument::setBackupPath(const QString & _path) 2254 { 2255 d->backupPath = _path; 2256 } 2257 2258 QString KoDocument::backupPath()const 2259 { 2260 return d->backupPath; 2261 } 2262 2263 bool KoDocument::hasExternURL() const 2264 { 2265 return !url().scheme().isEmpty() 2266 && url().scheme() != STORE_PROTOCOL 2267 && url().scheme() != INTERNAL_PROTOCOL; 2268 } 2269 2270 static const struct { 2271 const char *localName; 2272 const char *documentType; 2273 } TN2DTArray[] = { 2274 { "text", I18N_NOOP("a word processing") }, 2275 { "spreadsheet", I18N_NOOP("a spreadsheet") }, 2276 { "presentation", I18N_NOOP("a presentation") }, 2277 { "chart", I18N_NOOP("a chart") }, 2278 { "drawing", I18N_NOOP("a drawing") } 2279 }; 2280 static const unsigned int numTN2DT = sizeof(TN2DTArray) / sizeof(*TN2DTArray); 2281 2282 QString KoDocument::tagNameToDocumentType(const QString& localName) 2283 { 2284 for (unsigned int i = 0 ; i < numTN2DT ; ++i) 2285 if (localName == TN2DTArray[i].localName) 2286 return i18n(TN2DTArray[i].documentType); 2287 return localName; 2288 } 2289 2290 KoPageLayout KoDocument::pageLayout(int /*pageNumber*/) const 2291 { 2292 return d->pageLayout; 2293 } 2294 2295 void KoDocument::setPageLayout(const KoPageLayout &pageLayout) 2296 { 2297 d->pageLayout = pageLayout; 2298 } 2299 2300 KoUnit KoDocument::unit() const 2301 { 2302 return d->unit; 2303 } 2304 2305 void KoDocument::setUnit(const KoUnit &unit) 2306 { 2307 if (d->unit != unit) { 2308 d->unit = unit; 2309 emit unitChanged(unit); 2310 } 2311 } 2312 2313 void KoDocument::saveUnitOdf(KoXmlWriter *settingsWriter) const 2314 { 2315 settingsWriter->addConfigItem("unit", unit().symbol()); 2316 } 2317 2318 2319 void KoDocument::initEmpty() 2320 { 2321 setEmpty(); 2322 setModified(false); 2323 } 2324 2325 QList<KoVersionInfo> & KoDocument::versionList() 2326 { 2327 return d->versionInfo; 2328 } 2329 2330 KUndo2Stack *KoDocument::undoStack() 2331 { 2332 return d->undoStack; 2333 } 2334 2335 void KoDocument::addCommand(KUndo2Command *command) 2336 { 2337 if (command) 2338 d->undoStack->push(command); 2339 } 2340 2341 void KoDocument::beginMacro(const KUndo2MagicString & text) 2342 { 2343 d->undoStack->beginMacro(text); 2344 } 2345 2346 void KoDocument::endMacro() 2347 { 2348 d->undoStack->endMacro(); 2349 } 2350 2351 void KoDocument::slotUndoStackIndexChanged(int idx) 2352 { 2353 // even if the document was already modified, call setModified to re-start autosave timer 2354 setModified(idx != d->undoStack->cleanIndex()); 2355 } 2356 2357 void KoDocument::setProfileStream(QTextStream *profilestream) 2358 { 2359 d->profileStream = profilestream; 2360 } 2361 2362 void KoDocument::setProfileReferenceTime(const QTime& referenceTime) 2363 { 2364 d->profileReferenceTime = referenceTime; 2365 } 2366 2367 void KoDocument::clearUndoHistory() 2368 { 2369 d->undoStack->clear(); 2370 } 2371 2372 KoGridData &KoDocument::gridData() 2373 { 2374 return d->gridData; 2375 } 2376 2377 KoGuidesData &KoDocument::guidesData() 2378 { 2379 return d->guidesData; 2380 } 2381 2382 bool KoDocument::isEmpty() const 2383 { 2384 return d->isEmpty; 2385 } 2386 2387 void KoDocument::setEmpty() 2388 { 2389 d->isEmpty = true; 2390 } 2391 2392 2393 // static 2394 int KoDocument::defaultAutoSave() 2395 { 2396 return 300; 2397 } 2398 2399 void KoDocument::resetURL() { 2400 setUrl(QUrl()); 2401 setLocalFilePath(QString()); 2402 } 2403 2404 int KoDocument::pageCount() const { 2405 return 1; 2406 } 2407 2408 void KoDocument::setupOpenFileSubProgress() {} 2409 2410 KoDocumentInfoDlg *KoDocument::createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const 2411 { 2412 KoDocumentInfoDlg *dlg = new KoDocumentInfoDlg(parent, docInfo); 2413 KoMainWindow *mainwin = dynamic_cast<KoMainWindow*>(parent); 2414 if (mainwin) { 2415 connect(dlg, SIGNAL(saveRequested()), mainwin, SLOT(slotFileSave())); 2416 } 2417 return dlg; 2418 } 2419 2420 bool KoDocument::isReadWrite() const 2421 { 2422 return d->readwrite; 2423 } 2424 2425 QUrl KoDocument::url() const 2426 { 2427 return d->m_url; 2428 } 2429 2430 bool KoDocument::closeUrl(bool promptToSave) 2431 { 2432 abortLoad(); //just in case 2433 if (promptToSave) { 2434 if ( d->document->isReadWrite() && d->document->isModified()) { 2435 if (!queryClose()) 2436 return false; 2437 } 2438 } 2439 // Not modified => ok and delete temp file. 2440 d->mimeType = QByteArray(); 2441 2442 if ( d->m_bTemp ) 2443 { 2444 QFile::remove( d->m_file ); 2445 d->m_bTemp = false; 2446 } 2447 // It always succeeds for a read-only part, 2448 // but the return value exists for reimplementations 2449 // (e.g. pressing cancel for a modified read-write part) 2450 return true; 2451 } 2452 2453 2454 bool KoDocument::saveAs( const QUrl &kurl ) 2455 { 2456 if (!kurl.isValid()) 2457 { 2458 qCritical(/*1000*/) << "saveAs: Malformed URL " << kurl.url() << endl; 2459 return false; 2460 } 2461 d->m_duringSaveAs = true; 2462 d->m_originalURL = d->m_url; 2463 d->m_originalFilePath = d->m_file; 2464 d->m_url = kurl; // Store where to upload in saveToURL 2465 d->prepareSaving(); 2466 bool result = save(); // Save local file and upload local file 2467 if (!result) { 2468 d->m_url = d->m_originalURL; 2469 d->m_file = d->m_originalFilePath; 2470 d->m_duringSaveAs = false; 2471 d->m_originalURL = QUrl(); 2472 d->m_originalFilePath.clear(); 2473 } 2474 2475 return result; 2476 } 2477 2478 bool KoDocument::save() 2479 { 2480 d->m_saveOk = false; 2481 if ( d->m_file.isEmpty() ) // document was created empty 2482 d->prepareSaving(); 2483 2484 DocumentProgressProxy *progressProxy = 0; 2485 if (!d->document->progressProxy()) { 2486 KoMainWindow *mainWindow = 0; 2487 if (d->parentPart->mainwindowCount() > 0) { 2488 mainWindow = d->parentPart->mainWindows()[0]; 2489 } 2490 progressProxy = new DocumentProgressProxy(mainWindow); 2491 d->document->setProgressProxy(progressProxy); 2492 } 2493 d->document->setUrl(url()); 2494 2495 // THIS IS WRONG! KoDocument::saveFile should move here, and whoever subclassed KoDocument to 2496 // reimplement saveFile should now subclass KoPart. 2497 bool ok = d->document->saveFile(); 2498 2499 if (progressProxy) { 2500 d->document->setProgressProxy(0); 2501 delete progressProxy; 2502 } 2503 2504 if (ok) { 2505 return saveToUrl(); 2506 } 2507 else { 2508 emit canceled(QString()); 2509 } 2510 return false; 2511 } 2512 2513 2514 bool KoDocument::waitSaveComplete() 2515 { 2516 if (!d->m_uploadJob) 2517 return d->m_saveOk; 2518 2519 d->m_waitForSave = true; 2520 2521 d->m_eventLoop.exec(QEventLoop::ExcludeUserInputEvents); 2522 2523 d->m_waitForSave = false; 2524 2525 return d->m_saveOk; 2526 } 2527 2528 2529 void KoDocument::abortLoad() 2530 { 2531 if ( d->m_statJob ) { 2532 //kDebug(1000) << "Aborting job" << d->m_statJob; 2533 d->m_statJob->kill(); 2534 d->m_statJob = 0; 2535 } 2536 if ( d->m_job ) { 2537 //kDebug(1000) << "Aborting job" << d->m_job; 2538 d->m_job->kill(); 2539 d->m_job = 0; 2540 } 2541 } 2542 2543 2544 void KoDocument::setUrl(const QUrl &url) 2545 { 2546 d->m_url = url; 2547 } 2548 2549 QString KoDocument::localFilePath() const 2550 { 2551 return d->m_file; 2552 } 2553 2554 2555 void KoDocument::setLocalFilePath( const QString &localFilePath ) 2556 { 2557 d->m_file = localFilePath; 2558 } 2559 2560 bool KoDocument::queryClose() 2561 { 2562 if ( !d->document->isReadWrite() || !d->document->isModified() ) 2563 return true; 2564 2565 QString docName = url().fileName(); 2566 if (docName.isEmpty()) docName = i18n( "Untitled" ); 2567 2568 2569 int res = KMessageBox::warningYesNoCancel( 0, 2570 i18n( "The document \"%1\" has been modified.\n" 2571 "Do you want to save your changes or discard them?" , docName ), 2572 i18n( "Close Document" ), KStandardGuiItem::save(), KStandardGuiItem::discard() ); 2573 2574 bool abortClose=false; 2575 bool handled=false; 2576 2577 switch(res) { 2578 case KMessageBox::Yes : 2579 if (!handled) 2580 { 2581 if (d->m_url.isEmpty()) 2582 { 2583 KoMainWindow *mainWindow = 0; 2584 if (d->parentPart->mainWindows().count() > 0) { 2585 mainWindow = d->parentPart->mainWindows()[0]; 2586 } 2587 KoFileDialog dialog(mainWindow, KoFileDialog::SaveFile, "SaveDocument"); 2588 QUrl url = QUrl::fromLocalFile(dialog.filename()); 2589 if (url.isEmpty()) 2590 return false; 2591 2592 saveAs( url ); 2593 } 2594 else 2595 { 2596 save(); 2597 } 2598 } else if (abortClose) return false; 2599 return waitSaveComplete(); 2600 case KMessageBox::No : 2601 return true; 2602 default : // case KMessageBox::Cancel : 2603 return false; 2604 } 2605 } 2606 2607 2608 bool KoDocument::saveToUrl() 2609 { 2610 if ( d->m_url.isLocalFile() ) { 2611 d->document->setModified( false ); 2612 emit completed(); 2613 // if m_url is a local file there won't be a temp file -> nothing to remove 2614 Q_ASSERT( !d->m_bTemp ); 2615 d->m_saveOk = true; 2616 d->m_duringSaveAs = false; 2617 d->m_originalURL = QUrl(); 2618 d->m_originalFilePath.clear(); 2619 return true; // Nothing to do 2620 } 2621 #ifndef Q_OS_WIN 2622 else { 2623 if (d->m_uploadJob) { 2624 QFile::remove(d->m_uploadJob->srcUrl().toLocalFile()); 2625 d->m_uploadJob->kill(); 2626 d->m_uploadJob = 0; 2627 } 2628 QTemporaryFile *tempFile = new QTemporaryFile(); 2629 tempFile->open(); 2630 QString uploadFile = tempFile->fileName(); 2631 delete tempFile; 2632 QUrl uploadUrl; 2633 uploadUrl.setPath( uploadFile ); 2634 // Create hardlink 2635 if (::link(QFile::encodeName(d->m_file), QFile::encodeName(uploadFile)) != 0) { 2636 // Uh oh, some error happened. 2637 return false; 2638 } 2639 d->m_uploadJob = KIO::file_move( uploadUrl, d->m_url, -1, KIO::Overwrite ); 2640 #ifndef QT_NO_DBUS 2641 KJobWidgets::setWindow(d->m_uploadJob, 0); 2642 #endif 2643 connect( d->m_uploadJob, SIGNAL(result(KJob*)), this, SLOT(_k_slotUploadFinished(KJob*)) ); 2644 return true; 2645 } 2646 #else 2647 return false; 2648 #endif 2649 } 2650 2651 2652 bool KoDocument::openUrlInternal(const QUrl &url) 2653 { 2654 if ( !url.isValid() ) 2655 return false; 2656 2657 if (d->m_bAutoDetectedMime) { 2658 d->mimeType = QByteArray(); 2659 d->m_bAutoDetectedMime = false; 2660 } 2661 2662 QByteArray mimetype = d->mimeType; 2663 2664 if ( !closeUrl() ) 2665 return false; 2666 2667 d->mimeType = mimetype; 2668 setUrl(url); 2669 2670 d->m_file.clear(); 2671 2672 if (d->m_url.isLocalFile()) { 2673 d->m_file = d->m_url.toLocalFile(); 2674 return d->openLocalFile(); 2675 } 2676 else { 2677 d->openRemoteFile(); 2678 return true; 2679 } 2680 } 2681 2682 // have to include this because of Q_PRIVATE_SLOT 2683 #include <moc_KoDocument.cpp>