diff --git a/src/kptmaindocument.cpp b/src/kptmaindocument.cpp index 3ebf14df..ba534d0c 100644 --- a/src/kptmaindocument.cpp +++ b/src/kptmaindocument.cpp @@ -1,1638 +1,1653 @@ /* This file is part of the KDE project * Copyright (C) 1998, 1999, 2000 Torben Weis * Copyright (C) 2004, 2010, 2012 Dag Andersen * Copyright (C) 2006 Raphael Langerhorst * Copyright (C) 2007 Thorsten Zachmann * Copyright (C) 2019 Dag Andersen * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ // clazy:excludeall=qstring-arg #include "kptmaindocument.h" #include "kptpart.h" #include "kptview.h" #include "kptfactory.h" #include "kptproject.h" #include "kptlocale.h" #include "kptresource.h" #include "kptcontext.h" #include "kptschedulerpluginloader.h" #include "kptschedulerplugin.h" #include "kptbuiltinschedulerplugin.h" #include "kptschedule.h" #include "kptcommand.h" #include "calligraplansettings.h" #include "kpttask.h" #include "KPlatoXmlLoader.h" #include "XmlSaveContext.h" #include "kptpackage.h" #include "kptdebug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_KHOLIDAYS #include #endif namespace KPlato { MainDocument::MainDocument(KoPart *part) : KoDocument(part), m_project(0), m_context(0), m_xmlLoader(), m_loadingTemplate(false), m_loadingSharedResourcesTemplate(false), m_viewlistModified(false), m_checkingForWorkPackages(false), m_loadingSharedProject(false), m_skipSharedProjects(false), + m_isLoading(false), m_isTaskModule(false), m_calculationCommand(nullptr), m_currentCalculationManager(nullptr), m_nextCalculationManager(nullptr), m_taskModulesWatch(nullptr) { Q_ASSERT(part); setAlwaysAllowSaving(true); m_config.setReadWrite(isReadWrite()); loadSchedulerPlugins(); setProject(new Project(m_config)); // after config & plugins are loaded m_project->setId(m_project->uniqueNodeId()); m_project->registerNodeId(m_project); // register myself connect(this, &MainDocument::insertSharedProject, this, &MainDocument::slotInsertSharedProject); } MainDocument::~MainDocument() { qDeleteAll(m_schedulerPlugins); if (m_project) { m_project->deref(); // deletes if last user } qDeleteAll(m_mergedPackages); delete m_context; delete m_calculationCommand; } void MainDocument::initEmpty() { KoDocument::initEmpty(); setProject(new Project(m_config)); } void MainDocument::slotNodeChanged(Node *node, int property) { switch (property) { case Node::TypeProperty: case Node::ResourceRequestProperty: case Node::ConstraintTypeProperty: case Node::StartConstraintProperty: case Node::EndConstraintProperty: case Node::PriorityProperty: case Node::EstimateProperty: case Node::EstimateRiskProperty: setCalculationNeeded(); break; case Node::EstimateOptimisticProperty: case Node::EstimatePessimisticProperty: if (node->estimate()->risktype() != Estimate::Risk_None) { setCalculationNeeded(); } break; default: break; } } void MainDocument::slotScheduleManagerChanged(ScheduleManager *sm, int property) { if (sm->schedulingMode() == ScheduleManager::AutoMode) { switch (property) { case ScheduleManager::DirectionProperty: case ScheduleManager::OverbookProperty: case ScheduleManager::DistributionProperty: case ScheduleManager::SchedulingModeProperty: case ScheduleManager::GranularityProperty: setCalculationNeeded(); break; default: break; } } } void MainDocument::setCalculationNeeded() { for (ScheduleManager *sm : m_project->allScheduleManagers()) { if (sm->isBaselined()) { continue; } if (sm->schedulingMode() == ScheduleManager::AutoMode) { m_nextCalculationManager = sm; break; } } if (!m_currentCalculationManager) { m_currentCalculationManager = m_nextCalculationManager; m_nextCalculationManager = nullptr; QTimer::singleShot(0, this, &MainDocument::slotStartCalculation); } } void MainDocument::slotStartCalculation() { if (m_currentCalculationManager) { m_calculationCommand = new CalculateScheduleCmd(*m_project, m_currentCalculationManager); m_calculationCommand->redo(); } } void MainDocument::slotCalculationFinished(Project *p, ScheduleManager *sm) { if (sm != m_currentCalculationManager) { return; } delete m_calculationCommand; m_calculationCommand = nullptr; m_currentCalculationManager = m_nextCalculationManager; m_nextCalculationManager = nullptr; if (m_currentCalculationManager) { QTimer::singleShot(0, this, &MainDocument::slotStartCalculation); } } void MainDocument::setReadWrite(bool rw) { m_config.setReadWrite(rw); KoDocument::setReadWrite(rw); } void MainDocument::loadSchedulerPlugins() { // Add built-in scheduler addSchedulerPlugin("Built-in", new BuiltinSchedulerPlugin(this)); // Add all real scheduler plugins SchedulerPluginLoader *loader = new SchedulerPluginLoader(this); connect(loader, &SchedulerPluginLoader::pluginLoaded, this, &MainDocument::addSchedulerPlugin); loader->loadAllPlugins(); } void MainDocument::addSchedulerPlugin(const QString &key, SchedulerPlugin *plugin) { debugPlan<setConfig(m_config); } void MainDocument::setProject(Project *project) { if (m_project) { delete m_project; } m_project = project; if (m_project) { connect(m_project, &Project::projectChanged, this, &MainDocument::changed); // m_project->setConfig(config()); m_project->setSchedulerPlugins(m_schedulerPlugins); // For auto scheduling delete m_calculationCommand; m_calculationCommand = nullptr; m_currentCalculationManager = nullptr; m_nextCalculationManager = nullptr; connect(m_project, &Project::nodeAdded, this, &MainDocument::setCalculationNeeded); connect(m_project, &Project::nodeRemoved, this, &MainDocument::setCalculationNeeded); connect(m_project, &Project::relationAdded, this, &MainDocument::setCalculationNeeded); connect(m_project, &Project::relationRemoved, this, &MainDocument::setCalculationNeeded); connect(m_project, &Project::calendarChanged, this, &MainDocument::setCalculationNeeded); connect(m_project, &Project::defaultCalendarChanged, this, &MainDocument::setCalculationNeeded); connect(m_project, &Project::calendarAdded, this, &MainDocument::setCalculationNeeded); connect(m_project, &Project::calendarRemoved, this, &MainDocument::setCalculationNeeded); connect(m_project, &Project::scheduleManagerChanged, this, &MainDocument::slotScheduleManagerChanged); connect(m_project, &Project::nodeChanged, this, &MainDocument::slotNodeChanged); connect(m_project, &Project::sigCalculationFinished, this, &MainDocument::slotCalculationFinished); } m_aboutPage.setProject(project); QString dir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); if (!dir.isEmpty()) { dir += "/taskmodules"; m_project->setLocalTaskModulesPath(QUrl::fromLocalFile(dir)); } setTaskModulesWatch(); connect(project, &Project::taskModulesChanged, this, &MainDocument::setTaskModulesWatch); emit changed(); } void MainDocument::setTaskModulesWatch() { delete m_taskModulesWatch; m_taskModulesWatch = new KDirWatch(this); for (const QUrl &url : m_project->taskModules()) { m_taskModulesWatch->addDir(url.toLocalFile()); } connect(m_taskModulesWatch, &KDirWatch::dirty, this, &MainDocument::taskModuleDirChanged); } void MainDocument::taskModuleDirChanged() { // HACK to trigger update FIXME m_project->setUseLocalTaskModules(m_project->useLocalTaskModules()); } bool MainDocument::loadOdf(KoOdfReadStore &odfStore) { warnPlan<< "OpenDocument not supported, let's try native xml format"; return loadXML(odfStore.contentDoc(), 0); // We have only one format, so try to load that! } bool MainDocument::loadXML(const KoXmlDocument &document, KoStore*) { QPointer updater; if (progressUpdater()) { updater = progressUpdater()->startSubtask(1, "Plan::Part::loadXML"); updater->setProgress(0); m_xmlLoader.setUpdater(updater); } QString value; KoXmlElement plan = document.documentElement(); // Check if this is the right app value = plan.attribute("mime", QString()); if (value.isEmpty()) { errorPlan << "No mime type specified!"; setErrorMessage(i18n("Invalid document. No mimetype specified.")); return false; } if (value == "application/x-vnd.kde.kplato") { if (updater) { updater->setProgress(5); } m_xmlLoader.setMimetype(value); QString message; Project *newProject = new Project(m_config, false); KPlatoXmlLoader loader(m_xmlLoader, newProject); bool ok = loader.load(plan); if (ok) { setProject(newProject); setModified(false); debugPlan<schedules(); // Cleanup after possible bug: // There should *not* be any deleted schedules (or with parent == 0) foreach (Node *n, newProject->nodeDict()) { foreach (Schedule *s, n->schedules()) { if (s->isDeleted()) { // true also if parent == 0 errorPlan<name()<takeSchedule(s); delete s; } } } } else { setErrorMessage(loader.errorMessage()); delete newProject; } if (updater) { updater->setProgress(100); // the rest is only processing, not loading } emit changed(); return ok; } if (value != "application/x-vnd.kde.plan") { errorPlan << "Unknown mime type " << value; setErrorMessage(i18n("Invalid document. Expected mimetype application/x-vnd.kde.plan, got %1", value)); return false; } QString syntaxVersion = plan.attribute("version", PLAN_FILE_SYNTAX_VERSION); m_xmlLoader.setVersion(syntaxVersion); if (syntaxVersion > PLAN_FILE_SYNTAX_VERSION) { KMessageBox::ButtonCode ret = KMessageBox::warningContinueCancel( 0, i18n("This document was created with a newer version of Plan (syntax version: %1)\n" "Opening it in this version of Plan will lose some information.", syntaxVersion), i18n("File-Format Mismatch"), KGuiItem(i18n("Continue"))); if (ret == KMessageBox::Cancel) { setErrorMessage("USER_CANCELED"); return false; } } if (updater) updater->setProgress(5); /* #ifdef KOXML_USE_QDOM int numNodes = plan.childNodes().count(); #else int numNodes = plan.childNodesCount(); #endif */ #if 0 This test does not work any longer. KoXml adds a couple of elements not present in the file!! if (numNodes > 2) { //TODO: Make a proper bitching about this debugPlan <<"*** Error ***"; debugPlan <<" Children count should be maximum 2, but is" << numNodes; return false; } #endif m_xmlLoader.startLoad(); KoXmlNode n = plan.firstChild(); for (; ! n.isNull(); n = n.nextSibling()) { if (! n.isElement()) { continue; } KoXmlElement e = n.toElement(); if (e.tagName() == "project") { Project *newProject = new Project(m_config, true); m_xmlLoader.setProject(newProject); if (newProject->load(e, m_xmlLoader)) { if (newProject->id().isEmpty()) { newProject->setId(newProject->uniqueNodeId()); newProject->registerNodeId(newProject); } // The load went fine. Throw out the old project setProject(newProject); // Cleanup after possible bug: // There should *not* be any deleted schedules (or with parent == 0) foreach (Node *n, newProject->nodeDict()) { foreach (Schedule *s, n->schedules()) { if (s->isDeleted()) { // true also if parent == 0 errorPlan<name()<takeSchedule(s); delete s; } } } } else { delete newProject; m_xmlLoader.addMsg(XMLLoaderObject::Errors, "Loading of project failed"); //TODO add some ui here } } } m_xmlLoader.stopLoad(); if (updater) updater->setProgress(100); // the rest is only processing, not loading setModified(false); emit changed(); return true; } QDomDocument MainDocument::saveXML() { debugPlan; // Save the project XmlSaveContext context(m_project); context.save(); return context.document; } QDomDocument MainDocument::saveWorkPackageXML(const Node *node, long id, Resource *resource) { debugPlanWp<name()); wp.setAttribute("owner-id", resource->id()); } wp.setAttribute("time-tag", QDateTime::currentDateTime().toString(Qt::ISODate)); wp.setAttribute("save-url", m_project->workPackageInfo().retrieveUrl.toString(QUrl::None)); wp.setAttribute("load-url", m_project->workPackageInfo().publishUrl.toString(QUrl::None)); debugPlanWp<<"publish:"<workPackageInfo().publishUrl.toString(QUrl::None); debugPlanWp<<"retrieve:"<workPackageInfo().retrieveUrl.toString(QUrl::None); doc.appendChild(wp); // Save the project m_project->saveWorkPackageXML(doc, node, id); return document; } bool MainDocument::saveWorkPackageToStream(QIODevice *dev, const Node *node, long id, Resource *resource) { QDomDocument doc = saveWorkPackageXML(node, id, resource); // Save to buffer QByteArray s = doc.toByteArray(); // utf8 already dev->open(QIODevice::WriteOnly); int nwritten = dev->write(s.data(), s.size()); if (nwritten != (int)s.size()) { warnPlanWp<<"wrote:"<m_specialOutputFlag == SaveEncrypted) { backend = KoStore::Encrypted; debugPlan <<"Saving using encrypted backend."; }*/ #endif QByteArray mimeType = "application/x-vnd.kde.plan.work"; debugPlanWp <<"MimeType=" << mimeType; KoStore *store = KoStore::createStore(file, KoStore::Write, mimeType, backend); /* if (d->m_specialOutputFlag == SaveEncrypted && !d->m_password.isNull()) { store->setPassword(d->m_password); }*/ if (store->bad()) { setErrorMessage(i18n("Could not create the workpackage file for saving: %1", file)); // more details needed? delete store; return false; } // Tell KoStore not to touch the file names if (! store->open("root")) { setErrorMessage(i18n("Not able to write '%1'. Partition full?", QString("maindoc.xml"))); delete store; return false; } KoStoreDevice dev(store); if (!saveWorkPackageToStream(&dev, node, id, resource) || !store->close()) { errorPlanWp <<"saveToStream failed"; delete store; return false; } node->documents().saveToStore(store); debugPlanWp <<"Saving done of url:" << file; if (!store->finalize()) { delete store; return false; } // Success delete store; return true; } bool MainDocument::saveWorkPackageUrl(const QUrl &_url, const Node *node, long id, Resource *resource) { debugPlanWp<<_url; QApplication::setOverrideCursor(Qt::WaitCursor); emit statusBarMessage(i18n("Saving...")); bool ret = false; ret = saveWorkPackageFormat(_url.path(), node, id, resource); // kzip don't handle file:// QApplication::restoreOverrideCursor(); emit clearStatusBarMessage(); return ret; } bool MainDocument::loadWorkPackage(Project &project, const QUrl &url) { debugPlanWp<bad()) { // d->lastErrorMessage = i18n("Not a valid Calligra file: %1", file); errorPlanWp<<"bad store"<open("root")) { // "old" file format (maindoc.xml) // i18n("File does not have a maindoc.xml: %1", file); errorPlanWp<<"No root"<device(), &errorMsg, &errorLine, &errorColumn); if (! ok) { errorPlanWp << "Parsing error in " << url.url() << "! Aborting!" << endl << " In line: " << errorLine << ", column: " << errorColumn << endl << " Error message: " << errorMsg; //d->lastErrorMessage = i18n("Parsing error in %1 at line %2, column %3\nError message: %4",filename ,errorLine, errorColumn , QCoreApplication::translate("QXml", errorMsg.toUtf8(), 0, QCoreApplication::UnicodeUTF8)); } else { package = loadWorkPackageXML(project, store->device(), doc, url); if (package) { package->url = url; m_workpackages.insert(package->timeTag, package); if (!m_mergedPackages.contains(package->timeTag)) { m_mergedPackages[package->timeTag] = package->project; // register this for next time } } else { ok = false; } } store->close(); //### if (ok && package && package->settings.documents) { ok = extractFiles(store, package); } delete store; if (! ok) { // QApplication::restoreOverrideCursor(); return false; } return true; } Package *MainDocument::loadWorkPackageXML(Project &project, QIODevice *, const KoXmlDocument &document, const QUrl &url) { QString value; bool ok = true; Project *proj = 0; Package *package = 0; KoXmlElement plan = document.documentElement(); // Check if this is the right app value = plan.attribute("mime", QString()); if (value.isEmpty()) { errorPlanWp<timeTag = QDateTime::fromString(loader.timeTag(), Qt::ISODate); } else if (value != "application/x-vnd.kde.plan.work") { errorPlanWp << "Unknown mime type " << value; setErrorMessage(i18n("Invalid document. Expected mimetype application/x-vnd.kde.plan.work, got %1", value)); return 0; } else { if (plan.attribute("editor") != QStringLiteral("PlanWork")) { warnPlanWp<<"Skipped work package file not generated with PlanWork:"< PLANWORK_FILE_SYNTAX_VERSION) { KMessageBox::ButtonCode ret = KMessageBox::warningContinueCancel( 0, i18n("This document was created with a newer version of PlanWork (syntax version: %1)\n" "Opening it in this version of PlanWork will lose some information.", syntaxVersion), i18n("File-Format Mismatch"), KGuiItem(i18n("Continue"))); if (ret == KMessageBox::Cancel) { setErrorMessage("USER_CANCELED"); return 0; } } m_xmlLoader.setVersion(plan.attribute("plan-version", PLAN_FILE_SYNTAX_VERSION)); m_xmlLoader.startLoad(); proj = new Project(); package = new Package(); package->project = proj; KoXmlNode n = plan.firstChild(); for (; ! n.isNull(); n = n.nextSibling()) { if (! n.isElement()) { continue; } KoXmlElement e = n.toElement(); if (e.tagName() == "project") { m_xmlLoader.setProject(proj); ok = proj->load(e, m_xmlLoader); if (! ok) { m_xmlLoader.addMsg(XMLLoaderObject::Errors, "Loading of work package failed"); warnPlanWp<<"Skip workpackage:"<<"Loading project failed"; //TODO add some ui here } } else if (e.tagName() == "workpackage") { package->timeTag = QDateTime::fromString(e.attribute("time-tag"), Qt::ISODate); package->ownerId = e.attribute("owner-id"); package->ownerName = e.attribute("owner"); debugPlan<<"workpackage:"<timeTag<ownerId<ownerName; KoXmlElement elem; forEachElement(elem, e) { if (elem.tagName() != "settings") { continue; } package->settings.usedEffort = (bool)elem.attribute("used-effort").toInt(); package->settings.progress = (bool)elem.attribute("progress").toInt(); package->settings.documents = (bool)elem.attribute("documents").toInt(); } } } if (proj->numChildren() > 0) { package->task = static_cast(proj->childNode(0)); package->toTask = qobject_cast(m_project->findNode(package->task->id())); WorkPackage &wp = package->task->workPackage(); if (wp.ownerId().isEmpty()) { wp.setOwnerId(package->ownerId); wp.setOwnerName(package->ownerName); } if (wp.ownerId() != package->ownerId) { warnPlanWp<<"Current owner:"<ownerName; } debugPlanWp<<"Task set:"<task->name(); } m_xmlLoader.stopLoad(); } if (ok && proj->id() != project.id()) { debugPlanWp<<"Skip workpackage:"<<"Not the correct project"; ok = false; } if (ok && (package->task == nullptr)) { warnPlanWp<<"Skip workpackage:"<<"No task in workpackage file"; ok = false; } if (ok && (package->toTask == nullptr)) { warnPlanWp<<"Skip workpackage:"<<"Cannot find task:"<task->id()<task->name(); ok = false; } if (ok && !package->timeTag.isValid()) { warnPlanWp<<"Work package is not time tagged:"<task->name()<url; ok = false; } if (ok && m_mergedPackages.contains(package->timeTag)) { debugPlanWp<<"Skip workpackage:"<<"already merged:"<task->name()<url; ok = false; // already merged } if (!ok) { delete proj; delete package; return nullptr; } return package; } bool MainDocument::extractFiles(KoStore *store, Package *package) { if (package->task == 0) { errorPlan<<"No task!"; return false; } foreach (Document *doc, package->task->documents().documents()) { if (! doc->isValid() || doc->type() != Document::Type_Product || doc->sendAs() != Document::SendAs_Copy) { continue; } if (! extractFile(store, package, doc)) { return false; } } return true; } bool MainDocument::extractFile(KoStore *store, Package *package, const Document *doc) { QTemporaryFile tmpfile; if (! tmpfile.open()) { errorPlan<<"Failed to open temporary file"; return false; } if (! store->extractFile(doc->url().fileName(), tmpfile.fileName())) { errorPlan<<"Failed to extract file:"<url().fileName()<<"to:"<documents.insert(tmpfile.fileName(), doc->url()); tmpfile.setAutoRemove(false); debugPlan<<"extracted:"<url().fileName()<<"->"<(sender()); if (m_project && m_project->workPackageInfo().checkForWorkPackages) { checkForWorkPackages(true); } if (timer && timer->interval() != 10000) { timer->stop(); timer->setInterval(10000); timer->start(); } } void MainDocument::checkForWorkPackages(bool keep) { if (m_checkingForWorkPackages || m_project == nullptr || m_project->numChildren() == 0 || m_project->workPackageInfo().retrieveUrl.isEmpty()) { return; } if (! keep) { qDeleteAll(m_mergedPackages); m_mergedPackages.clear(); } QDir dir(m_project->workPackageInfo().retrieveUrl.path(), "*.planwork"); m_infoList = dir.entryInfoList(QDir::Files | QDir::Readable, QDir::Time); checkForWorkPackage(); return; } void MainDocument::checkForWorkPackage() { if (! m_infoList.isEmpty()) { m_checkingForWorkPackages = true; QUrl url = QUrl::fromLocalFile(m_infoList.takeLast().absoluteFilePath()); if (!m_skipUrls.contains(url) && !loadWorkPackage(*m_project, url)) { m_skipUrls << url; debugPlanWp<<"skip url:"<toTask<url; if (m_workpackages.value(package->timeTag) == package) { m_workpackages.remove(package->timeTag); } QFile file(package->url.path()); if (! file.exists()) { warnPlanWp<<"File does not exist:"<toTask<url; return; } Project::WorkPackageInfo wpi = m_project->workPackageInfo(); debugPlanWp<<"retrieve:"<url.adjusted(QUrl::RemoveFilename); if (wpi.archiveAfterRetrieval && wpi.archiveUrl.isValid()) { QDir dir(wpi.archiveUrl.path()); if (! dir.exists()) { if (! dir.mkpath(dir.path())) { //TODO message warnPlanWp<<"Failed to create archive directory:"<generateUniqueIds(); m_project->setConstraintStartTime(QDateTime(QDate::currentDate(), QTime(0, 0, 0), Qt::LocalTime)); m_project->setConstraintEndTime(m_project->constraintStartTime().addYears(2)); m_project->locale()->setCurrencyLocale(QLocale::AnyLanguage, QLocale::AnyCountry); m_project->locale()->setCurrencySymbol(QString()); } else if (isImporting()) { // NOTE: I don't think this is a good idea. // Let the filter generate ids for non-plan files. // If the user wants to create a new project from an old one, // he should use Tools -> Insert Project File //m_project->generateUniqueNodeIds(); } if (m_loadingSharedResourcesTemplate && m_project->calendarCount() > 0) { Calendar *c = m_project->calendarAt(0); c->setTimeZone(QTimeZone::systemTimeZone()); } if (m_project->useSharedResources() && !m_project->sharedResourcesFile().isEmpty() && !m_skipSharedProjects) { QUrl url = QUrl::fromLocalFile(m_project->sharedResourcesFile()); if (url.isValid()) { insertResourcesFile(url, m_project->loadProjectsAtStartup() ? m_project->sharedProjectsUrl() : QUrl()); } } if (store == 0) { // can happen if loading a template debugPlan<<"No store"; return true; // continue anyway } delete m_context; m_context = new Context(); KoXmlDocument doc; if (loadAndParse(store, "context.xml", doc)) { store->close(); m_context->load(doc); } else warnPlan<<"No context"; return true; } // TODO: // Due to splitting of KoDocument into a document and a part, // we simulate the old behaviour by registering all views in the document. // Find a better solution! void MainDocument::registerView(View* view) { if (view && ! m_views.contains(view)) { m_views << QPointer(view); } } bool MainDocument::completeSaving(KoStore *store) { if (m_context && m_views.isEmpty()) { if (store->open("context.xml")) { // When e.g. saving as a template there are no views, // so we cannot get info from them. // Just use the context info we have in this case. KoStoreDevice dev(store); QByteArray s = m_context->document().toByteArray(); (void)dev.write(s.data(), s.size()); (void)store->close(); return true; } return false; } foreach (View *view, m_views) { if (view) { if (store->open("context.xml")) { if (m_context == 0) m_context = new Context(); QDomDocument doc = m_context->save(view); KoStoreDevice dev(store); QByteArray s = doc.toByteArray(); // this is already Utf8! (void)dev.write(s.data(), s.size()); (void)store->close(); m_viewlistModified = false; emit viewlistModified(false); } break; } } return true; } bool MainDocument::loadAndParse(KoStore *store, const QString &filename, KoXmlDocument &doc) { //debugPlan << "oldLoadAndParse: Trying to open " << filename; if (!store->open(filename)) { warnPlan << "Entry " << filename << " not found!"; // d->lastErrorMessage = i18n("Could not find %1",filename); return false; } // Error variables for QDomDocument::setContent QString errorMsg; int errorLine, errorColumn; bool ok = doc.setContent(store->device(), &errorMsg, &errorLine, &errorColumn); if (!ok) { errorPlan << "Parsing error in " << filename << "! Aborting!" << endl << " In line: " << errorLine << ", column: " << errorColumn << endl << " Error message: " << errorMsg; /* d->lastErrorMessage = i18n("Parsing error in %1 at line %2, column %3\nError message: %4" ,filename ,errorLine, errorColumn , QCoreApplication::translate("QXml", errorMsg.toUtf8(), 0, QCoreApplication::UnicodeUTF8));*/ store->close(); return false; } debugPlan << "File " << filename << " loaded and parsed"; return true; } void MainDocument::insertFile(const QUrl &url, Node *parent, Node *after) { Part *part = new Part(this); MainDocument *doc = new MainDocument(part); part->setDocument(doc); doc->disconnect(); // doc shall not handle feedback from openUrl() doc->setAutoSave(0); //disable doc->m_insertFileInfo.url = url; doc->m_insertFileInfo.parent = parent; doc->m_insertFileInfo.after = after; connect(doc, &KoDocument::completed, this, &MainDocument::insertFileCompleted); connect(doc, &KoDocument::canceled, this, &MainDocument::insertFileCancelled); + m_isLoading = true; doc->openUrl(url); } void MainDocument::insertFileCompleted() { debugPlan<(sender()); if (doc) { Project &p = doc->getProject(); insertProject(p, doc->m_insertFileInfo.parent, doc->m_insertFileInfo.after); doc->documentPart()->deleteLater(); // also deletes document } else { KMessageBox::error(0, i18n("Internal error, failed to insert file.")); } + m_isLoading = false; } void MainDocument::insertResourcesFile(const QUrl &url, const QUrl &projects) { insertSharedProjects(projects); // prepare for insertion after shared resources m_sharedProjectsFiles.removeAll(url); // resource file is not a project Part *part = new Part(this); MainDocument *doc = new MainDocument(part); doc->m_skipSharedProjects = true; // should not have shared projects, but... part->setDocument(doc); doc->disconnect(); // doc shall not handle feedback from openUrl() doc->setAutoSave(0); //disable doc->setCheckAutoSaveFile(false); connect(doc, &KoDocument::completed, this, &MainDocument::insertResourcesFileCompleted); connect(doc, &KoDocument::canceled, this, &MainDocument::insertFileCancelled); + m_isLoading = true; doc->openUrl(url); } void MainDocument::insertResourcesFileCompleted() { debugPlanShared<(sender()); if (doc) { Project &p = doc->getProject(); mergeResources(p); m_project->setSharedResourcesLoaded(true); doc->documentPart()->deleteLater(); // also deletes document slotInsertSharedProject(); // insert shared bookings } else { KMessageBox::error(0, i18n("Internal error, failed to insert file.")); } + m_isLoading = false; } void MainDocument::insertFileCancelled(const QString &error) { debugPlan<(sender()); if (doc) { doc->documentPart()->deleteLater(); // also deletes document } + m_isLoading = false; } void MainDocument::clearResourceAssignments() { foreach (Resource *r, m_project->resourceList()) { r->clearExternalAppointments(); } } void MainDocument::loadResourceAssignments(QUrl url) { insertSharedProjects(url); slotInsertSharedProject(); } void MainDocument::insertSharedProjects(const QList &urls) { clearResourceAssignments(); m_sharedProjectsFiles = urls; slotInsertSharedProject(); } void MainDocument::insertSharedProjects(const QUrl &url) { m_sharedProjectsFiles.clear(); QFileInfo fi(url.path()); if (!fi.exists()) { return; } if (fi.isFile()) { m_sharedProjectsFiles = QList() << url; debugPlan<<"Get all projects in file:"<m_skipSharedProjects = true; // never load recursively part->setDocument(doc); doc->disconnect(); // doc shall not handle feedback from openUrl() doc->setAutoSave(0); //disable doc->setCheckAutoSaveFile(false); doc->m_loadingSharedProject = true; connect(doc, &KoDocument::completed, this, &MainDocument::insertSharedProjectCompleted); connect(doc, &KoDocument::canceled, this, &MainDocument::insertSharedProjectCancelled); + m_isLoading = true; doc->openUrl(m_sharedProjectsFiles.takeFirst()); } void MainDocument::insertSharedProjectCompleted() { debugPlanShared<(sender()); if (doc) { Project &p = doc->getProject(); debugPlanShared<id()<<"Loaded project:"<id() && p.isScheduled(ANYSCHEDULED)) { // FIXME: improve! // find a suitable schedule ScheduleManager *sm = 0; foreach(ScheduleManager *m, p.allScheduleManagers()) { if (m->isBaselined()) { sm = m; break; } if (m->isScheduled()) { sm = m; // take the last one, more likely to be subschedule } } if (sm) { foreach(Resource *r, p.resourceList()) { Resource *res = m_project->resource(r->id()); if (res && res->isShared()) { Appointment *app = new Appointment(); app->setAuxcilliaryInfo(p.name()); foreach(const Appointment *a, r->appointments(sm->scheduleId())) { *app += *a; } if (app->isEmpty()) { delete app; } else { res->addExternalAppointment(p.id(), app); debugPlanShared<name()<<"added:"<auxcilliaryInfo()<documentPart()->deleteLater(); // also deletes document + m_isLoading = false; emit insertSharedProject(); // do next file } else { KMessageBox::error(0, i18n("Internal error, failed to insert file.")); + m_isLoading = false; } } void MainDocument::insertSharedProjectCancelled(const QString &error) { debugPlanShared<(sender()); if (doc) { doc->documentPart()->deleteLater(); // also deletes document } + m_isLoading = false; } bool MainDocument::insertProject(Project &project, Node *parent, Node *after) { debugPlan<<&project; // make sure node ids in new project is unique also in old project QList existingIds = m_project->nodeDict().keys(); foreach (Node *n, project.allNodes()) { QString oldid = n->id(); n->setId(project.uniqueNodeId(existingIds)); project.removeId(oldid); // remove old id project.registerNodeId(n); // register new id } MacroCommand *m = new InsertProjectCmd(project, parent==0?m_project:parent, after, kundo2_i18n("Insert project")); if (m->isEmpty()) { delete m; } else { addCommand(m); } return true; } // check if calendar 'c' has children that will not be removed (normally 'Local' calendars) bool canRemoveCalendar(const Calendar *c, const QList &lst) { for (Calendar *cc : c->calendars()) { if (!lst.contains(cc)) { return false; } if (!canRemoveCalendar(cc, lst)) { return false; } } return true; } // sort parent calendars before children QList sortedRemoveCalendars(Project &shared, const QList &lst) { QList result; for (Calendar *c : lst) { if (c->isShared() && !shared.calendar(c->id())) { result << c; } result += sortedRemoveCalendars(shared, c->calendars()); } return result; } bool MainDocument::mergeResources(Project &project) { debugPlanShared<<&project; // Just in case, remove stuff not related to resources foreach(Node *n, project.childNodeIterator()) { debugPlanShared<<"Project not empty, delete node:"<name(); NodeDeleteCmd cmd(n); cmd.execute(); } foreach(ScheduleManager *m, project.scheduleManagers()) { debugPlanShared<<"Project not empty, delete schedule:"<name(); DeleteScheduleManagerCmd cmd(project, m); cmd.execute(); } foreach(Account *a, project.accounts().accountList()) { debugPlanShared<<"Project not empty, delete account:"<name(); RemoveAccountCmd cmd(project, a); cmd.execute(); } // Mark all resources / groups as shared foreach(ResourceGroup *g, project.resourceGroups()) { g->setShared(true); } foreach(Resource *r, project.resourceList()) { r->setShared(true); } // Mark all calendars shared foreach(Calendar *c, project.allCalendars()) { c->setShared(true); } // check if any shared stuff has been removed QList removedGroups; QList removedResources; QList removedCalendars; QStringList removed; foreach(ResourceGroup *g, m_project->resourceGroups()) { if (g->isShared() && !project.findResourceGroup(g->id())) { removedGroups << g; removed << i18n("Group: %1", g->name()); } } foreach(Resource *r, m_project->resourceList()) { if (r->isShared() && !project.findResource(r->id())) { removedResources << r; removed << i18n("Resource: %1", r->name()); } } removedCalendars = sortedRemoveCalendars(project, m_project->calendars()); for (Calendar *c : qAsConst(removedCalendars)) { removed << i18n("Calendar: %1", c->name()); } if (!removed.isEmpty()) { KMessageBox::ButtonCode result = KMessageBox::warningYesNoCancelList( 0, i18n("Shared resources has been removed from the shared resources file." "\nSelect how they shall be treated in this project."), removed, xi18nc("@title:window", "Shared resources"), KStandardGuiItem::remove(), KGuiItem(i18n("Convert")), KGuiItem(i18n("Keep")) ); switch (result) { case KMessageBox::Yes: // Remove for (Resource *r : qAsConst(removedResources)) { RemoveResourceCmd cmd(r->parentGroup(), r); cmd.redo(); } for (ResourceGroup *g : qAsConst(removedGroups)) { if (g->resources().isEmpty()) { RemoveResourceGroupCmd cmd(m_project, g); cmd.redo(); } else { // we may have put local resource(s) in this group // so we need to keep it g->setShared(false); m_project->removeResourceGroupId(g->id()); g->setId(m_project->uniqueResourceGroupId()); m_project->insertResourceGroupId(g->id(), g); } } for (Calendar *c : qAsConst(removedCalendars)) { CalendarRemoveCmd cmd(m_project, c); cmd.redo(); } break; case KMessageBox::No: // Convert for (Resource *r : qAsConst(removedResources)) { r->setShared(false); m_project->removeResourceId(r->id()); r->setId(m_project->uniqueResourceId()); m_project->insertResourceId(r->id(), r); } for (ResourceGroup *g : qAsConst(removedGroups)) { g->setShared(false); m_project->removeResourceGroupId(g->id()); g->setId(m_project->uniqueResourceGroupId()); m_project->insertResourceGroupId(g->id(), g); } for (Calendar *c : qAsConst(removedCalendars)) { c->setShared(false); m_project->removeCalendarId(c->id()); c->setId(m_project->uniqueCalendarId()); m_project->insertCalendarId(c->id(), c); } break; case KMessageBox::Cancel: // Keep break; default: break; } } // update values of already existing objects QStringList l1; foreach(ResourceGroup *g, project.resourceGroups()) { l1 << g->id(); } QStringList l2; foreach(ResourceGroup *g, m_project->resourceGroups()) { l2 << g->id(); } debugPlanShared< removegroups; foreach(ResourceGroup *g, project.resourceGroups()) { ResourceGroup *group = m_project->findResourceGroup(g->id()); if (group) { if (!group->isShared()) { // User has probably created shared resources from this project, // so the resources exists but are local ones. // Convert to shared and do not load the group from shared. removegroups << g; group->setShared(true); debugPlanShared<<"Set group to shared:"<id(); } group->setName(g->name()); group->setType(g->type()); debugPlanShared<<"Updated group:"<id(); } } QList removeresources; foreach(Resource *r, project.resourceList()) { Resource *resource = m_project->findResource(r->id()); if (resource) { if (!resource->isShared()) { // User has probably created shared resources from this project, // so the resources exists but are local ones. // Convert to shared and do not load the resource from shared. removeresources << r; resource->setShared(true); debugPlanShared<<"Set resource to shared:"<id(); } resource->setName(r->name()); resource->setInitials(r->initials()); resource->setEmail(r->email()); resource->setType(r->type()); resource->setAutoAllocate(r->autoAllocate()); resource->setAvailableFrom(r->availableFrom()); resource->setAvailableUntil(r->availableUntil()); resource->setUnits(r->units()); resource->setNormalRate(r->normalRate()); resource->setOvertimeRate(r->overtimeRate()); QString id = r->calendar(true) ? r->calendar(true)->id() : QString(); resource->setCalendar(m_project->findCalendar(id)); id = r->account() ? r->account()->name() : QString(); resource->setAccount(m_project->accounts().findAccount(id)); resource->setRequiredIds(r->requiredIds()); resource->setTeamMemberIds(r->teamMemberIds()); debugPlanShared<<"Updated resource:"<id(); } } QList removecalendars; foreach(Calendar *c, project.allCalendars()) { Calendar *calendar = m_project->findCalendar(c->id()); if (calendar) { if (!calendar->isShared()) { // User has probably created shared resources from this project, // so the calendar exists but are local ones. // Convert to shared and do not load the resource from shared. removecalendars << c; calendar->setShared(true); debugPlanShared<<"Set calendar to shared:"<id(); } *calendar = *c; debugPlanShared<<"Updated calendar:"<id(); } } debugPlanShared<<"Remove:"<childCount() == 0) { removecalendars.removeAt(i); debugPlanShared<<"Delete calendar:"<id(); CalendarRemoveCmd cmd(&project, c); cmd.execute(); } } } for (Resource *r : qAsConst(removeresources)) { debugPlanShared<<"Delete resource:"<id(); RemoveResourceCmd cmd(r->parentGroup(), r); cmd.execute(); } for (ResourceGroup *g : qAsConst(removegroups)) { debugPlanShared<<"Delete group:"<id(); RemoveResourceGroupCmd cmd(&project, g); cmd.execute(); } // insert new objects Q_ASSERT(project.childNodeIterator().isEmpty()); InsertProjectCmd cmd(project, m_project, 0); cmd.execute(); return true; } void MainDocument::insertViewListItem(View */*view*/, const ViewListItem *item, const ViewListItem *parent, int index) { // FIXME callers should take care that they now get a signal even if originating from themselves emit viewListItemAdded(item, parent, index); setModified(true); m_viewlistModified = true; } void MainDocument::removeViewListItem(View */*view*/, const ViewListItem *item) { // FIXME callers should take care that they now get a signal even if originating from themselves emit viewListItemRemoved(item); setModified(true); m_viewlistModified = true; } +bool MainDocument::isLoading() const +{ + return m_isLoading || KoDocument::isLoading(); +} + void MainDocument::setModified(bool mod) { debugPlan<name().isEmpty()) { setUrl(QUrl(m_project->name() + ".plan")); } if (m_project->scheduleManagers().isEmpty()) { ScheduleManager *sm = m_project->createScheduleManager(); sm->setAllowOverbooking(false); sm->setSchedulingMode(ScheduleManager::AutoMode); } Calendar *week = nullptr; if (KPlatoSettings::generateWeek()) { bool always = KPlatoSettings::generateWeekChoice() == KPlatoSettings::EnumGenerateWeekChoice::Always; bool ifnone = KPlatoSettings::generateWeekChoice() == KPlatoSettings::EnumGenerateWeekChoice::NoneExists; if (always || (ifnone && m_project->calendarCount() == 0)) { // create a calendar week = new Calendar(i18nc("Base calendar name", "Base")); m_project->addCalendar(week); CalendarDay vd(CalendarDay::NonWorking); for (int i = Qt::Monday; i <= Qt::Sunday; ++i) { if (m_config.isWorkingday(i)) { CalendarDay wd(CalendarDay::Working); TimeInterval ti(m_config.dayStartTime(i), m_config.dayLength(i)); wd.addInterval(ti); week->setWeekday(i, wd); } else { week->setWeekday(i, vd); } } } } #ifdef HAVE_KHOLIDAYS if (KPlatoSettings::generateHolidays()) { bool inweek = week != 0 && KPlatoSettings::generateHolidaysChoice() == KPlatoSettings::EnumGenerateHolidaysChoice::InWeekCalendar; bool subcalendar = week != 0 && KPlatoSettings::generateHolidaysChoice() == KPlatoSettings::EnumGenerateHolidaysChoice::AsSubCalendar; bool separate = week == 0 || KPlatoSettings::generateHolidaysChoice() == KPlatoSettings::EnumGenerateHolidaysChoice::AsSeparateCalendar; Calendar *holiday = nullptr; if (inweek) { holiday = week; week->setDefault(true); debugPlan<<"in week"; } else if (subcalendar) { holiday = new Calendar(i18n("Holidays")); m_project->addCalendar(holiday, week); holiday->setDefault(true); debugPlan<<"subcalendar"; } else if (separate) { holiday = new Calendar(i18n("Holidays")); m_project->addCalendar(holiday); holiday->setDefault(true); debugPlan<<"separate"; } else { Q_ASSERT(false); // something wrong } debugPlan<setHolidayRegion(KPlatoSettings::region()); } #else week->setDefault(true); #endif } // creates a "new" project from current project (new ids etc) void MainDocument::createNewProject() { setEmpty(); clearUndoHistory(); setModified(false); resetURL(); KoDocumentInfo *info = documentInfo(); info->resetMetaData(); info->setProperty("title", ""); setTitleModified(); m_project->generateUniqueNodeIds(); Duration dur = m_project->constraintEndTime() - m_project->constraintStartTime(); m_project->setConstraintStartTime(QDateTime(QDate::currentDate(), QTime(0, 0, 0), Qt::LocalTime)); m_project->setConstraintEndTime(m_project->constraintStartTime() + dur); while (m_project->numScheduleManagers() > 0) { foreach (ScheduleManager *sm, m_project->allScheduleManagers()) { if (sm->childCount() > 0) { continue; } if (sm->expected()) { sm->expected()->setDeleted(true); sm->setExpected(0); } m_project->takeScheduleManager(sm); delete sm; } } foreach (Schedule *s, m_project->schedules()) { m_project->takeSchedule(s); delete s; } foreach (Node *n, m_project->allNodes()) { foreach (Schedule *s, n->schedules()) { n->takeSchedule(s); delete s; } } foreach (Resource *r, m_project->resourceList()) { foreach (Schedule *s, r->schedules()) { r->takeSchedule(s); delete s; } } } void MainDocument::setIsTaskModule(bool value) { m_isTaskModule = value; } bool MainDocument::isTaskModule() const { return m_isTaskModule; } } //KPlato namespace diff --git a/src/kptmaindocument.h b/src/kptmaindocument.h index 41aae415..8051c7f5 100644 --- a/src/kptmaindocument.h +++ b/src/kptmaindocument.h @@ -1,277 +1,284 @@ /* This file is part of the KDE project * Copyright (C) 1998, 1999, 2000 Torben Weis * Copyright (C) 2004 - 2010 Dag Andersen * Copyright (C) 2006 Raphael Langerhorst * Copyright (C) 2007 Thorsten Zachmann * Copyright (C) 2019 Dag Andersen * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KPTMAINDOCUMENT_H #define KPTMAINDOCUMENT_H #include "plan_export.h" #include "kptpackage.h" #include "kpttask.h" #include "kptconfig.h" #include "kptwbsdefinition.h" #include "kptxmlloaderobject.h" #include "about/aboutpage.h" #include "KoDocument.h" #include #include #define PLAN_MIME_TYPE "application/x-vnd.kde.plan" class KDirWatch; /// The main namespace. namespace KPlato { class DocumentChild; class Project; class Context; class SchedulerPlugin; class ViewListItem; class View; class Package; class PLAN_EXPORT MainDocument : public KoDocument { Q_OBJECT public: explicit MainDocument(KoPart *part); ~MainDocument() override; void initEmpty() override; /// reimplemented from KoDocument QByteArray nativeFormatMimeType() const override { return PLAN_MIME_TYPE; } /// reimplemented from KoDocument QByteArray nativeOasisMimeType() const override { return ""; } /// reimplemented from KoDocument QStringList extraNativeMimeTypes() const override { return QStringList() << PLAN_MIME_TYPE; } void setReadWrite(bool rw) override; void configChanged(); void paintContent(QPainter& painter, const QRect& rect) override; void setProject(Project *project); Project *project() const override { return m_project; } Project &getProject() { return *m_project; } const Project &getProject() const { return * m_project; } QString projectName() const override { return m_project->name(); } /** * Return the set of SupportedSpecialFormats that the kplato wants to * offer in the "Save" file dialog. * Note: SaveEncrypted is not supported. */ int supportedSpecialFormats() const override { return SaveAsDirectoryStore; } // The load and save functions. Look in the file kplato.dtd for info bool loadXML(const KoXmlDocument &document, KoStore *store) override; QDomDocument saveXML() override; /// Save a workpackage file containing @p node with schedule identity @p id, owned by @p resource QDomDocument saveWorkPackageXML(const Node *node, long id, Resource *resource = 0); bool saveOdf(SavingContext &/*documentContext */) override { return false; } bool loadOdf(KoOdfReadStore & odfStore) override; Config &config() { return m_config; } Context *context() const { return m_context; } WBSDefinition &wbsDefinition() { return m_project->wbsDefinition(); } const XMLLoaderObject &xmlLoader() const { return m_xmlLoader; } DocumentChild *createChild(KoDocument *doc, const QRect &geometry = QRect()); bool saveWorkPackageToStream(QIODevice * dev, const Node *node, long id, Resource *resource = 0); bool saveWorkPackageFormat(const QString &file, const Node *node, long id, Resource *resource = 0); bool saveWorkPackageUrl(const QUrl & _url, const Node *node, long id, Resource *resource = 0 ); /// Load the workpackage from @p url into @p project. Return true if successful, else false. bool loadWorkPackage(Project &project, const QUrl &url); Package *loadWorkPackageXML(Project& project, QIODevice*, const KoXmlDocument& document, const QUrl& url); QMap workPackages() const { return m_workpackages; } void clearWorkPackages() { qDeleteAll(m_workpackages); m_workpackages.clear(); m_checkingForWorkPackages = false; } void insertFile(const QUrl &url, Node *parent, Node *after = 0); bool insertProject(Project &project, Node *parent, Node *after); bool mergeResources(Project &project); KPlatoAboutPage &aboutPage() { return m_aboutPage; } bool extractFiles(KoStore *store, Package *package); bool extractFile(KoStore *store, Package *package, const Document *doc); void registerView(View *view); /// Create a new project from this project /// Generates new project id and task ids /// Keeps resource- and calendar ids void createNewProject(); bool isTaskModule() const; + /** + * Returns true during loading (openUrl can be asynchronous) + */ + bool isLoading() const override; + using KoDocument::setModified; public Q_SLOTS: void setModified(bool mod) override; /// Inserts an item into all other views than @p view void insertViewListItem(KPlato::View *view, const KPlato::ViewListItem *item, const KPlato::ViewListItem *parent, int index); /// Removes the view list item from all other views than @p view void removeViewListItem(KPlato::View *view, const KPlato::ViewListItem *item); /// View selector has been modified void slotViewlistModified(); /// Check for workpackages /// If @p keep is true, packages that has been refused will not be checked for again void checkForWorkPackages(bool keep); /// Remove @p package void terminateWorkPackage(const KPlato::Package *package); void setLoadingTemplate(bool); void setLoadingSharedResourcesTemplate(bool); void insertResourcesFile(const QUrl &url, const QUrl &projects = QUrl()); void slotProjectCreated(); /// Prepare for insertion of resource assignments of shared resources from the project(s) in @p urls void insertSharedProjects(const QList &urls); /// Prepare for insertion of resource assignments of shared resources from the project(s) in @p url void insertSharedProjects(const QUrl &url); /// Clear resource assignments of shared resources void clearResourceAssignments(); /// Load resource assignments of shared resources from the project(s) in @p url void loadResourceAssignments(QUrl url); void setIsTaskModule(bool value); void autoCheckForWorkPackages(); Q_SIGNALS: void changed(); void workPackageLoaded(); void viewlistModified(bool); void viewListItemAdded(const KPlato::ViewListItem *item, const KPlato::ViewListItem *parent, int index); void viewListItemRemoved(const KPlato::ViewListItem *item); void insertSharedProject(); protected: /// Load kplato specific files bool completeLoading(KoStore* store) override; /// Save kplato specific files bool completeSaving(KoStore* store) override; // used by insert file struct InsertFileInfo { QUrl url; Node *parent; Node *after; } m_insertFileInfo; protected Q_SLOTS: void slotViewDestroyed(); void addSchedulerPlugin(const QString&, KPlato::SchedulerPlugin *plugin); void checkForWorkPackage(); void insertFileCompleted(); void insertResourcesFileCompleted(); void insertFileCancelled(const QString&); void slotInsertSharedProject(); void insertSharedProjectCompleted(); void insertSharedProjectCancelled(const QString&); void slotNodeChanged(KPlato::Node*, int); void slotScheduleManagerChanged(KPlato::ScheduleManager *sm, int property); void setCalculationNeeded(); void slotCalculationFinished(KPlato::Project *project, KPlato::ScheduleManager *sm); void slotStartCalculation(); void setTaskModulesWatch(); void taskModuleDirChanged(); private: bool loadAndParse(KoStore* store, const QString& filename, KoXmlDocument& doc); void loadSchedulerPlugins(); private: Project *m_project; QWidget* m_parentWidget; Config m_config; Context *m_context; XMLLoaderObject m_xmlLoader; bool m_loadingTemplate; bool m_loadingSharedResourcesTemplate; QMap m_schedulerPlugins; QMap m_workpackages; QFileInfoList m_infoList; QList m_skipUrls; QMap m_mergedPackages; KPlatoAboutPage m_aboutPage; QDomDocument m_reports; bool m_viewlistModified; bool m_checkingForWorkPackages; QList > m_views; bool m_loadingSharedProject; QList m_sharedProjectsFiles; bool m_skipSharedProjects; + bool m_isLoading; + bool m_isTaskModule; KUndo2Command* m_calculationCommand; ScheduleManager* m_currentCalculationManager; ScheduleManager* m_nextCalculationManager; KDirWatch *m_taskModulesWatch; }; } //KPlato namespace #endif diff --git a/src/libs/main/KoDocument.h b/src/libs/main/KoDocument.h index a5c66396..837a56de 100644 --- a/src/libs/main/KoDocument.h +++ b/src/libs/main/KoDocument.h @@ -1,821 +1,821 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2000-2005 David Faure Copyright (C) 2007 Thorsten Zachmann Copyright (C) 2010 Boudewijn Rempt This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KODOCUMENT_H #define KODOCUMENT_H #include #include #include "komain_export.h" #include #include #include class KUndo2Command; class KoPart; class KoStore; class KoDocumentInfo; //class KoDocumentRdf; //class KoDocumentRdfBase; class KoProgressUpdater; class KoProgressProxy; class KoDocumentInfoDlg; class KoUnit; //class KoGridData; //class KoGuidesData; class KoXmlWriter; class QDomDocument; // MSVC seems to need to know the declaration of the classes // we pass references of in, when used by external modules // e.g. // when building chartshapecore.lib, the forward-declaration // approach lead to unresolved externals warnings when it used // the pagelayout functions. // Also when building calligra_shape_formular.dll - FormulaDocument // referenced the same two pagelayout functions incorrectly. #if defined(_WIN32) || defined(_WIN64) #include #else struct KoPageLayout; #endif namespace KPlato { class Project; } class KoVersionInfo { public: QDateTime date; QString saved_by; QString comment; QString title; QByteArray data; //the content of the compressed version }; /** * The %Calligra document class * * This class provides some functionality each %Calligra document should have. * * @short The %Calligra document class */ class KOMAIN_EXPORT KoDocument : public QObject, public KoDocumentBase { Q_OBJECT Q_PROPERTY(bool backupFile READ backupFile WRITE setBackupFile NOTIFY backupFileChanged) Q_PROPERTY(int pageCount READ pageCount) // clazy:exclude=qproperty-without-notify public: /** * Constructor. * * @param parent The KoPart that owns the document. XXX: should be removed! * @param undoStack accepts the stack for the document. You can create any type of stack if you need. * The stack objects will become owned by the document. This is used by Krita's KisDoc2. The default value for this * parameter is a usual Qt's stack. */ explicit KoDocument(KoPart *parent, KUndo2Stack *undoStack = new KUndo2Stack()); virtual KPlato::Project *project() const { return nullptr; } virtual QString projectName() const { return QString(); } /** * Destructor. * * The destructor does not delete any attached KoView objects and it does not * delete the attached widget as returned by widget(). */ ~KoDocument() override; /// XXX: Temporary! KoPart *documentPart() const; /** * Reimplemented from KoParts::ReadWritePart for internal reasons * (for the autosave functionality) */ virtual bool openUrl(const QUrl &url); /** * Opens the document given by @p url, without storing the URL * in the KoDocument. * Call this instead of openUrl() to implement KoMainWindow's * File --> Import feature. * * @note This will call openUrl(). To differentiate this from an ordinary * Open operation (in any reimplementation of openUrl() or openFile()) * call isImporting(). */ bool importDocument(const QUrl &url); /** * Saves the document as @p url without changing the state of the * KoDocument (URL, modified flag etc.). Call this instead of * KoParts::ReadWritePart::saveAs() to implement KoMainWindow's * File --> Export feature. * * @note This will call KoDocument::saveAs(). To differentiate this * from an ordinary Save operation (in any reimplementation of * saveFile()) call isExporting(). */ bool exportDocument(const QUrl &url); /** * @brief Sets whether the document can be edited or is read only. * * This recursively applied to all child documents and * KoView::updateReadWrite is called for every attached * view. */ virtual void setReadWrite(bool readwrite = true); /** * To be preferred when a document exists. It is fast when calling * it multiple times since it caches the result that readNativeFormatMimeType() * delivers. * This comes from the X-KDE-NativeMimeType key in the .desktop file. */ virtual QByteArray nativeFormatMimeType() const = 0; /** * Returns the OASIS OpenDocument mimetype of the document, if supported * This comes from the X-KDE-NativeOasisMimeType key in the * desktop file * * @return the oasis mimetype or, if it hasn't one, the nativeformatmimetype. */ QByteArray nativeOasisMimeType() const override = 0; /// Checks whether a given mimetype can be handled natively. bool isNativeFormat(const QByteArray& mimetype) const; /// Returns a list of the mimetypes considered "native", i.e. which can /// be saved by KoDocument without a filter, in *addition* to the main one virtual QStringList extraNativeMimeTypes() const = 0; /** * Return the set of SupportedSpecialFormats that the application wants to * offer in the "Save" file dialog. */ int supportedSpecialFormats() const override; /** * Returns the actual mimetype of the document */ QByteArray mimeType() const override; /** * @brief Sets the mime type for the document. * * When choosing "save as" this is also the mime type * selected by default. */ void setMimeType(const QByteArray & mimeType) override; /** * @brief Set the format in which the document should be saved. * * This is called on loading, and in "save as", so you shouldn't * have to call it. * * @param mimeType the mime type (format) to use. * @param specialOutputFlag is for "save as older version" etc. */ void setOutputMimeType(const QByteArray & mimeType, int specialOutputFlag = 0) override; QByteArray outputMimeType() const override; int specialOutputFlag() const override; /** * Returns true if this document was the result of opening a foreign * file format and if the user hasn't yet saved the document (in any * format). * * Used by KoMainWindow to warn the user when s/he lazily presses * CTRL+S to save in the same foreign format, putting all his/her * formatting at risk (normally an export confirmation only comes up * with Save As). * * @param exporting specifies whether this is the setting for a * File --> Export or File --> Save/Save As operation. */ bool confirmNonNativeSave(const bool exporting) const; void setConfirmNonNativeSave(const bool exporting, const bool on); /** * @return true if saving/exporting should inhibit the option dialog */ bool saveInBatchMode() const; /** * @param batchMode if true, do not show the option dialog when saving or exporting. */ void setSaveInBatchMode(const bool batchMode); /** * Sets the error message to be shown to the user (use i18n()!) * when loading or saving fails. * If you asked the user about something and they chose "Cancel", * set the message to the magic string "USER_CANCELED", to skip the error dialog. */ void setErrorMessage(const QString& errMsg); /** * Return the last error message. Usually KoDocument takes care of * showing it; this method is mostly provided for non-interactive use. */ QString errorMessage() const; /** * Show the last error message in a message box. * The dialog box will mention a loading problem. * openUrl/openFile takes care of doing it, but not loadNativeFormat itself, * so this is often called after loadNativeFormat returned false. */ void showLoadingErrorDialog(); /** * @brief Generates a preview picture of the document * @note The preview is used in the File Dialog and also to create the Thumbnail */ virtual QPixmap generatePreview(const QSize& size); /** * Paints the data itself. * It's this method that %Calligra Parts have to implement. * * @param painter The painter object onto which will be drawn. * @param rect The rect that should be used in the painter object. */ virtual void paintContent(QPainter &painter, const QRect &rect) = 0; /** * Tells the document that its title has been modified, either because * the modified status changes (this is done by setModified()) or * because the URL or the document-info's title changed. */ void setTitleModified(); /** * @return true if the document is empty. */ bool isEmpty() const override; /** * @brief Sets the document to empty. * * Used after loading a template * (which is not empty, but not the user's input). * * @see isEmpty() */ virtual void setEmpty(); /** * @brief Loads a document from a store. * * You should never have to reimplement. * * @param store The store to load from * @param url An internal url, like tar:/1/2 */ virtual bool loadFromStore(KoStore *store, const QString& url); /** * @brief Loads an OASIS document from a store. * This is used for both the main document and embedded objects. */ virtual bool loadOasisFromStore(KoStore *store); /** * @brief Saves a sub-document to a store. * * You should not have to reimplement this. */ bool saveToStore(KoStore *store, const QString& path) override; /** * Reimplement this method to load the contents of your Calligra document, * from the XML document. This is for the pre-Oasis file format (maindoc.xml). */ virtual bool loadXML(const KoXmlDocument & doc, KoStore *store) = 0; /** * Reimplement this to save the contents of the %Calligra document into * a QDomDocument. The framework takes care of saving it to the store. */ virtual QDomDocument saveXML(); /** * Return a correctly created QDomDocument for this KoDocument, * including processing instruction, complete DOCTYPE tag (with systemId and publicId), and root element. * @param tagName the name of the tag for the root element * @param version the DTD version (usually the application's version). */ QDomDocument createDomDocument(const QString& tagName, const QString& version) const; /** * Return a correctly created QDomDocument for an old (1.3-style) %Calligra document, * including processing instruction, complete DOCTYPE tag (with systemId and publicId), and root element. * This static method can be used e.g. by filters. * @param appName the app's instance name, e.g. words, kspread, kpresenter etc. * @param tagName the name of the tag for the root element, e.g. DOC for words/kpresenter. * @param version the DTD version (usually the application's version). */ static QDomDocument createDomDocument(const QString& appName, const QString& tagName, const QString& version); /** * The first thing to do in loadOasis is get hold of the office:body tag, then its child. * If the child isn't the expected one, the error message can indicate what it is instead. * This method returns a translated name for the type of document, * e.g. i18n("Word Processing") for office:text. */ static QString tagNameToDocumentType(const QString& localName); /** * Loads a document in the native format from a given URL. * Reimplement if your native format isn't XML. * * @param file the file to load - usually KReadOnlyPart::m_file or the result of a filter */ virtual bool loadNativeFormat(const QString & file); /** * Saves the document in native format, to a given file * You should never have to reimplement. * Made public for writing templates. */ virtual bool saveNativeFormat(const QString & file); /** * Saves the document in native ODF format to the given store. */ bool saveNativeFormatODF(KoStore *store, const QByteArray &mimeType); /** * Saves the document in the native format to the given store. */ bool saveNativeFormatCalligra(KoStore *store); /** * Activate/deactivate/configure the autosave feature. * @param delay in seconds, 0 to disable */ void setAutoSave(int delay); /** * Checks whether the document is currently in the process of autosaving */ bool isAutosaving() const override; /** * Set whether the next openUrl call should check for an auto-saved file * and offer to open it. This is usually true, but can be turned off * (e.g. for the preview module). This only checks for names auto-saved * files, unnamed auto-saved files are only checked on KoApplication startup. */ void setCheckAutoSaveFile(bool b); /** * Set whether the next openUrl call should show error message boxes in case * of errors. This is usually the case, but e.g. not when generating thumbnail * previews. */ void setAutoErrorHandlingEnabled(bool b); /** * Checks whether error message boxes should be shown. */ bool isAutoErrorHandlingEnabled() const; /** * Retrieve the default value for autosave in seconds. * Called by the applications to use the correct default in their config */ static int defaultAutoSave(); /** * @return the information concerning this document. * @see KoDocumentInfo */ KoDocumentInfo *documentInfo() const; /** * @return the Rdf metadata for this document. * This method should only be used by code that links to * the RDF system and needs full access to the KoDocumentRdf object. * @see KoDocumentRdf */ // KoDocumentRdfBase *documentRdf() const; /** * Replace the current rdf document with the given rdf document. The existing RDF document * will be deleted, and if RDF support is compiled out, KoDocument does not take ownership. * Otherwise, KoDocument will own the rdf document. */ //void setDocumentRdf(KoDocumentRdfBase *rdfDocument); /** * @return the object to report progress to. * One can add more KoUpdaters to it to make the progress reporting more * accurate. If no active progress reporter is present, 0 is returned. **/ KoProgressUpdater *progressUpdater() const; /** * Set a custom progress proxy to use to report loading * progress to. */ void setProgressProxy(KoProgressProxy *progressProxy); KoProgressProxy* progressProxy() const; /** * Return true if url() is a real filename, false if url() is * an internal url in the store, like "tar:/..." */ bool isStoredExtern() const override; /** * @return the page layout associated with this document (margins, pageSize, etc). * Override this if you want to provide different sized pages. * * @see KoPageLayout */ virtual KoPageLayout pageLayout(int pageNumber = 0) const; virtual void setPageLayout(const KoPageLayout &pageLayout); /** * Performs a cleanup of unneeded backup files */ void removeAutoSaveFiles(); void setBackupFile(bool _b); bool backupFile()const; /** * Returns true if this document or any of its internal child documents are modified. */ Q_INVOKABLE bool isModified() const override; /** * Returns true during loading (openUrl can be asynchronous) */ - bool isLoading() const; + virtual bool isLoading() const; int queryCloseDia(); /** * Sets the backup path of the document */ void setBackupPath(const QString & _path); /** * @return path to the backup document */ QString backupPath()const; /** * @return caption of the document * * Caption is of the form "[title] - [url]", * built out of the document info (title) and pretty-printed * document URL. * If the title is not present, only the URL it returned. */ QString caption() const; /** * Sets the document URL to empty URL * KParts doesn't allow this, but %Calligra apps have e.g. templates * After using loadNativeFormat on a template, one wants * to set the url to QUrl() */ void resetURL() override; /** * Set when you want an external embedded document to be stored internally */ void setStoreInternal(bool i); /** * @return true when external embedded documents are stored internally */ bool storeInternal() const; bool hasExternURL() const; /** * @internal (public for KoMainWindow) */ void setMimeTypeAfterLoading(const QString& mimeType); /** * @return returns the number of pages in the document. */ virtual int pageCount() const; /** * Returns the unit used to display all measures/distances. */ KoUnit unit() const; /** * Sets the unit used to display all measures/distances. */ void setUnit(const KoUnit &unit); /** * Save the unit to the settings writer * * @param settingsWriter */ void saveUnitOdf(KoXmlWriter *settingsWriter) const; QList &versionList(); bool loadNativeFormatFromStore(QByteArray &data); /** * Adds a new version and then saves the whole document. * @param comment the comment for the version * @return true on success, otherwise false */ bool addVersion(const QString& comment); /// return the grid data for this document. // KoGridData &gridData(); /// returns the guides data for this document. // KoGuidesData &guidesData(); void clearUndoHistory(); /** * Sets the modified flag on the document. This means that it has * to be saved or not before deleting it. */ Q_INVOKABLE virtual void setModified(bool _mod); /** * Initialize an empty document using default values */ virtual void initEmpty(); /** * Returns the global undo stack */ KUndo2Stack *undoStack(); /** * Set the output stream to report profile information to. */ void setProfileStream(QTextStream *profilestream); /** * Set the output stream to report profile information to. */ void setProfileReferenceTime(const QTime& referenceTime); /// If set, the document shall be saved even if it is not marked as modified. /// @see setAlwaysAllowSaving() bool alwaysAllowSaving() const; /// Set alwaysAllowSaving to @p allow. /// Enables applications to always allow saving even when document is not modified. /// This makes it possible to save settings/context info without marking /// the document as modified. /// @see alwaysAllowSaving() void setAlwaysAllowSaving(bool allow); public Q_SLOTS: /** * Adds a command to the undo stack and executes it by calling the redo() function. * @param command command to add to the undo stack */ virtual void addCommand(KUndo2Command *command); /** * Begins recording of a macro command. At the end endMacro needs to be called. * @param text command description */ virtual void beginMacro(const KUndo2MagicString &text); /** * Ends the recording of a macro command. */ virtual void endMacro(); Q_SIGNALS: /** * This signal is emitted when the unit is changed by setUnit(). * It is common to connect views to it, in order to change the displayed units * (e.g. in the rulers) */ void unitChanged(const KoUnit &unit); /** * Progress info while loading or saving. The value is in percents (i.e. a number between 0 and 100) * Your KoDocument-derived class should emit the signal now and then during load/save. * KoMainWindow will take care of displaying a progress bar automatically. */ void sigProgress(int value); /** * Emitted e.g. at the beginning of a save operation * This is emitted by KoDocument and used by KoView to display a statusbar message */ void statusBarMessage(const QString& text); /** * Emitted e.g. at the end of a save operation * This is emitted by KoDocument and used by KoView to clear the statusbar message */ void clearStatusBarMessage(); /** * Emitted when the document is modified */ void modified(bool); void titleModified(const QString &caption, bool isModified); void backupFileChanged(bool); protected: friend class KoPart; /** * Generate a name for the document. */ QString newObjectName(); QString autoSaveFile(const QString & path) const; void setDisregardAutosaveFailure(bool disregardFailure); /** * Loads a document from KReadOnlyPart::m_file (KParts takes care of downloading * remote documents). * Applies a filter if necessary, and calls loadNativeFormat in any case * You should not have to reimplement, except for very special cases. * * NOTE: this method also creates a new KoView instance! * * This method is called from the KReadOnlyPart::openUrl method. */ virtual bool openFile(); /** * This method is called by @a openFile() to allow applications to setup there * own KoProgressUpdater-subTasks which are then taken into account for the * displayed progressbar during loading. */ virtual void setupOpenFileSubProgress(); /** * Saves a document to KReadOnlyPart::m_file (KParts takes care of uploading * remote documents) * Applies a filter if necessary, and calls saveNativeFormat in any case * You should not have to reimplement, except for very special cases. */ virtual bool saveFile(); /** * Overload this function if you have to load additional files * from a store. This function is called after loadXML() * and after loadChildren() have been called. */ virtual bool completeLoading(KoStore *store); /** * If you want to write additional files to a store, * then you must do it here. * In the implementation, you should prepend the document * url (using url().url()) before the filename, so that everything is kept relative * to this document. For instance it will produce urls such as * tar:/1/pictures/picture0.png, if the doc url is tar:/1 * But do this ONLY if the document is not stored extern (see isStoredExtern()). * If it is, then the pictures should be saved to tar:/pictures. */ virtual bool completeSaving(KoStore *store); /** @internal */ virtual void setModified(); /** * Returns whether or not the current openUrl() or openFile() call is * actually an import operation (like File --> Import). * This is for informational purposes only. */ bool isImporting() const; /** * Returns whether or not the current saveFile() call is actually an export * operation (like File --> Export). * If this function returns true during saveFile() and you are changing * some sort of state, you _must_ restore it before the end of saveFile(); * otherwise, File --> Export will not work properly. */ bool isExporting() const; public: QString localFilePath() const override; void setLocalFilePath(const QString &localFilePath); virtual KoDocumentInfoDlg* createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const; bool isReadWrite() const; QUrl url() const override; void setUrl(const QUrl &url) override; virtual bool closeUrl(bool promptToSave = true); virtual bool saveAs(const QUrl &url); public Q_SLOTS: virtual bool save(); bool waitSaveComplete(); Q_SIGNALS: void completed(); void canceled(const QString &); private Q_SLOTS: void slotAutoSave(); /// Called by the undo stack when undo or redo is called void slotUndoStackIndexChanged(int idx); protected: bool oldLoadAndParse(KoStore *store, const QString& filename, KoXmlDocument& doc); private: bool saveToStream(QIODevice *dev); QString checkImageMimeTypes(const QString &mimeType, const QUrl &url) const; bool loadNativeFormatFromStore(const QString& file); bool loadNativeFormatFromStoreInternal(KoStore *store); bool savePreview(KoStore *store); bool saveOasisPreview(KoStore *store, KoXmlWriter *manifestWriter); QString prettyPathOrUrl() const; bool queryClose(); bool saveToUrl(); bool openUrlInternal(const QUrl &url); void abortLoad(); class Private; Private *const d; Q_PRIVATE_SLOT(d, void _k_slotJobFinished(KJob * job)) Q_PRIVATE_SLOT(d, void _k_slotStatJobFinished(KJob*)) Q_PRIVATE_SLOT(d, void _k_slotGotMimeType(KIO::Job *job, const QString &mime)) Q_PRIVATE_SLOT(d, void _k_slotUploadFinished(KJob * job)) }; Q_DECLARE_METATYPE(KoDocument*) #endif