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>