1 /* This file is part of the KDE project
2  Copyright (C) 1998, 1999, 2000 Torben Weis <weis@kde.org>
3  Copyright (C) 2004 - 2009 Dag Andersen <danders@get2net.dk>
4  Copyright (C) 2006 Raphael Langerhorst <raphael.langerhorst@kdemail.net>
5  Copyright (C) 2007 Thorsten Zachmann <zachmann@kde.org>
6   Copyright (C) 2007 - 2009, 2012 Dag Andersen <danders@get2net.dk>
7 
8  This library is free software; you can redistribute it and/or
9  modify it under the terms of the GNU Library General Public
10  License as published by the Free Software Foundation; either
11  version 2 of the License, or (at your option) any later version.
12 
13  This library is distributed in the hope that it will be useful,
14  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  Library General Public License for more details.
17 
18  You should have received a copy of the GNU Library General Public License
19  along with this library; see the file COPYING.LIB.  If not, write to
20  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 * Boston, MA 02110-1301, USA.
22 */
23 
24 // clazy:excludeall=qstring-arg
25 #include "part.h"
26 #include "view.h"
27 #include "factory.h"
28 #include "mainwindow.h"
29 #include "workpackage.h"
30 #include "calligraplanworksettings.h"
31 
32 #include "KPlatoXmlLoader.h" //NB!
33 
34 #include "kptglobal.h"
35 #include "kptnode.h"
36 #include "kptproject.h"
37 #include "kpttask.h"
38 #include "kptdocuments.h"
39 #include "kptcommand.h"
40 
41 #include <KoXmlReader.h>
42 #include <KoStore.h>
43 #include <KoDocumentInfo.h>
44 #include <KoResourcePaths.h>
45 #include <KoComponentData.h>
46 
47 #include <QPainter>
48 #include <QFileInfo>
49 #include <QDir>
50 #include <QTimer>
51 #include <QFileSystemWatcher>
52 #include <kundo2qstack.h>
53 #include <QPointer>
54 #include <QUrl>
55 #include <QMimeDatabase>
56 #include <QApplication>
57 
58 #include <KLocalizedString>
59 #include <kmessagebox.h>
60 #include <kparts/partmanager.h>
61 #include <kopenwithdialog.h>
62 #include <kmimetypetrader.h>
63 //#include <kserviceoffer.h>
64 #include <KIO/DesktopExecParser>
65 #include <krun.h>
66 #include <kprocess.h>
67 #include <kactioncollection.h>
68 
69 #include "debugarea.h"
70 
71 using namespace KPlato;
72 
73 namespace KPlatoWork
74 {
75 
76 //-------------------------------
DocumentChild(WorkPackage * parent)77 DocumentChild::DocumentChild(WorkPackage *parent)
78     : QObject(parent),
79     m_doc(0),
80     m_type(Type_Unknown),
81     m_copy(false),
82     m_process(0),
83     m_editor(0),
84     m_editormodified(false),
85     m_filemodified(false),
86     m_fileSystemWatcher(new QFileSystemWatcher(this))
87 
88 {
89 }
90 
91 // DocumentChild::DocumentChild(KParts::ReadWritePart *editor, const QUrl &url, const Document *doc, Part *parent)
92 //     : KoDocumentChild(parent),
93 //     m_doc(doc),
94 //     m_type(Type_Unknown),
95 //     m_copy(true),
96 //     m_process(0),
97 //     m_editor(editor),
98 //     m_editormodified(false),
99 //     m_filemodified(false)
100 // {
101 //     setFileInfo(url);
102 //     if (dynamic_cast<KoDocument*>(editor)) {
103 //         debugPlanWork<<"Creating Calligra doc";
104 //         m_type = Type_Calligra;
105 //         connect(static_cast<KoDocument*>(editor), SIGNAL(modified(bool)), this, SLOT(setModified(bool)));
106 //     } else {
107 //         debugPlanWork<<"Creating KParts doc";
108 //         m_type = Type_KParts;
109 //         slotUpdateModified();
110 //     }
111 // }
112 
~DocumentChild()113 DocumentChild::~DocumentChild()
114 {
115     debugPlanWork;
116     disconnect(m_fileSystemWatcher, &QFileSystemWatcher::fileChanged, this, &DocumentChild::slotDirty);
117     m_fileSystemWatcher->removePath(filePath());
118 
119     if (m_type == Type_Calligra || m_type == Type_KParts) {
120         delete m_editor;
121     }
122 }
123 
parentPackage() const124 WorkPackage *DocumentChild::parentPackage() const
125 {
126     return static_cast<WorkPackage*>(parent());
127 }
128 
setFileInfo(const QUrl & url)129 void DocumentChild::setFileInfo(const QUrl &url)
130 {
131     m_fileinfo.setFile(url.path());
132     //debugPlanWork<<url;
133     bool res = connect(m_fileSystemWatcher, &QFileSystemWatcher::fileChanged, this, &DocumentChild::slotDirty);
134     //debugPlanWork<<res<<filePath();
135 #ifndef NDEBUG
136     Q_ASSERT(res);
137 #else
138     Q_UNUSED(res);
139 #endif
140     m_fileSystemWatcher->addPath(filePath());
141 }
142 
setModified(bool mod)143 void DocumentChild::setModified(bool mod)
144 {
145     debugPlanWork<<mod<<filePath();
146     if (m_editormodified != mod) {
147         m_editormodified = mod;
148         emit modified(mod);
149     }
150 }
151 
slotDirty(const QString & file)152 void DocumentChild::slotDirty(const QString &file)
153 {
154     //debugPlanWork<<filePath()<<file<<m_filemodified;
155     if (file == filePath() && ! m_filemodified) {
156         debugPlanWork<<file<<"is modified";
157         m_filemodified = true;
158         emit fileModified(true);
159     }
160 }
161 
slotUpdateModified()162 void DocumentChild::slotUpdateModified()
163 {
164     if (m_type == Type_KParts && m_editor && (m_editor->isModified() != m_editormodified)) {
165         setModified(m_editor->isModified());
166     }
167     QTimer::singleShot(500, this, &DocumentChild::slotUpdateModified);
168 }
169 
setDoc(const Document * doc)170 bool DocumentChild::setDoc(const Document *doc)
171 {
172     Q_ASSERT (m_doc == 0);
173     if (isOpen()) {
174         KMessageBox::error(0, i18n("Document is already open:<br>%1", doc->url().url()));
175         return false;
176     }
177     m_doc = doc;
178     QUrl url;
179     if (parentPackage()->newDocuments().contains(doc)) {
180         url = parentPackage()->newDocuments().value(doc);
181         Q_ASSERT(url.isValid());
182         parentPackage()->removeNewDocument(doc);
183     } else if (doc->sendAs() == Document::SendAs_Copy) {
184         url = parentPackage()->extractFile(doc);
185         if (url.url().isEmpty()) {
186             KMessageBox::error(0, i18n("Could not extract document from storage:<br>%1", doc->url().url()));
187             return false;
188         }
189         m_copy = true;
190     } else {
191         url = doc->url();
192     }
193     if (! url.isValid()) {
194         KMessageBox::error(0, i18n("Invalid URL:<br>%1", url.url()));
195         return false;
196     }
197     setFileInfo(url);
198     return true;
199 }
200 
openDoc(const Document * doc,KoStore * store)201 bool DocumentChild::openDoc(const Document *doc, KoStore *store)
202 {
203     Q_ASSERT (m_doc == 0);
204     if (isOpen()) {
205         KMessageBox::error(0, i18n("Document is already open:<br>%1", doc->url().path()));
206         return false;
207     }
208     m_doc = doc;
209     QUrl url;
210     if (doc->sendAs() == Document::SendAs_Copy) {
211         url = parentPackage()->extractFile(doc, store);
212         if (url.url().isEmpty()) {
213             KMessageBox::error(0, i18n("Could not extract document from storage:<br>%1", doc->url().path()));
214             return false;
215         }
216         m_copy = true;
217     } else {
218         url = doc->url();
219     }
220     if (! url.isValid()) {
221         KMessageBox::error(0, i18n("Invalid URL:<br>%1", url.url()));
222         return false;
223     }
224     setFileInfo(url);
225     return true;
226 }
227 
editDoc()228 bool DocumentChild::editDoc()
229 {
230     Q_ASSERT(m_doc != 0);
231     debugPlanWork<<"file:"<<filePath();
232     if (isOpen()) {
233         KMessageBox::error(0, i18n("Document is already open:<br> %1", m_doc->url().path()));
234         return false;
235     }
236     if (! m_fileinfo.exists()) {
237         KMessageBox::error(0, i18n("File does not exist:<br>%1", fileName()));
238         return false;
239     }
240     QUrl filename = QUrl::fromLocalFile(filePath());
241     const QMimeType mimetype = QMimeDatabase().mimeTypeForUrl(filename);
242     KService::Ptr service = KMimeTypeTrader::self()->preferredService(mimetype.name());
243     bool editing = startProcess(service, filename);
244     if (editing) {
245         m_type = Type_Other; // FIXME: try to be more specific
246     }
247     return editing;
248 }
249 
startProcess(KService::Ptr service,const QUrl & url)250 bool DocumentChild::startProcess(KService::Ptr service, const QUrl &url)
251 {
252     QStringList args;
253     QList<QUrl> files;
254     if (url.isValid()) {
255         files << url;
256     }
257     if (service) {
258         KIO::DesktopExecParser parser(*service, files);
259         parser.setUrlsAreTempFiles(false);
260         args = parser.resultingArguments();
261     } else {
262         QList<QUrl> list;
263         QPointer<KOpenWithDialog> dlg = new KOpenWithDialog(list, i18n("Edit with:"), QString(), 0);
264         if (dlg->exec() == QDialog::Accepted && dlg){
265             args << dlg->text();
266         }
267         if (args.isEmpty()) {
268             debugPlanWork<<"No executable selected";
269             return false;
270         }
271         args << url.url();
272         delete dlg;
273     }
274     debugPlanWork<<args;
275     m_process = new KProcess();
276     m_process->setProgram(args);
277     connect(m_process, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(slotEditFinished(int,QProcess::ExitStatus)));
278     connect(m_process, SIGNAL(error(QProcess::ProcessError)), SLOT(slotEditError(QProcess::ProcessError)));
279     m_process->start();
280     //debugPlanWork<<m_process->pid()<<m_process->program();
281     return true;
282 }
283 
isModified() const284 bool DocumentChild::isModified() const
285 {
286     return m_editormodified;
287 }
288 
isFileModified() const289 bool DocumentChild::isFileModified() const
290 {
291     return m_filemodified;
292 }
293 
slotEditFinished(int,QProcess::ExitStatus)294 void DocumentChild::slotEditFinished(int /*par*/,  QProcess::ExitStatus)
295 {
296     //debugPlanWork<<par<<filePath();
297     delete m_process;
298     m_process = 0;
299 }
300 
slotEditError(QProcess::ProcessError status)301 void DocumentChild::slotEditError(QProcess::ProcessError status)
302 {
303     debugPlanWork<<status;
304     if (status == QProcess::FailedToStart || status == QProcess::Crashed) {
305         m_process->deleteLater();
306         m_process = 0;
307     } else debugPlanWork<<"Error="<<status<<" what to do?";
308 }
309 
saveToStore(KoStore * store)310 bool DocumentChild::saveToStore(KoStore *store)
311 {
312     debugPlanWork<<filePath();
313     m_fileSystemWatcher->removePath(filePath());
314     bool ok = false;
315     bool wasmod = m_filemodified;
316     if (m_type == Type_Calligra || m_type == Type_KParts) {
317         if (m_editor->isModified()) {
318             ok = m_editor->save(); // hmmmm
319         } else {
320             ok = true;
321         }
322     } else if (m_type == Type_Other) {
323         if (isOpen()) {
324             warnPlanWork<<"External editor open";
325         }
326         ok = true;
327     } else {
328         errorPlanWork<<"Unknown document type";
329     }
330     if (ok) {
331         debugPlanWork<<"Add to store:"<<fileName();
332         store->addLocalFile(filePath(), fileName());
333         m_filemodified = false;
334         if (wasmod != m_filemodified) {
335             emit fileModified(m_filemodified);
336         }
337     }
338     m_fileSystemWatcher->addPath(filePath());
339     return ok;
340 }
341 
342 
343 //------------------------------------
Part(QWidget * parentWidget,QObject * parent,const QVariantList &)344 Part::Part(QWidget *parentWidget, QObject *parent, const QVariantList & /*args*/)
345     : KParts::ReadWritePart(parent),
346     m_xmlLoader(),
347     m_modified(false),
348     m_loadingFromProjectStore(false),
349     m_undostack(new KUndo2QStack(this))
350 {
351     debugPlanWork;
352     setComponentName(Factory::global().componentName(), Factory::global().componentDisplayName());
353     if (isReadWrite()) {
354         setXMLFile("calligraplanwork.rc");
355     } else {
356         setXMLFile("calligraplanwork_readonly.rc");
357     }
358 
359     m_view = new View(this, parentWidget, actionCollection());
360     setWidget(m_view);
361     connect(m_view, &View::viewDocument, this, &Part::viewWorkpackageDocument);
362 
363     loadWorkPackages();
364 
365     connect(m_undostack, &KUndo2QStack::cleanChanged, this, &Part::setDocumentClean);
366 
367 }
368 
~Part()369 Part::~Part()
370 {
371     debugPlanWork;
372 //    m_config.save();
373     // views must be deleted before packages
374     delete m_view;
375     qDeleteAll(m_packageMap);
376     PlanWorkSettings::self()->save();
377 }
378 
addCommand(KUndo2Command * cmd)379 void Part::addCommand(KUndo2Command *cmd)
380 {
381     if (cmd) {
382         m_undostack->push(cmd);
383     }
384 }
385 
setWorkPackage(WorkPackage * wp,KoStore * store)386 bool Part::setWorkPackage(WorkPackage *wp, KoStore *store)
387 {
388     //debugPlanWork;
389     QString id = wp->id();
390     if (m_packageMap.contains(id)) {
391         if (KMessageBox::warningYesNo(0, i18n("<p>The work package already exists in the projects store.</p>"
392                 "<p>Project: %1<br>Task: %2</p>"
393                 "<p>Do you want to update the existing package with data from the new?</p>",
394                 wp->project()->name(), wp->node()->name())) == KMessageBox::No) {
395             delete wp;
396             return false;
397         }
398         m_packageMap[ id ]->merge(this, wp, store);
399         delete wp;
400         return true;
401     }
402     wp->setFilePath(m_loadingFromProjectStore ? wp->fileName(this) : localFilePath());
403     m_packageMap[ id ] = wp;
404     if (! m_loadingFromProjectStore) {
405         wp->saveToProjects(this);
406     }
407     connect(wp->project(), SIGNAL(projectChanged()), wp, SLOT(projectChanged()));
408     connect (wp, SIGNAL(modified(bool)), this, SLOT(setModified(bool)));
409     emit workPackageAdded(wp, indexOf(wp));
410     connect(wp, &WorkPackage::saveWorkPackage, this, &Part::saveWorkPackage);
411     return true;
412 }
413 
removeWorkPackage(Node * node,MacroCommand * m)414 void Part::removeWorkPackage(Node *node, MacroCommand *m)
415 {
416     debugPlanWork<<node->name();
417     WorkPackage *wp = findWorkPackage(node);
418     if (wp == 0) {
419         KMessageBox::error(0, i18n("Remove failed. Cannot find work package"));
420         return;
421     }
422     PackageRemoveCmd *cmd = new PackageRemoveCmd(this, wp, kundo2_i18n("Remove work package"));
423     if (m) {
424         m->addCommand(cmd);
425     } else {
426         addCommand(cmd);
427     }
428 }
429 
removeWorkPackages(const QList<Node * > & nodes)430 void Part::removeWorkPackages(const QList<Node*> &nodes)
431 {
432     debugPlanWork<<nodes;
433     MacroCommand *m = new MacroCommand(kundo2_i18np("Remove work package", "Remove work packages", nodes.count()));
434     foreach (Node *n, nodes) {
435         removeWorkPackage(n, m);
436     }
437     if (m->isEmpty()) {
438         delete m;
439     } else {
440         addCommand(m);
441     }
442 }
443 
removeWorkPackage(WorkPackage * wp)444 void Part::removeWorkPackage(WorkPackage *wp)
445 {
446     //debugPlanWork;
447     int row = indexOf(wp);
448     if (row >= 0) {
449         const QList<QString> &lst = m_packageMap.keys();
450         const QString &key = lst.value(row);
451         m_packageMap.remove(key);
452         emit workPackageRemoved(wp, row);
453     }
454 }
455 
addWorkPackage(WorkPackage * wp)456 void Part::addWorkPackage(WorkPackage *wp)
457 {
458     //debugPlanWork;
459     QString id = wp->id();
460     Q_ASSERT(! m_packageMap.contains(id));
461     m_packageMap[ id ] = wp;
462     emit workPackageAdded(wp, indexOf(wp));
463 }
464 
loadWorkPackages()465 bool Part::loadWorkPackages()
466 {
467     m_loadingFromProjectStore = true;
468     const QStringList lst = KoResourcePaths::findAllResources("projects", "*.planwork", KoResourcePaths::Recursive | KoResourcePaths::NoDuplicates);
469     debugPlanWork<<lst;
470     foreach (const QString &file, lst) {
471         if (! loadNativeFormatFromStore(file)) {
472             KMessageBox::information(0, i18n("Failed to load file:<br>%1" , file));
473         }
474     }
475     m_loadingFromProjectStore = false;
476     return true;
477 
478 }
479 
loadNativeFormatFromStore(const QString & file)480 bool Part::loadNativeFormatFromStore(const QString& file)
481 {
482     debugPlanWork<<file;
483     KoStore * store = KoStore::createStore(file, KoStore::Read, "", KoStore::Auto);
484 
485     if (store->bad()) {
486         KMessageBox::error(0, i18n("Not a valid work package file:<br>%1", file));
487         delete store;
488         QApplication::restoreOverrideCursor();
489         return false;
490     }
491 
492     const bool success = loadNativeFormatFromStoreInternal(store);
493 
494     delete store;
495 
496     return success;
497 }
498 
loadNativeFormatFromStoreInternal(KoStore * store)499 bool Part::loadNativeFormatFromStoreInternal(KoStore * store)
500 {
501     if (store->hasFile("root")) {
502         KoXmlDocument doc;
503         bool ok = loadAndParse(store, "root", doc);
504         if (ok) {
505             ok = loadXML(doc, store);
506         }
507         if (!ok) {
508             QApplication::restoreOverrideCursor();
509             return false;
510         }
511 
512     } else {
513         errorPlanWork << "ERROR: No maindoc.xml" << endl;
514         KMessageBox::error(0, i18n("Invalid document. The document does not contain 'maindoc.xml'."));
515         QApplication::restoreOverrideCursor();
516         return false;
517     }
518 //     if (store->hasFile("documentinfo.xml")) {
519 //         KoXmlDocument doc;
520 //         if (oldLoadAndParse(store, "documentinfo.xml", doc)) {
521 //             d->m_docInfo->load(doc);
522 //         }
523 //     } else {
524 //         //debugPlanWork <<"cannot open document info";
525 //         delete d->m_docInfo;
526 //         d->m_docInfo = new KoDocumentInfo(this);
527 //     }
528 
529     bool res = completeLoading(store);
530     QApplication::restoreOverrideCursor();
531     return res;
532 }
533 
loadAndParse(KoStore * store,const QString & filename,KoXmlDocument & doc)534 bool Part::loadAndParse(KoStore* store, const QString& filename, KoXmlDocument& doc)
535 {
536     //debugPlanWork <<"Trying to open" << filename;
537 
538     if (!store->open(filename)) {
539         warnPlanWork << "Entry " << filename << " not found!";
540         KMessageBox::error(0, i18n("Failed to open file: %1", filename));
541         return false;
542     }
543     // Error variables for QDomDocument::setContent
544     QString errorMsg;
545     int errorLine, errorColumn;
546     bool ok = doc.setContent(store->device(), &errorMsg, &errorLine, &errorColumn);
547     store->close();
548     if (!ok) {
549         errorPlanWork << "Parsing error in " << filename << "! Aborting!" << endl
550         << " In line: " << errorLine << ", column: " << errorColumn << endl
551         << " Error message: " << errorMsg;
552         KMessageBox::error(0, i18n("Parsing error in file '%1' at line %2, column %3<br>Error message: %4", filename  , errorLine, errorColumn ,
553                                    QCoreApplication::translate("QXml", errorMsg.toUtf8(), 0)));
554         return false;
555     }
556     return true;
557 }
558 
loadXML(const KoXmlDocument & document,KoStore * store)559 bool Part::loadXML(const KoXmlDocument &document, KoStore* store)
560 {
561     debugPlanWork;
562     QString value;
563     KoXmlElement plan = document.documentElement();
564 
565     // Check if this is the right app
566     value = plan.attribute("mime", QString());
567     if (value.isEmpty()) {
568         errorPlanWork << "No mime type specified!" << endl;
569         KMessageBox::error(0, i18n("Invalid document. No mimetype specified."));
570         return false;
571     } else if (value == "application/x-vnd.kde.kplato.work") {
572         return loadKPlatoXML(document, store);
573     } else if (value != "application/x-vnd.kde.plan.work") {
574         errorPlanWork << "Unknown mime type " << value;
575         KMessageBox::error(0, i18n("Invalid document. Expected mimetype application/x-vnd.kde.plan.work, got %1", value));
576         return false;
577     }
578     QString syntaxVersion = plan.attribute("version", PLANWORK_FILE_SYNTAX_VERSION);
579     m_xmlLoader.setWorkVersion(syntaxVersion);
580     if (syntaxVersion > PLANWORK_FILE_SYNTAX_VERSION) {
581         KMessageBox::ButtonCode ret = KMessageBox::warningContinueCancel(
582                       0, i18n("This document is a newer version than supported by PlanWork (syntax version: %1)<br>"
583                                "Opening it in this version of PlanWork will lose some information.", syntaxVersion),
584                       i18n("File-Format Mismatch"), KGuiItem(i18n("Continue")));
585         if (ret == KMessageBox::Cancel) {
586             return false;
587         }
588     }
589     m_xmlLoader.setVersion(plan.attribute("plan-version", PLAN_FILE_SYNTAX_VERSION));
590     m_xmlLoader.startLoad();
591     WorkPackage *wp = new WorkPackage(m_loadingFromProjectStore);
592     wp->loadXML(plan, m_xmlLoader);
593     m_xmlLoader.stopLoad();
594     if (! setWorkPackage(wp, store)) {
595         // rejected, so nothing changed...
596         return true;
597     }
598     emit changed();
599     return true;
600 }
601 
loadKPlatoXML(const KoXmlDocument & document,KoStore *)602 bool Part::loadKPlatoXML(const KoXmlDocument &document, KoStore*)
603 {
604     debugPlanWork;
605     QString value;
606     KoXmlElement plan = document.documentElement();
607 
608     // Check if this is the right app
609     value = plan.attribute("mime", QString());
610     if (value.isEmpty()) {
611         errorPlanWork << "No mime type specified!" << endl;
612         KMessageBox::error(0, i18n("Invalid document. No mimetype specified."));
613         return false;
614     } else if (value != "application/x-vnd.kde.kplato.work") {
615         errorPlanWork << "Unknown mime type " << value;
616         KMessageBox::error(0, i18n("Invalid document. Expected mimetype application/x-vnd.kde.kplato.work, got %1", value));
617         return false;
618     }
619     QString syntaxVersion = plan.attribute("version", KPLATOWORK_MAX_FILE_SYNTAX_VERSION);
620     m_xmlLoader.setWorkVersion(syntaxVersion);
621     if (syntaxVersion > KPLATOWORK_MAX_FILE_SYNTAX_VERSION) {
622         KMessageBox::ButtonCode ret = KMessageBox::warningContinueCancel(
623                       0, i18n("This document is a newer version than supported by PlanWork (syntax version: %1)<br>"
624                                "Opening it in this version of PlanWork will lose some information.", syntaxVersion),
625                       i18n("File-Format Mismatch"), KGuiItem(i18n("Continue")));
626         if (ret == KMessageBox::Cancel) {
627             return false;
628         }
629     }
630     m_xmlLoader.setMimetype(value);
631     m_xmlLoader.setVersion(plan.attribute("kplato-version", KPLATO_MAX_FILE_SYNTAX_VERSION));
632     m_xmlLoader.startLoad();
633     WorkPackage *wp = new WorkPackage(m_loadingFromProjectStore);
634     wp->loadKPlatoXML(plan, m_xmlLoader);
635     m_xmlLoader.stopLoad();
636     if (! setWorkPackage(wp)) {
637         // rejected, so nothing changed...
638         return true;
639     }
640     emit changed();
641     return true;
642 }
643 
completeLoading(KoStore *)644 bool Part::completeLoading(KoStore *)
645 {
646     return true;
647 }
648 
extractFile(const Document * doc)649 QUrl Part::extractFile(const Document *doc)
650 {
651     WorkPackage *wp = findWorkPackage(doc);
652     return wp == 0 ? QUrl() : wp->extractFile(doc);
653 }
654 
docType(const Document * doc) const655 int Part::docType(const Document *doc) const
656 {
657     DocumentChild *ch = findChild(doc);
658     if (ch == 0) {
659         return DocumentChild::Type_Unknown;
660     }
661     return ch->type();
662 }
663 
findChild(const Document * doc) const664 DocumentChild *Part::findChild(const Document *doc) const
665 {
666     foreach (const WorkPackage *wp, m_packageMap) {
667         DocumentChild *c = wp->findChild(doc);
668         if (c) {
669             return c;
670         }
671     }
672     return 0;
673 }
674 
findWorkPackage(const Document * doc) const675 WorkPackage *Part::findWorkPackage(const Document *doc) const
676 {
677     foreach (const WorkPackage *wp, m_packageMap) {
678         if (wp->contains(doc)) {
679             return const_cast<WorkPackage*>(wp);
680         }
681     }
682     return 0;
683 }
684 
findWorkPackage(const DocumentChild * child) const685 WorkPackage *Part::findWorkPackage(const DocumentChild *child) const
686 {
687     foreach (const WorkPackage *wp, m_packageMap) {
688         if (wp->contains(child)) {
689             return const_cast<WorkPackage*>(wp);
690         }
691     }
692     return 0;
693 }
694 
findWorkPackage(const Node * node) const695 WorkPackage *Part::findWorkPackage(const Node *node) const
696 {
697     return m_packageMap.value(node->projectNode()->id() + node->id());
698 }
699 
editWorkpackageDocument(const Document * doc)700 bool Part::editWorkpackageDocument(const Document *doc)
701 {
702     //debugPlanWork<<doc<<doc->url();
703     // start in any suitable application
704     return editOtherDocument(doc);
705 }
706 
editOtherDocument(const Document * doc)707 bool Part::editOtherDocument(const Document *doc)
708 {
709     Q_ASSERT(doc != 0);
710     //debugPlanWork<<doc->url();
711     WorkPackage *wp = findWorkPackage(doc);
712     if (wp == 0) {
713         KMessageBox::error(0, i18n("Edit failed. Cannot find a work package."));
714         return false;
715     }
716     return wp->addChild(this, doc);
717 }
718 
viewWorkpackageDocument(Document * doc)719 void Part::viewWorkpackageDocument(Document *doc)
720 {
721     debugPlanWork<<doc;
722     if (doc == 0) {
723         return;
724     }
725     QUrl filename;
726     if (doc->sendAs() == Document::SendAs_Copy) {
727         filename = extractFile(doc);
728     } else {
729         filename = doc->url();
730     }
731     // open for view
732     viewDocument(filename);
733 }
734 
removeDocument(Document * doc)735 bool Part::removeDocument(Document *doc)
736 {
737     if (doc == 0) {
738         return false;
739     }
740     WorkPackage *wp = findWorkPackage(doc);
741     if (wp == 0) {
742         return false;
743     }
744     return wp->removeDocument(this, doc);
745 }
746 
viewDocument(const QUrl & filename)747 bool Part::viewDocument(const QUrl &filename)
748 {
749     debugPlanWork<<"url:"<<filename;
750     if (! filename.isValid()) {
751         //KMessageBox::error(0, i18n("Cannot open document. Invalid url: %1", filename.pathOrUrl()));
752         return false;
753     }
754     KRun *run = new KRun(filename, 0);
755     Q_UNUSED(run); // XXX: shouldn't run be deleted?
756     return true;
757 }
758 
setDocumentClean(bool clean)759 void Part::setDocumentClean(bool clean)
760 {
761     debugPlanWork<<clean;
762     setModified(! clean);
763     if (! clean) {
764         saveModifiedWorkPackages();
765         return;
766     }
767 }
768 
setModified(bool mod)769 void Part::setModified(bool mod)
770 {
771     KParts::ReadWritePart::setModified(mod);
772     emit captionChanged(QString(), mod);
773 }
774 
saveAs(const QUrl &)775 bool Part::saveAs(const QUrl &/*url*/)
776 {
777     return false;
778 }
779 
saveModifiedWorkPackages()780 void Part::saveModifiedWorkPackages()
781 {
782     foreach (WorkPackage *wp, m_packageMap) {
783         if (wp->isModified()) {
784             saveWorkPackage(wp);
785         }
786     }
787     m_undostack->setClean();
788 }
789 
saveWorkPackage(WorkPackage * wp)790 void Part::saveWorkPackage(WorkPackage *wp)
791 {
792     wp->saveToProjects(this);
793 }
794 
saveWorkPackages(bool silent)795 bool Part::saveWorkPackages(bool silent)
796 {
797     debugPlanWork<<silent;
798     foreach (WorkPackage *wp, m_packageMap) {
799         wp->saveToProjects(this);
800     }
801     m_undostack->setClean();
802     return true;
803 }
804 
completeSaving(KoStore *)805 bool Part::completeSaving(KoStore */*store*/)
806 {
807     return true;
808 }
809 
saveXML()810 QDomDocument Part::saveXML()
811 {
812     debugPlanWork;
813     return QDomDocument();
814 }
815 
queryClose()816 bool Part::queryClose()
817 {
818     debugPlanWork;
819     QList<WorkPackage*> modifiedList;
820     foreach (WorkPackage *wp, m_packageMap) {
821         switch (wp->queryClose(this)) {
822             case KMessageBox::No:
823                 modifiedList << wp;
824                 break;
825             case KMessageBox::Cancel:
826                 debugPlanWork<<"Cancel";
827                 return false;
828         }
829     }
830     // closeEvent calls queryClose so modified must be reset or else wps are queried all over again
831     foreach (WorkPackage *wp, modifiedList) {
832         wp->setModified(false);
833     }
834     setModified(false);
835     return true;
836 }
837 
openFile()838 bool Part::openFile()
839 {
840     debugPlanWork<<localFilePath();
841     return loadNativeFormatFromStore(localFilePath());
842 }
843 
saveFile()844 bool Part::saveFile()
845 {
846     return false;
847 }
848 
849 }  //KPlatoWork namespace
850