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

0001 /* This file is part of the KDE project
0002  Copyright (C) 2009, 2012 Dag Andersen <danders@get2net.dk>
0003  Copyright (C) 2019 Dag Andersen <danders@get2net.dk>
0004  
0005  This library is free software; you can redistribute it and/or
0006  modify it under the terms of the GNU Library General Public
0007  License as published by the Free Software Foundation; either
0008  version 2 of the License, or (at your option) any later version.
0009 
0010  This library is distributed in the hope that it will be useful,
0011  but WITHOUT ANY WARRANTY; without even the implied warranty of
0012  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0013  Library General Public License for more details.
0014 
0015  You should have received a copy of the GNU Library General Public License
0016  along with this library; see the file COPYING.LIB.  If not, write to
0017  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0018  Boston, MA 02110-1301, USA.
0019 */
0020 
0021 // clazy:excludeall=qstring-arg
0022 #include "workpackage.h"
0023 
0024 #include "KPlatoXmlLoader.h" //NOTE: this file should probably be moved
0025 
0026 #include "part.h"
0027 #include "kptglobal.h"
0028 #include "kptnode.h"
0029 #include "kptproject.h"
0030 #include "kptdocuments.h"
0031 #include "kptcommand.h"
0032 #include "kptxmlloaderobject.h"
0033 #include "XmlSaveContext.h"
0034 #include "kptconfigbase.h"
0035 #include "kptcommonstrings.h"
0036 
0037 #include <KoStore.h>
0038 #include <KoXmlReader.h>
0039 #include <KoStoreDevice.h>
0040 #include <KoResourcePaths.h>
0041 
0042 #include <QDir>
0043 #include <QUrl>
0044 #include <QTimer>
0045 #include <QDateTime>
0046 #include <QDomDocument>
0047 
0048 #include <kmessagebox.h>
0049 
0050 
0051 #include "debugarea.h"
0052 
0053 using namespace KPlato;
0054 
0055 namespace KPlatoWork
0056 {
0057 
0058 WorkPackage::WorkPackage(bool fromProjectStore)
0059     : m_project(new Project()),
0060     m_fromProjectStore(fromProjectStore),
0061     m_modified(false)
0062 {
0063     m_project->setConfig(&m_config);
0064 }
0065 
0066 WorkPackage::WorkPackage(Project *project, bool fromProjectStore)
0067     : m_project(project),
0068     m_fromProjectStore(fromProjectStore),
0069     m_modified(false)
0070 {
0071     Q_ASSERT(project);
0072     Q_ASSERT (project->childNode(0));
0073 
0074     m_project->setConfig(&m_config);
0075 
0076     if (! project->scheduleManagers().isEmpty()) {
0077         // should be only one manager, so just get the first
0078         const QList<ScheduleManager*> &lst = m_project->scheduleManagers();
0079         project->setCurrentSchedule(lst.first()->scheduleId());
0080     }
0081     connect(project, &KPlato::Project::projectChanged, this, &WorkPackage::projectChanged);
0082 
0083 }
0084 
0085 WorkPackage::~WorkPackage()
0086 {
0087     delete m_project;
0088     qDeleteAll(m_childdocs);
0089 }
0090 
0091 void WorkPackage::setSettings(const WorkPackageSettings &settings)
0092 {
0093     if (m_settings != settings) {
0094         m_settings = settings;
0095         setModified(true);
0096     }
0097 }
0098 
0099 //TODO find a way to know when changes are undone
0100 void WorkPackage::projectChanged()
0101 {
0102     debugPlanWork;
0103     setModified(true);
0104 }
0105 
0106 bool WorkPackage::addChild(Part */*part*/, const Document *doc)
0107 {
0108     DocumentChild *ch = findChild(doc);
0109     if (ch) {
0110         if (ch->isOpen()) {
0111             KMessageBox::error(0, i18n("Document is already open"));
0112             return false;
0113         }
0114     } else {
0115         ch = new DocumentChild(this);
0116         if (! ch->setDoc(doc)) {
0117             delete ch;
0118             return false;
0119         }
0120     }
0121     if (! ch->editDoc()) {
0122         delete ch;
0123         return false;
0124     }
0125     if (! m_childdocs.contains(ch)) {
0126         m_childdocs.append(ch);
0127         connect(ch, &DocumentChild::fileModified, this, &WorkPackage::slotChildModified);
0128     }
0129     return true;
0130 }
0131 
0132 void WorkPackage::slotChildModified(bool mod)
0133 {
0134     debugPlanWork<<mod;
0135     emit modified(isModified());
0136     emit saveWorkPackage(this);
0137 }
0138 
0139 void WorkPackage::removeChild(DocumentChild *child)
0140 {
0141     disconnect(child, &DocumentChild::fileModified, this, &WorkPackage::slotChildModified);
0142 
0143     int i = m_childdocs.indexOf(child);
0144     if (i != -1) {
0145         // TODO: process etc
0146         m_childdocs.removeAt(i);
0147         delete child;
0148     } else {
0149         warnPlanWork<<"Could not find document child";
0150     }
0151 }
0152 
0153 bool WorkPackage::contains(const Document *doc) const
0154 {
0155     return node() ? node()->documents().contains(doc) : false;
0156 }
0157 
0158 DocumentChild *WorkPackage::findChild(const Document *doc) const
0159 {
0160     foreach (DocumentChild *c, m_childdocs) {
0161         if (c->doc() == doc) {
0162             return c;
0163         }
0164     }
0165     return 0;
0166 }
0167 
0168 bool WorkPackage::loadXML(const KoXmlElement &element, XMLLoaderObject &status)
0169 {
0170     bool ok = false;
0171     QString wbsCode = "Unknown";
0172     KoXmlNode n = element.firstChild();
0173     for (; ! n.isNull(); n = n.nextSibling()) {
0174         if (! n.isElement()) {
0175             continue;
0176         }
0177         KoXmlElement e = n.toElement();
0178         debugPlanWork<<e.tagName();
0179         if (e.tagName() == "project") {
0180             status.setProject(m_project);
0181             debugPlanWork<<"loading new project";
0182             if (! (ok = m_project->load(e, status))) {
0183                 status.addMsg(XMLLoaderObject::Errors, "Loading of work package failed");
0184                 KMessageBox::error(0, i18n("Failed to load project: %1" , m_project->name()));
0185             } else {
0186                 KoXmlElement te = e.namedItem("task").toElement();
0187                 if (!te.isNull()) {
0188                     wbsCode = te.attribute("wbs", "empty");
0189                 }
0190             }
0191         }
0192     }
0193     if (ok) {
0194         KoXmlNode n = element.firstChild();
0195         for (; ! n.isNull(); n = n.nextSibling()) {
0196             if (! n.isElement()) {
0197                 continue;
0198             }
0199             KoXmlElement e = n.toElement();
0200             debugPlanWork<<e.tagName();
0201             if (e.tagName() == "workpackage") {
0202                 Task *t = static_cast<Task*>(m_project->childNode(0));
0203                 t->workPackage().setOwnerName(e.attribute("owner"));
0204                 t->workPackage().setOwnerId(e.attribute("owner-id"));
0205                 m_sendUrl = QUrl(e.attribute("save-url"));
0206                 m_fetchUrl = QUrl(e.attribute("load-url"));
0207                 m_wbsCode = wbsCode;
0208 
0209                 Resource *r = m_project->findResource(t->workPackage().ownerId());
0210                 if (r == 0) {
0211                     debugPlanWork<<"Cannot find resource id!!"<<t->workPackage().ownerId()<<t->workPackage().ownerName();
0212                 }
0213                 debugPlanWork<<"is this me?"<<t->workPackage().ownerName();
0214                 KoXmlNode ch = e.firstChild();
0215                 for (; ! ch.isNull(); ch = ch.nextSibling()) {
0216                     if (! ch.isElement()) {
0217                         continue;
0218                     }
0219                     KoXmlElement el = ch.toElement();
0220                     debugPlanWork<<el.tagName();
0221                     if (el.tagName() == "settings") {
0222                         m_settings.loadXML(el);
0223                     }
0224                 }
0225             }
0226         }
0227     }
0228     if (! m_project->scheduleManagers().isEmpty()) {
0229         // should be only one manager
0230         const QList<ScheduleManager*> &lst = m_project->scheduleManagers();
0231         m_project->setCurrentSchedule(lst.first()->scheduleId());
0232     }
0233     return ok;
0234 }
0235 
0236 bool WorkPackage::loadKPlatoXML(const KoXmlElement &element, XMLLoaderObject &status)
0237 {
0238     bool ok = false;
0239     KoXmlNode n = element.firstChild();
0240     for (; ! n.isNull(); n = n.nextSibling()) {
0241         if (! n.isElement()) {
0242             continue;
0243         }
0244         KoXmlElement e = n.toElement();
0245         debugPlanWork<<e.tagName();
0246         if (e.tagName() == "project") {
0247             status.setProject(m_project);
0248             KPlatoXmlLoader loader(status, m_project);
0249             debugPlanWork<<"loading new project";
0250             if (! (ok = loader.load(m_project, e, status))) {
0251                 status.addMsg(XMLLoaderObject::Errors, "Loading of work package failed");
0252                 KMessageBox::error(0, i18n("Failed to load project: %1" , m_project->name()));
0253             }
0254         }
0255     }
0256     if (ok) {
0257         KoXmlNode n = element.firstChild();
0258         for (; ! n.isNull(); n = n.nextSibling()) {
0259             if (! n.isElement()) {
0260                 continue;
0261             }
0262             KoXmlElement e = n.toElement();
0263             debugPlanWork<<e.tagName();
0264             if (e.tagName() == "workpackage") {
0265                 Task *t = static_cast<Task*>(m_project->childNode(0));
0266                 t->workPackage().setOwnerName(e.attribute("owner"));
0267                 t->workPackage().setOwnerId(e.attribute("owner-id"));
0268 
0269                 Resource *r = m_project->findResource(t->workPackage().ownerId());
0270                 if (r == 0) {
0271                     debugPlanWork<<"Cannot find resource id!!"<<t->workPackage().ownerId()<<t->workPackage().ownerName();
0272                 }
0273                 debugPlanWork<<"is this me?"<<t->workPackage().ownerName();
0274                 KoXmlNode ch = e.firstChild();
0275                 for (; ! ch.isNull(); ch = ch.nextSibling()) {
0276                     if (! ch.isElement()) {
0277                         continue;
0278                     }
0279                     KoXmlElement el = ch.toElement();
0280                     debugPlanWork<<el.tagName();
0281                     if (el.tagName() == "settings") {
0282                         m_settings.loadXML(el);
0283                     }
0284                 }
0285             }
0286         }
0287     }
0288     if (! m_project->scheduleManagers().isEmpty()) {
0289         // should be only one manager
0290         const QList<ScheduleManager*> &lst = m_project->scheduleManagers();
0291         m_project->setCurrentSchedule(lst.first()->scheduleId());
0292     }
0293     return ok;
0294 }
0295 
0296 bool WorkPackage::saveToStream(QIODevice * dev)
0297 {
0298     QDomDocument doc = saveXML();
0299     // Save to buffer
0300     QByteArray s = doc.toByteArray(); // utf8 already
0301     dev->open(QIODevice::WriteOnly);
0302     int nwritten = dev->write(s.data(), s.size());
0303     if (nwritten != (int)s.size())
0304         warnPlanWork << "wrote " << nwritten << "- expected" <<  s.size();
0305     return nwritten == (int)s.size();
0306 }
0307 
0308 bool WorkPackage::saveNativeFormat(Part */*part*/, const QString &path)
0309 {
0310     if (path.isEmpty()) {
0311         KMessageBox::error(0, i18n("Cannot save to empty filename"));
0312         return false;
0313     }
0314     debugPlanWork<<node()->name()<<path;
0315     KoStore* store = KoStore::createStore(path, KoStore::Write, "application/x-vnd.kde.plan.work", KoStore::Auto);
0316     if (store->bad()) {
0317         KMessageBox::error(0, i18n("Could not create the file for saving"));
0318         delete store;
0319         return false;
0320     }
0321     if (store->open("root")) {
0322         KoStoreDevice dev(store);
0323         if (! saveToStream(&dev) || ! store->close()) {
0324             debugPlanWork << "saveToStream failed";
0325             delete store;
0326             return false;
0327         }
0328     } else {
0329         KMessageBox::error(0, i18n("Not able to write '%1'. Partition full?", QString("maindoc.xml")));
0330         delete store;
0331         return false;
0332     }
0333 
0334     if (!completeSaving(store)) {
0335         delete store;
0336         return false;
0337     }
0338     if (!store->finalize()) {
0339         delete store;
0340         return false;
0341     }
0342     // Success
0343     delete store;
0344     m_modified = false;
0345     return true;
0346 }
0347 
0348 bool WorkPackage::completeSaving(KoStore *store)
0349 {
0350     debugPlanWork;
0351     KoStore *oldstore = KoStore::createStore(filePath(), KoStore::Read, "", KoStore::Zip);
0352     if (oldstore->bad()) {
0353         KMessageBox::error(0, i18n("Failed to open store:\n %1", filePath()));
0354         return false;
0355     }
0356     if (oldstore->hasFile("documentinfo.xml")) {
0357         copyFile(oldstore, store, "documentinfo.xml");
0358     }
0359     if (oldstore->hasFile("preview.png")) {
0360         copyFile(oldstore, store, "preview.png");
0361     }
0362 
0363     // First get all open documents
0364     debugPlanWork<<m_childdocs.count();
0365     foreach (DocumentChild *cd, m_childdocs) {
0366         if (! cd->saveToStore(store)) {
0367         }
0368     }
0369     // Then get new files
0370     foreach (const Document *doc,  node()->documents().documents()) {
0371         if (m_newdocs.contains(doc)) {
0372             store->addLocalFile(m_newdocs[ doc ].path(), doc->url().fileName());
0373             m_newdocs.remove(doc);
0374             // TODO remove temp file ??
0375         }
0376     }
0377     // Then get files from the old store copied to the new store
0378     foreach (Document *doc,  node()->documents().documents()) {
0379         if (doc->sendAs() != Document::SendAs_Copy) {
0380             continue;
0381         }
0382         if (! store->hasFile(doc->url().fileName())) {
0383             copyFile(oldstore, store, doc->url().fileName());
0384         }
0385     }
0386     return true;
0387 }
0388 
0389 QString WorkPackage::fileName(const Part *part) const
0390 {
0391     Q_UNUSED(part);
0392     if (m_project == 0) {
0393         warnPlanWork<<"No project in this package";
0394         return QString();
0395     }
0396     Node *n = node();
0397     if (n == 0) {
0398         warnPlanWork<<"No node in this project";
0399         return QString();
0400     }
0401     QString projectName = m_project->name().remove(' ');
0402     // FIXME: workaround: KoResourcePaths::saveLocation("projects", projectName + '/');
0403     const QString path = KoResourcePaths::saveLocation("appdata", "projects/" + projectName + '/');
0404     QString wpName = n->name();
0405     wpName = QString(wpName.remove(' ').replace('/', '_') + '_' + n->id() + ".planwork");
0406     return path + wpName;
0407 }
0408 
0409 void WorkPackage::removeFile()
0410 {
0411     QFile file(m_filePath);
0412     if (! file.exists()) {
0413         warnPlanWork<<"No project in this package";
0414         return;
0415     }
0416     file.remove();
0417 }
0418 
0419 void WorkPackage::saveToProjects(Part *part)
0420 {
0421     debugPlanWork;
0422     QString path = fileName(part);
0423     debugPlanWork<<node()->name();
0424     if (saveNativeFormat(part, path)) {
0425         m_fromProjectStore = true;
0426         m_filePath = path;
0427     } else {
0428         KMessageBox::error(0, i18n("Cannot save to projects store:\n%1" , path));
0429     }
0430     return;
0431 }
0432 
0433 bool WorkPackage::isModified() const
0434 {
0435     if (m_modified) {
0436         return true;
0437     }
0438     foreach (DocumentChild *ch, m_childdocs) {
0439         if (ch->isModified() || ch->isFileModified()) {
0440             return true;
0441         }
0442     }
0443     return false;
0444 }
0445 
0446 QString WorkPackage::name() const
0447 {
0448     Task *t = task();
0449     return t ? t->name() : QString();
0450 }
0451 
0452 Node *WorkPackage::node() const
0453 {
0454     return m_project == 0 ? 0 : m_project->childNode(0);
0455 }
0456 
0457 Task *WorkPackage::task() const
0458 {
0459     Task *task = qobject_cast<Task*>(node());
0460     Q_ASSERT(task);
0461     return task;
0462 }
0463 
0464 bool WorkPackage::removeDocument(Part *part, Document *doc)
0465 {
0466     Node *n = node();
0467     if (n == 0) {
0468         return false;
0469     }
0470     part->addCommand(new DocumentRemoveCmd(n->documents(), doc, UndoText::removeDocument()));
0471     return true;
0472 }
0473 
0474 bool WorkPackage::copyFile(KoStore *from, KoStore *to, const QString &filename)
0475 {
0476     QByteArray data;
0477     if (! from->extractFile(filename , data)) {
0478         KMessageBox::error(0, i18n("Failed read file:\n %1", filename));
0479         return false;
0480     }
0481     if (! to->addDataToFile(data, filename)) {
0482         KMessageBox::error(0, i18n("Failed write file:\n %1", filename));
0483         return false;
0484     }
0485     debugPlanWork<<"Copied file:"<<filename;
0486     return true;
0487 }
0488 
0489 QDomDocument WorkPackage::saveXML()
0490 {
0491     debugPlanWork;
0492     QDomDocument document("plan-workpackage");
0493 
0494     document.appendChild(document.createProcessingInstruction(
0495                               "xml",
0496                               "version=\"1.0\" encoding=\"UTF-8\""));
0497 
0498     QDomElement doc = document.createElement("planwork");
0499     doc.setAttribute("editor", "PlanWork");
0500     doc.setAttribute("mime", "application/x-vnd.kde.plan.work");
0501     doc.setAttribute("version", PLANWORK_FILE_SYNTAX_VERSION);
0502     doc.setAttribute("plan-version", PLAN_FILE_SYNTAX_VERSION);
0503     document.appendChild(doc);
0504 
0505     // Work package info
0506     QDomElement wp = document.createElement("workpackage");
0507     wp.setAttribute("time-tag", QDateTime::currentDateTime().toString(Qt::ISODate));
0508     m_settings.saveXML(wp);
0509     Task *t = qobject_cast<Task*>(node());
0510     if (t) {
0511         wp.setAttribute("owner", t->workPackage().ownerName());
0512         wp.setAttribute("owner-id", t->workPackage().ownerId());
0513     }
0514     doc.appendChild(wp);
0515     m_project->save(doc, XmlSaveContext());
0516     return document;
0517 }
0518 
0519 void WorkPackage::merge(Part *part, const WorkPackage *wp, KoStore *store)
0520 {
0521     debugPlanWork;
0522     const Node *from = wp->node();
0523     Node *to = node();
0524 
0525     MacroCommand *m = new MacroCommand(kundo2_i18n("Merge data"));
0526     if (m_wbsCode != wp->wbsCode()) {
0527         m->addCommand(new ModifyWbsCodeCmd(this, wp->wbsCode()));
0528     }
0529     if (to->name() != from->name()) {
0530         m->addCommand(new NodeModifyNameCmd(*to, from->name()));
0531     }
0532     if (to->description() != from->description()) {
0533         m->addCommand(new NodeModifyDescriptionCmd(*to, from->description()));
0534     }
0535     if (to->startTime() != from->startTime() && from->startTime().isValid()) {
0536         m->addCommand(new NodeModifyStartTimeCmd(*to, from->startTime()));
0537     }
0538     if (to->endTime() != from->endTime() && from->endTime().isValid()) {
0539         m->addCommand(new NodeModifyEndTimeCmd(*to, from->endTime()));
0540     }
0541     if (to->leader() != from->leader()) {
0542         m->addCommand(new NodeModifyLeaderCmd(*to, from->leader()));
0543     }
0544 
0545     if (from->type() == Node::Type_Task && from->type() == Node::Type_Task) {
0546         if (static_cast<Task*>(to)->workPackage().ownerId() != static_cast<const Task*>(from)->workPackage().ownerId()) {
0547             debugPlanWork<<"merge:"<<"different owners"<<static_cast<const Task*>(from)->workPackage().ownerName()<<static_cast<Task*>(to)->workPackage().ownerName();
0548             if (static_cast<Task*>(to)->workPackage().ownerId().isEmpty()) {
0549                 //TODO cmd
0550                 static_cast<Task*>(to)->workPackage().setOwnerId(static_cast<const Task*>(from)->workPackage().ownerId());
0551                 static_cast<Task*>(to)->workPackage().setOwnerName(static_cast<const Task*>(from)->workPackage().ownerName());
0552             }
0553         }
0554         foreach (Document *doc,  from->documents().documents()) {
0555             Document *org = to->documents().findDocument(doc->url());
0556             if (org) {
0557                 // TODO: also handle modified type, sendas
0558                 // update ? what if open, modified ...
0559                 if (doc->type() == Document::Type_Product) {
0560                     //### FIXME. user feedback
0561                     warnPlanWork<<"We do not update existing deliverables (except name change)";
0562                     if (doc->name() != org->name()) {
0563                         m->addCommand(new DocumentModifyNameCmd(org, doc->name()));
0564                     }
0565                 } else {
0566                     if (doc->name() != org->name()) {
0567                         m->addCommand(new DocumentModifyNameCmd(org, doc->name()));
0568                     }
0569                     if (doc->sendAs() != org->sendAs()) {
0570                         m->addCommand(new DocumentModifySendAsCmd(org, doc->sendAs()));
0571                     }
0572                     if (doc->sendAs() == Document::SendAs_Copy) {
0573                         debugPlanWork<<"Update existing doc:"<<org->url();
0574                         openNewDocument(org, store);
0575                     }
0576                 }
0577             } else {
0578                 debugPlanWork<<"new document:"<<doc->typeToString(doc->type())<<doc->url();
0579                 Document *newdoc = new Document(*doc);
0580                 m->addCommand(new DocumentAddCmd(to->documents(), newdoc));
0581                 if (doc->sendAs() == Document::SendAs_Copy) {
0582                     debugPlanWork<<"Copy file";
0583                     openNewDocument(newdoc, store);
0584                 }
0585             }
0586         }
0587     }
0588     const Project *fromProject = wp->project();
0589     Project *toProject = m_project;
0590     const ScheduleManager *fromSm = fromProject->scheduleManagers().value(0);
0591     Q_ASSERT(fromSm);
0592     ScheduleManager *toSm = toProject->scheduleManagers().value(0);
0593     Q_ASSERT(toSm);
0594     if (fromSm->managerId() != toSm->managerId() || fromSm->scheduleId() != toSm->scheduleId()) {
0595         // rescheduled, update schedules
0596         m->addCommand(new CopySchedulesCmd(*fromProject, *toProject));
0597     }
0598     if (m->isEmpty()) {
0599         delete m;
0600     } else {
0601         part->addCommand(m);
0602     }
0603 }
0604 
0605 void WorkPackage::openNewDocument(const Document *doc, KoStore *store)
0606 {
0607     const QUrl url = extractFile(doc, store);
0608     if (url.url().isEmpty()) {
0609         KMessageBox::error(0, i18n("Could not extract document from storage:<br>%1", doc->url().path()));
0610         return;
0611     }
0612     if (! url.isValid()) {
0613         KMessageBox::error(0, i18n("Invalid URL:<br>%1", url.path()));
0614         return;
0615     }
0616     m_newdocs.insert(doc, url);
0617 }
0618 
0619 int WorkPackage::queryClose(Part *part)
0620 {
0621     debugPlanWork<<isModified();
0622     QString name = node()->name();
0623     QStringList lst;
0624     if (! m_childdocs.isEmpty()) {
0625         foreach (DocumentChild *ch, m_childdocs) {
0626             if (ch->isOpen() && ch->doc()->sendAs() == Document::SendAs_Copy) {
0627                 lst << ch->doc()->url().fileName();
0628             }
0629         }
0630     }
0631     if (! lst.isEmpty()) {
0632         KMessageBox::ButtonCode result = KMessageBox::warningContinueCancelList(0,
0633                     i18np(
0634                         "<p>The work package <b>'%2'</b> has an open document.</p><p>Data may be lost if you continue.</p>",
0635                         "<p>The work package <b>'%2'</b> has open documents.</p><p>Data may be lost if you continue.</p>",
0636                         lst.count(),
0637                         name),
0638                     lst);
0639 
0640         switch (result) {
0641             case KMessageBox::Continue: {
0642                 debugPlanWork<<"Continue";
0643                 break;
0644             }
0645             default: // case KMessageBox::Cancel :
0646                 debugPlanWork<<"Cancel";
0647                 return KMessageBox::Cancel;
0648                 break;
0649         }
0650     }
0651     if (! isModified()) {
0652         return KMessageBox::Yes;
0653     }
0654     KMessageBox::ButtonCode res = KMessageBox::warningYesNoCancel(0,
0655                 i18n("<p>The work package <b>'%1'</b> has been modified.</p><p>Do you want to save it?</p>", name),
0656                 QString(),
0657                 KStandardGuiItem::save(),
0658                 KStandardGuiItem::discard());
0659 
0660     switch (res) {
0661         case KMessageBox::Yes: {
0662             debugPlanWork<<"Yes";
0663             saveToProjects(part);
0664             break;
0665         }
0666         case KMessageBox::No:
0667             debugPlanWork<<"No";
0668             break;
0669         default: // case KMessageBox::Cancel :
0670             debugPlanWork<<"Cancel";
0671             break;
0672     }
0673     return res;
0674 }
0675 
0676 QUrl WorkPackage::extractFile(const Document *doc)
0677 {
0678     KoStore *store = KoStore::createStore(m_filePath, KoStore::Read, "", KoStore::Zip);
0679     if (store->bad())
0680     {
0681         KMessageBox::error(0, i18n("<p>Work package <b>'%1'</b></p><p>Could not open store:</p><p>%2</p>", node()->name(), m_filePath));
0682         delete store;
0683         return QUrl();
0684     }
0685     const QUrl url = extractFile(doc, store);
0686     delete store;
0687     return url;
0688 }
0689 
0690 QUrl WorkPackage::extractFile(const Document *doc, KoStore *store)
0691 {
0692     //FIXME: should use a special tmp dir
0693     QString tmp = QDir::tempPath() + QLatin1Char('/') + doc->url().fileName();
0694     const QUrl url = QUrl::fromLocalFile(tmp);
0695     debugPlanWork<<"Extract: "<<doc->url().fileName()<<" -> "<<url.path();
0696     if (! store->extractFile(doc->url().fileName(), url.path())) {
0697         KMessageBox::error(0, i18n("<p>Work package <b>'%1'</b></p><p>Could not extract file:</p><p>%2</p>", node()->name(), doc->url().fileName()));
0698         return QUrl();
0699     }
0700     return url;
0701 }
0702 
0703 QString WorkPackage::id() const
0704 {
0705     QString id;
0706     if (node()) {
0707         id = m_project->id() + node()->id();
0708     }
0709     return id;
0710 }
0711 
0712 //--------------------------------
0713 PackageRemoveCmd::PackageRemoveCmd(Part *part, WorkPackage *value, const KUndo2MagicString& name)
0714     : NamedCommand(name),
0715     m_part(part),
0716     m_value(value),
0717     m_mine(false)
0718 {
0719 }
0720 PackageRemoveCmd::~PackageRemoveCmd()
0721 {
0722     if (m_mine) {
0723         m_value->removeFile();
0724         delete m_value;
0725     }
0726 }
0727 void PackageRemoveCmd::execute()
0728 {
0729     m_part->removeWorkPackage(m_value);
0730     m_mine = true;
0731 }
0732 void PackageRemoveCmd::unexecute()
0733 {
0734     m_part->addWorkPackage(m_value);
0735     m_mine = false;
0736 }
0737 
0738 //---------------------
0739 CopySchedulesCmd::CopySchedulesCmd(const Project &fromProject, Project &toProject, const KUndo2MagicString &name)
0740     : NamedCommand(name),
0741       m_project(toProject)
0742 {
0743     QDomDocument olddoc;
0744     QDomElement e = olddoc.createElement("old");
0745     olddoc.appendChild(e);
0746     toProject.save(e, XmlSaveContext());
0747     m_olddoc = olddoc.toString();
0748 
0749     QDomDocument newdoc;
0750     e = newdoc.createElement("new");
0751     newdoc.appendChild(e);
0752     fromProject.save(e, XmlSaveContext());
0753     m_newdoc = newdoc.toString();
0754 }
0755 void CopySchedulesCmd::execute()
0756 {
0757     load(m_newdoc);
0758 }
0759 void CopySchedulesCmd::unexecute()
0760 {
0761     load(m_olddoc);
0762 }
0763 
0764 void CopySchedulesCmd::load(const QString &doc)
0765 {
0766     clearSchedules();
0767 
0768     KoXmlDocument d;
0769     d.setContent(doc);
0770     KoXmlElement proj = d.documentElement().namedItem("project").toElement();
0771     Q_ASSERT(! proj.isNull());
0772     KoXmlElement task = proj.namedItem("task").toElement();
0773     Q_ASSERT(! task.isNull());
0774     KoXmlElement ts = task.namedItem("schedules").namedItem("schedule").toElement();
0775     Q_ASSERT(! ts.isNull());
0776     KoXmlElement ps = proj.namedItem("schedules").namedItem("plan").toElement();
0777     Q_ASSERT(! ps.isNull());
0778 
0779     XMLLoaderObject status;
0780     status.setProject(&m_project);
0781     status.setVersion(PLAN_FILE_SYNTAX_VERSION);
0782     // task first
0783     NodeSchedule *ns = new NodeSchedule();
0784     if (ns->loadXML(ts, status)) {
0785         debugPlanWork<<ns->name()<<ns->type()<<ns->id();
0786         ns->setNode(m_project.childNode(0));
0787         m_project.childNode(0)->addSchedule(ns);
0788     } else {
0789         Q_ASSERT(false);
0790         delete ns;
0791     }
0792     // schedule manager next (includes main schedule and resource schedule)
0793     ScheduleManager *sm = new ScheduleManager(m_project);
0794     if (sm->loadXML(ps, status)) {
0795         m_project.addScheduleManager(sm);
0796     } else {
0797         Q_ASSERT(false);
0798         delete sm;
0799     }
0800     if (sm) {
0801         m_project.setCurrentSchedule(sm->scheduleId());
0802     }
0803     m_project.childNode(0)->changed();
0804 }
0805 
0806 void CopySchedulesCmd::clearSchedules()
0807 {
0808     foreach (Schedule *s, m_project.schedules()) {
0809         m_project.takeSchedule(s);
0810     }
0811     foreach (Schedule *s, m_project.childNode(0)->schedules()) {
0812         foreach (Appointment *a, s->appointments()) {
0813             if (a->resource() && a->resource()->resource()) {
0814                 a->resource()->resource()->takeSchedule(a->resource());
0815             }
0816         }
0817         m_project.childNode(0)->takeSchedule(s);
0818     }
0819     foreach (ScheduleManager *sm,  m_project.scheduleManagers()) {
0820         m_project.takeScheduleManager(sm);
0821         delete sm;
0822     }
0823 }
0824 
0825 //---------------------
0826 ModifyWbsCodeCmd::ModifyWbsCodeCmd(WorkPackage *wp, QString wbsCode,  const KUndo2MagicString &name)
0827     : NamedCommand(name)
0828     , m_wp(wp)
0829     , m_old(wp->wbsCode())
0830     , m_new(wbsCode)
0831 {
0832 }
0833 
0834 void ModifyWbsCodeCmd::execute()
0835 {
0836     m_wp->setWbsCode(m_new);
0837 }
0838 
0839 void ModifyWbsCodeCmd::unexecute()
0840 {
0841     m_wp->setWbsCode(m_old);
0842 }
0843 
0844 }  //KPlatoWork namespace
0845 
0846 QDebug operator<<(QDebug dbg, const KPlatoWork::WorkPackage *wp)
0847 {
0848     if (!wp) {
0849         return dbg.noquote() << "WorkPackage[0x0]";
0850     }
0851     return dbg << *wp;
0852 }
0853 
0854 QDebug operator<<(QDebug dbg, const KPlatoWork::WorkPackage &wp)
0855 {
0856     dbg.noquote() << "WorkPackage[";
0857     dbg << wp.id();
0858     dbg << wp.name();
0859     dbg << ']';
0860     return dbg;
0861 }