File indexing completed on 2024-05-12 16:39:25

0001 /* This file is part of the KDE project
0002  Copyright (C) 1998, 1999, 2000 Torben Weis <weis@kde.org>
0003  Copyright (C) 2004 - 2009 Dag Andersen <danders@get2net.dk>
0004  Copyright (C) 2006 Raphael Langerhorst <raphael.langerhorst@kdemail.net>
0005  Copyright (C) 2007 Thorsten Zachmann <zachmann@kde.org>
0006   Copyright (C) 2007 - 2009, 2012 Dag Andersen <danders@get2net.dk>
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 // clazy:excludeall=qstring-arg
0025 #include "part.h"
0026 #include "view.h"
0027 #include "factory.h"
0028 #include "mainwindow.h"
0029 #include "workpackage.h"
0030 #include "calligraplanworksettings.h"
0031 
0032 #include "KPlatoXmlLoader.h" //NB!
0033 
0034 #include "kptglobal.h"
0035 #include "kptnode.h"
0036 #include "kptproject.h"
0037 #include "kpttask.h"
0038 #include "kptdocuments.h"
0039 #include "kptcommand.h"
0040 
0041 #include <KoXmlReader.h>
0042 #include <KoStore.h>
0043 #include <KoDocumentInfo.h>
0044 #include <KoResourcePaths.h>
0045 #include <KoComponentData.h>
0046 
0047 #include <QPainter>
0048 #include <QFileInfo>
0049 #include <QDir>
0050 #include <QTimer>
0051 #include <QFileSystemWatcher>
0052 #include <kundo2qstack.h>
0053 #include <QPointer>
0054 #include <QUrl>
0055 #include <QMimeDatabase>
0056 #include <QApplication>
0057 
0058 #include <KLocalizedString>
0059 #include <kmessagebox.h>
0060 #include <kparts/partmanager.h>
0061 #include <kopenwithdialog.h>
0062 #include <kmimetypetrader.h>
0063 //#include <kserviceoffer.h>
0064 #include <KIO/DesktopExecParser>
0065 #include <krun.h>
0066 #include <kprocess.h>
0067 #include <kactioncollection.h>
0068 
0069 #include "debugarea.h"
0070 
0071 using namespace KPlato;
0072 
0073 namespace KPlatoWork
0074 {
0075 
0076 //-------------------------------
0077 DocumentChild::DocumentChild(WorkPackage *parent)
0078     : QObject(parent),
0079     m_doc(0),
0080     m_type(Type_Unknown),
0081     m_copy(false),
0082     m_process(0),
0083     m_editor(0),
0084     m_editormodified(false),
0085     m_filemodified(false),
0086     m_fileSystemWatcher(new QFileSystemWatcher(this))
0087 
0088 {
0089 }
0090 
0091 // DocumentChild::DocumentChild(KParts::ReadWritePart *editor, const QUrl &url, const Document *doc, Part *parent)
0092 //     : KoDocumentChild(parent),
0093 //     m_doc(doc),
0094 //     m_type(Type_Unknown),
0095 //     m_copy(true),
0096 //     m_process(0),
0097 //     m_editor(editor),
0098 //     m_editormodified(false),
0099 //     m_filemodified(false)
0100 // {
0101 //     setFileInfo(url);
0102 //     if (dynamic_cast<KoDocument*>(editor)) {
0103 //         debugPlanWork<<"Creating Calligra doc";
0104 //         m_type = Type_Calligra;
0105 //         connect(static_cast<KoDocument*>(editor), SIGNAL(modified(bool)), this, SLOT(setModified(bool)));
0106 //     } else {
0107 //         debugPlanWork<<"Creating KParts doc";
0108 //         m_type = Type_KParts;
0109 //         slotUpdateModified();
0110 //     }
0111 // }
0112 
0113 DocumentChild::~DocumentChild()
0114 {
0115     debugPlanWork;
0116     disconnect(m_fileSystemWatcher, &QFileSystemWatcher::fileChanged, this, &DocumentChild::slotDirty);
0117     m_fileSystemWatcher->removePath(filePath());
0118 
0119     if (m_type == Type_Calligra || m_type == Type_KParts) {
0120         delete m_editor;
0121     }
0122 }
0123 
0124 WorkPackage *DocumentChild::parentPackage() const
0125 {
0126     return static_cast<WorkPackage*>(parent());
0127 }
0128 
0129 void DocumentChild::setFileInfo(const QUrl &url)
0130 {
0131     m_fileinfo.setFile(url.path());
0132     //debugPlanWork<<url;
0133     bool res = connect(m_fileSystemWatcher, &QFileSystemWatcher::fileChanged, this, &DocumentChild::slotDirty);
0134     //debugPlanWork<<res<<filePath();
0135 #ifndef NDEBUG
0136     Q_ASSERT(res);
0137 #else
0138     Q_UNUSED(res);
0139 #endif
0140     m_fileSystemWatcher->addPath(filePath());
0141 }
0142 
0143 void DocumentChild::setModified(bool mod)
0144 {
0145     debugPlanWork<<mod<<filePath();
0146     if (m_editormodified != mod) {
0147         m_editormodified = mod;
0148         emit modified(mod);
0149     }
0150 }
0151 
0152 void DocumentChild::slotDirty(const QString &file)
0153 {
0154     //debugPlanWork<<filePath()<<file<<m_filemodified;
0155     if (file == filePath() && ! m_filemodified) {
0156         debugPlanWork<<file<<"is modified";
0157         m_filemodified = true;
0158         emit fileModified(true);
0159     }
0160 }
0161 
0162 void DocumentChild::slotUpdateModified()
0163 {
0164     if (m_type == Type_KParts && m_editor && (m_editor->isModified() != m_editormodified)) {
0165         setModified(m_editor->isModified());
0166     }
0167     QTimer::singleShot(500, this, &DocumentChild::slotUpdateModified);
0168 }
0169 
0170 bool DocumentChild::setDoc(const Document *doc)
0171 {
0172     Q_ASSERT (m_doc == 0);
0173     if (isOpen()) {
0174         KMessageBox::error(0, i18n("Document is already open:<br>%1", doc->url().url()));
0175         return false;
0176     }
0177     m_doc = doc;
0178     QUrl url;
0179     if (parentPackage()->newDocuments().contains(doc)) {
0180         url = parentPackage()->newDocuments().value(doc);
0181         Q_ASSERT(url.isValid());
0182         parentPackage()->removeNewDocument(doc);
0183     } else if (doc->sendAs() == Document::SendAs_Copy) {
0184         url = parentPackage()->extractFile(doc);
0185         if (url.url().isEmpty()) {
0186             KMessageBox::error(0, i18n("Could not extract document from storage:<br>%1", doc->url().url()));
0187             return false;
0188         }
0189         m_copy = true;
0190     } else {
0191         url = doc->url();
0192     }
0193     if (! url.isValid()) {
0194         KMessageBox::error(0, i18n("Invalid URL:<br>%1", url.url()));
0195         return false;
0196     }
0197     setFileInfo(url);
0198     return true;
0199 }
0200 
0201 bool DocumentChild::openDoc(const Document *doc, KoStore *store)
0202 {
0203     Q_ASSERT (m_doc == 0);
0204     if (isOpen()) {
0205         KMessageBox::error(0, i18n("Document is already open:<br>%1", doc->url().path()));
0206         return false;
0207     }
0208     m_doc = doc;
0209     QUrl url;
0210     if (doc->sendAs() == Document::SendAs_Copy) {
0211         url = parentPackage()->extractFile(doc, store);
0212         if (url.url().isEmpty()) {
0213             KMessageBox::error(0, i18n("Could not extract document from storage:<br>%1", doc->url().path()));
0214             return false;
0215         }
0216         m_copy = true;
0217     } else {
0218         url = doc->url();
0219     }
0220     if (! url.isValid()) {
0221         KMessageBox::error(0, i18n("Invalid URL:<br>%1", url.url()));
0222         return false;
0223     }
0224     setFileInfo(url);
0225     return true;
0226 }
0227 
0228 bool DocumentChild::editDoc()
0229 {
0230     Q_ASSERT(m_doc != 0);
0231     debugPlanWork<<"file:"<<filePath();
0232     if (isOpen()) {
0233         KMessageBox::error(0, i18n("Document is already open:<br> %1", m_doc->url().path()));
0234         return false;
0235     }
0236     if (! m_fileinfo.exists()) {
0237         KMessageBox::error(0, i18n("File does not exist:<br>%1", fileName()));
0238         return false;
0239     }
0240     QUrl filename = QUrl::fromLocalFile(filePath());
0241     const QMimeType mimetype = QMimeDatabase().mimeTypeForUrl(filename);
0242     KService::Ptr service = KMimeTypeTrader::self()->preferredService(mimetype.name());
0243     bool editing = startProcess(service, filename);
0244     if (editing) {
0245         m_type = Type_Other; // FIXME: try to be more specific
0246     }
0247     return editing;
0248 }
0249 
0250 bool DocumentChild::startProcess(KService::Ptr service, const QUrl &url)
0251 {
0252     QStringList args;
0253     QList<QUrl> files;
0254     if (url.isValid()) {
0255         files << url;
0256     }
0257     if (service) {
0258         KIO::DesktopExecParser parser(*service, files);
0259         parser.setUrlsAreTempFiles(false);
0260         args = parser.resultingArguments();
0261     } else {
0262         QList<QUrl> list;
0263         QPointer<KOpenWithDialog> dlg = new KOpenWithDialog(list, i18n("Edit with:"), QString(), 0);
0264         if (dlg->exec() == QDialog::Accepted && dlg){
0265             args << dlg->text();
0266         }
0267         if (args.isEmpty()) {
0268             debugPlanWork<<"No executable selected";
0269             return false;
0270         }
0271         args << url.url();
0272         delete dlg;
0273     }
0274     debugPlanWork<<args;
0275     m_process = new KProcess();
0276     m_process->setProgram(args);
0277     connect(m_process, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(slotEditFinished(int,QProcess::ExitStatus)));
0278     connect(m_process, SIGNAL(error(QProcess::ProcessError)), SLOT(slotEditError(QProcess::ProcessError)));
0279     m_process->start();
0280     //debugPlanWork<<m_process->pid()<<m_process->program();
0281     return true;
0282 }
0283 
0284 bool DocumentChild::isModified() const
0285 {
0286     return m_editormodified;
0287 }
0288 
0289 bool DocumentChild::isFileModified() const
0290 {
0291     return m_filemodified;
0292 }
0293 
0294 void DocumentChild::slotEditFinished(int /*par*/,  QProcess::ExitStatus)
0295 {
0296     //debugPlanWork<<par<<filePath();
0297     delete m_process;
0298     m_process = 0;
0299 }
0300 
0301 void DocumentChild::slotEditError(QProcess::ProcessError status)
0302 {
0303     debugPlanWork<<status;
0304     if (status == QProcess::FailedToStart || status == QProcess::Crashed) {
0305         m_process->deleteLater();
0306         m_process = 0;
0307     } else debugPlanWork<<"Error="<<status<<" what to do?";
0308 }
0309 
0310 bool DocumentChild::saveToStore(KoStore *store)
0311 {
0312     debugPlanWork<<filePath();
0313     m_fileSystemWatcher->removePath(filePath());
0314     bool ok = false;
0315     bool wasmod = m_filemodified;
0316     if (m_type == Type_Calligra || m_type == Type_KParts) {
0317         if (m_editor->isModified()) {
0318             ok = m_editor->save(); // hmmmm
0319         } else {
0320             ok = true;
0321         }
0322     } else if (m_type == Type_Other) {
0323         if (isOpen()) {
0324             warnPlanWork<<"External editor open";
0325         }
0326         ok = true;
0327     } else {
0328         errorPlanWork<<"Unknown document type";
0329     }
0330     if (ok) {
0331         debugPlanWork<<"Add to store:"<<fileName();
0332         store->addLocalFile(filePath(), fileName());
0333         m_filemodified = false;
0334         if (wasmod != m_filemodified) {
0335             emit fileModified(m_filemodified);
0336         }
0337     }
0338     m_fileSystemWatcher->addPath(filePath());
0339     return ok;
0340 }
0341 
0342 
0343 //------------------------------------
0344 Part::Part(QWidget *parentWidget, QObject *parent, const QVariantList & /*args*/)
0345     : KParts::ReadWritePart(parent),
0346     m_xmlLoader(),
0347     m_modified(false),
0348     m_loadingFromProjectStore(false),
0349     m_undostack(new KUndo2QStack(this))
0350 {
0351     debugPlanWork;
0352     setComponentName(Factory::global().componentName(), Factory::global().componentDisplayName());
0353     if (isReadWrite()) {
0354         setXMLFile("calligraplanwork.rc");
0355     } else {
0356         setXMLFile("calligraplanwork_readonly.rc");
0357     }
0358 
0359     m_view = new View(this, parentWidget, actionCollection());
0360     setWidget(m_view);
0361     connect(m_view, &View::viewDocument, this, &Part::viewWorkpackageDocument);
0362 
0363     loadWorkPackages();
0364 
0365     connect(m_undostack, &KUndo2QStack::cleanChanged, this, &Part::setDocumentClean);
0366 
0367 }
0368 
0369 Part::~Part()
0370 {
0371     debugPlanWork;
0372 //    m_config.save();
0373     // views must be deleted before packages
0374     delete m_view;
0375     qDeleteAll(m_packageMap);
0376     PlanWorkSettings::self()->save();
0377 }
0378 
0379 void Part::addCommand(KUndo2Command *cmd)
0380 {
0381     if (cmd) {
0382         m_undostack->push(cmd);
0383     }
0384 }
0385 
0386 bool Part::setWorkPackage(WorkPackage *wp, KoStore *store)
0387 {
0388     //debugPlanWork;
0389     QString id = wp->id();
0390     if (m_packageMap.contains(id)) {
0391         if (KMessageBox::warningYesNo(0, i18n("<p>The work package already exists in the projects store.</p>"
0392                 "<p>Project: %1<br>Task: %2</p>"
0393                 "<p>Do you want to update the existing package with data from the new?</p>",
0394                 wp->project()->name(), wp->node()->name())) == KMessageBox::No) {
0395             delete wp;
0396             return false;
0397         }
0398         m_packageMap[ id ]->merge(this, wp, store);
0399         delete wp;
0400         return true;
0401     }
0402     wp->setFilePath(m_loadingFromProjectStore ? wp->fileName(this) : localFilePath());
0403     m_packageMap[ id ] = wp;
0404     if (! m_loadingFromProjectStore) {
0405         wp->saveToProjects(this);
0406     }
0407     connect(wp->project(), SIGNAL(projectChanged()), wp, SLOT(projectChanged()));
0408     connect (wp, SIGNAL(modified(bool)), this, SLOT(setModified(bool)));
0409     emit workPackageAdded(wp, indexOf(wp));
0410     connect(wp, &WorkPackage::saveWorkPackage, this, &Part::saveWorkPackage);
0411     return true;
0412 }
0413 
0414 void Part::removeWorkPackage(Node *node, MacroCommand *m)
0415 {
0416     debugPlanWork<<node->name();
0417     WorkPackage *wp = findWorkPackage(node);
0418     if (wp == 0) {
0419         KMessageBox::error(0, i18n("Remove failed. Cannot find work package"));
0420         return;
0421     }
0422     PackageRemoveCmd *cmd = new PackageRemoveCmd(this, wp, kundo2_i18n("Remove work package"));
0423     if (m) {
0424         m->addCommand(cmd);
0425     } else {
0426         addCommand(cmd);
0427     }
0428 }
0429 
0430 void Part::removeWorkPackages(const QList<Node*> &nodes)
0431 {
0432     debugPlanWork<<nodes;
0433     MacroCommand *m = new MacroCommand(kundo2_i18np("Remove work package", "Remove work packages", nodes.count()));
0434     foreach (Node *n, nodes) {
0435         removeWorkPackage(n, m);
0436     }
0437     if (m->isEmpty()) {
0438         delete m;
0439     } else {
0440         addCommand(m);
0441     }
0442 }
0443 
0444 void Part::removeWorkPackage(WorkPackage *wp)
0445 {
0446     //debugPlanWork;
0447     int row = indexOf(wp);
0448     if (row >= 0) {
0449         const QList<QString> &lst = m_packageMap.keys();
0450         const QString &key = lst.value(row);
0451         m_packageMap.remove(key);
0452         emit workPackageRemoved(wp, row);
0453     }
0454 }
0455 
0456 void Part::addWorkPackage(WorkPackage *wp)
0457 {
0458     //debugPlanWork;
0459     QString id = wp->id();
0460     Q_ASSERT(! m_packageMap.contains(id));
0461     m_packageMap[ id ] = wp;
0462     emit workPackageAdded(wp, indexOf(wp));
0463 }
0464 
0465 bool Part::loadWorkPackages()
0466 {
0467     m_loadingFromProjectStore = true;
0468     const QStringList lst = KoResourcePaths::findAllResources("projects", "*.planwork", KoResourcePaths::Recursive | KoResourcePaths::NoDuplicates);
0469     debugPlanWork<<lst;
0470     foreach (const QString &file, lst) {
0471         if (! loadNativeFormatFromStore(file)) {
0472             KMessageBox::information(0, i18n("Failed to load file:<br>%1" , file));
0473         }
0474     }
0475     m_loadingFromProjectStore = false;
0476     return true;
0477 
0478 }
0479 
0480 bool Part::loadNativeFormatFromStore(const QString& file)
0481 {
0482     debugPlanWork<<file;
0483     KoStore * store = KoStore::createStore(file, KoStore::Read, "", KoStore::Auto);
0484 
0485     if (store->bad()) {
0486         KMessageBox::error(0, i18n("Not a valid work package file:<br>%1", file));
0487         delete store;
0488         QApplication::restoreOverrideCursor();
0489         return false;
0490     }
0491 
0492     const bool success = loadNativeFormatFromStoreInternal(store);
0493 
0494     delete store;
0495 
0496     return success;
0497 }
0498 
0499 bool Part::loadNativeFormatFromStoreInternal(KoStore * store)
0500 {
0501     if (store->hasFile("root")) {
0502         KoXmlDocument doc;
0503         bool ok = loadAndParse(store, "root", doc);
0504         if (ok) {
0505             ok = loadXML(doc, store);
0506         }
0507         if (!ok) {
0508             QApplication::restoreOverrideCursor();
0509             return false;
0510         }
0511 
0512     } else {
0513         errorPlanWork << "ERROR: No maindoc.xml" << endl;
0514         KMessageBox::error(0, i18n("Invalid document. The document does not contain 'maindoc.xml'."));
0515         QApplication::restoreOverrideCursor();
0516         return false;
0517     }
0518 //     if (store->hasFile("documentinfo.xml")) {
0519 //         KoXmlDocument doc;
0520 //         if (oldLoadAndParse(store, "documentinfo.xml", doc)) {
0521 //             d->m_docInfo->load(doc);
0522 //         }
0523 //     } else {
0524 //         //debugPlanWork <<"cannot open document info";
0525 //         delete d->m_docInfo;
0526 //         d->m_docInfo = new KoDocumentInfo(this);
0527 //     }
0528 
0529     bool res = completeLoading(store);
0530     QApplication::restoreOverrideCursor();
0531     return res;
0532 }
0533 
0534 bool Part::loadAndParse(KoStore* store, const QString& filename, KoXmlDocument& doc)
0535 {
0536     //debugPlanWork <<"Trying to open" << filename;
0537 
0538     if (!store->open(filename)) {
0539         warnPlanWork << "Entry " << filename << " not found!";
0540         KMessageBox::error(0, i18n("Failed to open file: %1", filename));
0541         return false;
0542     }
0543     // Error variables for QDomDocument::setContent
0544     QString errorMsg;
0545     int errorLine, errorColumn;
0546     bool ok = doc.setContent(store->device(), &errorMsg, &errorLine, &errorColumn);
0547     store->close();
0548     if (!ok) {
0549         errorPlanWork << "Parsing error in " << filename << "! Aborting!" << endl
0550         << " In line: " << errorLine << ", column: " << errorColumn << endl
0551         << " Error message: " << errorMsg;
0552         KMessageBox::error(0, i18n("Parsing error in file '%1' at line %2, column %3<br>Error message: %4", filename  , errorLine, errorColumn ,
0553                                    QCoreApplication::translate("QXml", errorMsg.toUtf8(), 0)));
0554         return false;
0555     }
0556     return true;
0557 }
0558 
0559 bool Part::loadXML(const KoXmlDocument &document, KoStore* store)
0560 {
0561     debugPlanWork;
0562     QString value;
0563     KoXmlElement plan = document.documentElement();
0564 
0565     // Check if this is the right app
0566     value = plan.attribute("mime", QString());
0567     if (value.isEmpty()) {
0568         errorPlanWork << "No mime type specified!" << endl;
0569         KMessageBox::error(0, i18n("Invalid document. No mimetype specified."));
0570         return false;
0571     } else if (value == "application/x-vnd.kde.kplato.work") {
0572         return loadKPlatoXML(document, store);
0573     } else if (value != "application/x-vnd.kde.plan.work") {
0574         errorPlanWork << "Unknown mime type " << value;
0575         KMessageBox::error(0, i18n("Invalid document. Expected mimetype application/x-vnd.kde.plan.work, got %1", value));
0576         return false;
0577     }
0578     QString syntaxVersion = plan.attribute("version", PLANWORK_FILE_SYNTAX_VERSION);
0579     m_xmlLoader.setWorkVersion(syntaxVersion);
0580     if (syntaxVersion > PLANWORK_FILE_SYNTAX_VERSION) {
0581         KMessageBox::ButtonCode ret = KMessageBox::warningContinueCancel(
0582                       0, i18n("This document is a newer version than supported by PlanWork (syntax version: %1)<br>"
0583                                "Opening it in this version of PlanWork will lose some information.", syntaxVersion),
0584                       i18n("File-Format Mismatch"), KGuiItem(i18n("Continue")));
0585         if (ret == KMessageBox::Cancel) {
0586             return false;
0587         }
0588     }
0589     m_xmlLoader.setVersion(plan.attribute("plan-version", PLAN_FILE_SYNTAX_VERSION));
0590     m_xmlLoader.startLoad();
0591     WorkPackage *wp = new WorkPackage(m_loadingFromProjectStore);
0592     wp->loadXML(plan, m_xmlLoader);
0593     m_xmlLoader.stopLoad();
0594     if (! setWorkPackage(wp, store)) {
0595         // rejected, so nothing changed...
0596         return true;
0597     }
0598     emit changed();
0599     return true;
0600 }
0601 
0602 bool Part::loadKPlatoXML(const KoXmlDocument &document, KoStore*)
0603 {
0604     debugPlanWork;
0605     QString value;
0606     KoXmlElement plan = document.documentElement();
0607 
0608     // Check if this is the right app
0609     value = plan.attribute("mime", QString());
0610     if (value.isEmpty()) {
0611         errorPlanWork << "No mime type specified!" << endl;
0612         KMessageBox::error(0, i18n("Invalid document. No mimetype specified."));
0613         return false;
0614     } else if (value != "application/x-vnd.kde.kplato.work") {
0615         errorPlanWork << "Unknown mime type " << value;
0616         KMessageBox::error(0, i18n("Invalid document. Expected mimetype application/x-vnd.kde.kplato.work, got %1", value));
0617         return false;
0618     }
0619     QString syntaxVersion = plan.attribute("version", KPLATOWORK_MAX_FILE_SYNTAX_VERSION);
0620     m_xmlLoader.setWorkVersion(syntaxVersion);
0621     if (syntaxVersion > KPLATOWORK_MAX_FILE_SYNTAX_VERSION) {
0622         KMessageBox::ButtonCode ret = KMessageBox::warningContinueCancel(
0623                       0, i18n("This document is a newer version than supported by PlanWork (syntax version: %1)<br>"
0624                                "Opening it in this version of PlanWork will lose some information.", syntaxVersion),
0625                       i18n("File-Format Mismatch"), KGuiItem(i18n("Continue")));
0626         if (ret == KMessageBox::Cancel) {
0627             return false;
0628         }
0629     }
0630     m_xmlLoader.setMimetype(value);
0631     m_xmlLoader.setVersion(plan.attribute("kplato-version", KPLATO_MAX_FILE_SYNTAX_VERSION));
0632     m_xmlLoader.startLoad();
0633     WorkPackage *wp = new WorkPackage(m_loadingFromProjectStore);
0634     wp->loadKPlatoXML(plan, m_xmlLoader);
0635     m_xmlLoader.stopLoad();
0636     if (! setWorkPackage(wp)) {
0637         // rejected, so nothing changed...
0638         return true;
0639     }
0640     emit changed();
0641     return true;
0642 }
0643 
0644 bool Part::completeLoading(KoStore *)
0645 {
0646     return true;
0647 }
0648 
0649 QUrl Part::extractFile(const Document *doc)
0650 {
0651     WorkPackage *wp = findWorkPackage(doc);
0652     return wp == 0 ? QUrl() : wp->extractFile(doc);
0653 }
0654 
0655 int Part::docType(const Document *doc) const
0656 {
0657     DocumentChild *ch = findChild(doc);
0658     if (ch == 0) {
0659         return DocumentChild::Type_Unknown;
0660     }
0661     return ch->type();
0662 }
0663 
0664 DocumentChild *Part::findChild(const Document *doc) const
0665 {
0666     foreach (const WorkPackage *wp, m_packageMap) {
0667         DocumentChild *c = wp->findChild(doc);
0668         if (c) {
0669             return c;
0670         }
0671     }
0672     return 0;
0673 }
0674 
0675 WorkPackage *Part::findWorkPackage(const Document *doc) const
0676 {
0677     foreach (const WorkPackage *wp, m_packageMap) {
0678         if (wp->contains(doc)) {
0679             return const_cast<WorkPackage*>(wp);
0680         }
0681     }
0682     return 0;
0683 }
0684 
0685 WorkPackage *Part::findWorkPackage(const DocumentChild *child) const
0686 {
0687     foreach (const WorkPackage *wp, m_packageMap) {
0688         if (wp->contains(child)) {
0689             return const_cast<WorkPackage*>(wp);
0690         }
0691     }
0692     return 0;
0693 }
0694 
0695 WorkPackage *Part::findWorkPackage(const Node *node) const
0696 {
0697     return m_packageMap.value(node->projectNode()->id() + node->id());
0698 }
0699 
0700 bool Part::editWorkpackageDocument(const Document *doc)
0701 {
0702     //debugPlanWork<<doc<<doc->url();
0703     // start in any suitable application
0704     return editOtherDocument(doc);
0705 }
0706 
0707 bool Part::editOtherDocument(const Document *doc)
0708 {
0709     Q_ASSERT(doc != 0);
0710     //debugPlanWork<<doc->url();
0711     WorkPackage *wp = findWorkPackage(doc);
0712     if (wp == 0) {
0713         KMessageBox::error(0, i18n("Edit failed. Cannot find a work package."));
0714         return false;
0715     }
0716     return wp->addChild(this, doc);
0717 }
0718 
0719 void Part::viewWorkpackageDocument(Document *doc)
0720 {
0721     debugPlanWork<<doc;
0722     if (doc == 0) {
0723         return;
0724     }
0725     QUrl filename;
0726     if (doc->sendAs() == Document::SendAs_Copy) {
0727         filename = extractFile(doc);
0728     } else {
0729         filename = doc->url();
0730     }
0731     // open for view
0732     viewDocument(filename);
0733 }
0734 
0735 bool Part::removeDocument(Document *doc)
0736 {
0737     if (doc == 0) {
0738         return false;
0739     }
0740     WorkPackage *wp = findWorkPackage(doc);
0741     if (wp == 0) {
0742         return false;
0743     }
0744     return wp->removeDocument(this, doc);
0745 }
0746 
0747 bool Part::viewDocument(const QUrl &filename)
0748 {
0749     debugPlanWork<<"url:"<<filename;
0750     if (! filename.isValid()) {
0751         //KMessageBox::error(0, i18n("Cannot open document. Invalid url: %1", filename.pathOrUrl()));
0752         return false;
0753     }
0754     KRun *run = new KRun(filename, 0);
0755     Q_UNUSED(run); // XXX: shouldn't run be deleted?
0756     return true;
0757 }
0758 
0759 void Part::setDocumentClean(bool clean)
0760 {
0761     debugPlanWork<<clean;
0762     setModified(! clean);
0763     if (! clean) {
0764         saveModifiedWorkPackages();
0765         return;
0766     }
0767 }
0768 
0769 void Part::setModified(bool mod)
0770 {
0771     KParts::ReadWritePart::setModified(mod);
0772     emit captionChanged(QString(), mod);
0773 }
0774 
0775 bool Part::saveAs(const QUrl &/*url*/)
0776 {
0777     return false;
0778 }
0779 
0780 void Part::saveModifiedWorkPackages()
0781 {
0782     foreach (WorkPackage *wp, m_packageMap) {
0783         if (wp->isModified()) {
0784             saveWorkPackage(wp);
0785         }
0786     }
0787     m_undostack->setClean();
0788 }
0789 
0790 void Part::saveWorkPackage(WorkPackage *wp)
0791 {
0792     wp->saveToProjects(this);
0793 }
0794 
0795 bool Part::saveWorkPackages(bool silent)
0796 {
0797     debugPlanWork<<silent;
0798     foreach (WorkPackage *wp, m_packageMap) {
0799         wp->saveToProjects(this);
0800     }
0801     m_undostack->setClean();
0802     return true;
0803 }
0804 
0805 bool Part::completeSaving(KoStore */*store*/)
0806 {
0807     return true;
0808 }
0809 
0810 QDomDocument Part::saveXML()
0811 {
0812     debugPlanWork;
0813     return QDomDocument();
0814 }
0815 
0816 bool Part::queryClose()
0817 {
0818     debugPlanWork;
0819     QList<WorkPackage*> modifiedList;
0820     foreach (WorkPackage *wp, m_packageMap) {
0821         switch (wp->queryClose(this)) {
0822             case KMessageBox::No:
0823                 modifiedList << wp;
0824                 break;
0825             case KMessageBox::Cancel:
0826                 debugPlanWork<<"Cancel";
0827                 return false;
0828         }
0829     }
0830     // closeEvent calls queryClose so modified must be reset or else wps are queried all over again
0831     foreach (WorkPackage *wp, modifiedList) {
0832         wp->setModified(false);
0833     }
0834     setModified(false);
0835     return true;
0836 }
0837 
0838 bool Part::openFile()
0839 {
0840     debugPlanWork<<localFilePath();
0841     return loadNativeFormatFromStore(localFilePath());
0842 }
0843 
0844 bool Part::saveFile()
0845 {
0846     return false;
0847 }
0848 
0849 }  //KPlatoWork namespace