diff --git a/src/kptcontext.cpp b/src/kptcontext.cpp index 87bb2e5b..f9f3a41a 100644 --- a/src/kptcontext.cpp +++ b/src/kptcontext.cpp @@ -1,133 +1,145 @@ /* This file is part of the KDE project Copyright (C) 2005, 2007, 2011 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 "kptcontext.h" #include "kptview.h" #include "kptdebug.h" #include namespace KPlato { Context::Context() : currentEstimateType(0), currentSchedule(0), m_contextLoaded( false ) { ganttview.ganttviewsize = -1; ganttview.taskviewsize = -1; accountsview.accountsviewsize = -1; accountsview.periodviewsize = -1; } Context::~Context() { } const KoXmlElement &Context::context() const { return m_context; } bool Context::setContent( const QString &str ) { KoXmlDocument doc; if ( doc.setContent( str ) ) { return load( doc ); } return false; } +QDomDocument Context::document() const +{ + QDomDocument document( "plan.context" ); + + document.appendChild( document.createProcessingInstruction( + "xml", + "version=\"1.0\" encoding=\"UTF-8\"" ) ); + + KoXml::toQDomDocument(m_document, document); + return document; +} + bool Context::load( const KoXmlDocument &document ) { m_document = document; // create a copy, document is deleted under our feet // Check if this is the right app KoXmlElement elm = m_document.documentElement(); QString value = elm.attribute( "mime", QString() ); if ( value.isEmpty() ) { errorPlan << "No mime type specified!"; // setErrorMessage( i18n( "Invalid document. No mimetype specified." ) ); return false; } else if ( value != "application/x-vnd.kde.plan" ) { if ( value == "application/x-vnd.kde.kplato" ) { // accept, since we forgot to change kplato to plan for so long... } else { errorPlan << "Unknown mime type " << value; // setErrorMessage( i18n( "Invalid document. Expected mimetype application/x-vnd.kde.kplato, got %1", value ) ); return false; } } /* QString m_syntaxVersion = elm.attribute( "version", "0.0" ); if ( m_syntaxVersion > "0.0" ) { 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.", m_syntaxVersion ), i18n( "File-Format Mismatch" ), KGuiItem( i18n( "Continue" ) ) ); if ( ret == KMessageBox::Cancel ) { setErrorMessage( "USER_CANCELED" ); return false; } } */ /* #ifdef KOXML_USE_QDOM int numNodes = elm.childNodes().count(); #else int numNodes = elm.childNodesCount(); #endif */ KoXmlNode n = elm.firstChild(); for ( ; ! n.isNull(); n = n.nextSibling() ) { if ( ! n.isElement() ) { continue; } KoXmlElement element = n.toElement(); if ( element.tagName() == "context" ) { m_context = element; m_contextLoaded = true; } } return true; } QDomDocument Context::save( const View *view ) const { QDomDocument document( "plan.context" ); document.appendChild( document.createProcessingInstruction( "xml", "version=\"1.0\" encoding=\"UTF-8\"" ) ); QDomElement doc = document.createElement( "context" ); doc.setAttribute( "editor", "Plan" ); doc.setAttribute( "mime", "application/x-vnd.kde.plan" ); doc.setAttribute( "version", QString::number(0.0) ); document.appendChild( doc ); QDomElement e = doc.ownerDocument().createElement("context"); doc.appendChild( e ); view->saveContext( e ); return document; } } //KPlato namespace diff --git a/src/kptcontext.h b/src/kptcontext.h index db1b9ac4..d6f2e7cc 100644 --- a/src/kptcontext.h +++ b/src/kptcontext.h @@ -1,95 +1,98 @@ /* This file is part of the KDE project Copyright (C) 2005, 2007 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 KPTCONTEXT_H #define KPTCONTEXT_H #include #include #include namespace KPlato { class View; class Context { public: Context(); virtual ~Context(); virtual bool load( const KoXmlDocument &doc ); virtual QDomDocument save( const View *view ) const; const KoXmlElement &context() const; bool isLoaded() const { return m_contextLoaded; } bool setContent( const QString &str ); + QDomDocument document() const; + // View QString currentView; int currentEstimateType; long currentSchedule; bool actionViewExpected; bool actionViewOptimistic; bool actionViewPessimistic; struct Ganttview { int ganttviewsize; int taskviewsize; QString currentNode; bool showResources; bool showTaskName; bool showTaskLinks; bool showProgress; bool showPositiveFloat; bool showCriticalTasks; bool showCriticalPath; bool showNoInformation; QStringList closedNodes; } ganttview; struct Pertview { } pertview; struct Resourceview { } resourceview; struct Accountsview { int accountsviewsize; int periodviewsize; QDate date; int period; bool cumulative; QStringList closedItems; } accountsview; struct Reportview { } reportview; private: bool m_contextLoaded; KoXmlElement m_context; KoXmlDocument m_document; + QDomDocument m_qDomDocument; }; } //KPlato namespace #endif //CONTEXT_H diff --git a/src/kptmaindocument.cpp b/src/kptmaindocument.cpp index 11ad1ff2..0a3ef0e8 100644 --- a/src/kptmaindocument.cpp +++ b/src/kptmaindocument.cpp @@ -1,1625 +1,1638 @@ /* 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_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); 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.") ); } } 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); 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.") ); } } void MainDocument::insertFileCancelled( const QString &error ) { debugPlan<( sender() ); if ( doc ) { doc->documentPart()->deleteLater(); // also deletes document } } 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); 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 emit insertSharedProject(); // do next file } else { KMessageBox::error( 0, i18n("Internal error, failed to insert file.") ); } } void MainDocument::insertSharedProjectCancelled( const QString &error ) { debugPlanShared<( sender() ); if ( doc ) { doc->documentPart()->deleteLater(); // also deletes document } } 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; } 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/kptview.cpp b/src/kptview.cpp index 82d6d3ca..ea6373a5 100644 --- a/src/kptview.cpp +++ b/src/kptview.cpp @@ -1,3247 +1,3263 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999, 2000 Torben Weis Copyright (C) 2002 - 2011 Dag Andersen Copyright (C) 2012 Dag Andersen 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 "kptview.h" #include #include #include "KoDocumentInfo.h" #include "KoMainWindow.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kptlocale.h" #include "kptviewbase.h" #include "kptaccountsview.h" #include "kptaccountseditor.h" #include "kptcalendareditor.h" #include "kptfactory.h" #include "kptmilestoneprogressdialog.h" #include "kpttaskdescriptiondialog.h" #include "kptdocumentsdialog.h" #include "kptnode.h" #include "kptmaindocument.h" #include "kptproject.h" #include "kptmainprojectdialog.h" #include "kpttask.h" #include "kptsummarytaskdialog.h" #include "kpttaskdialog.h" #include "kpttaskprogressdialog.h" #include "kptganttview.h" #include "kpttaskeditor.h" #include "kptdependencyeditor.h" #include "kptperteditor.h" #include "kptdatetime.h" #include "kptcommand.h" #include "kptrelation.h" #include "kptrelationdialog.h" #include "kptresourceappointmentsview.h" #include "kptresourceeditor.h" #include "kptscheduleeditor.h" #include "kptresourcedialog.h" #include "kptresource.h" #include "kptstandardworktimedialog.h" #include "kptwbsdefinitiondialog.h" #include "kptresourceassignmentview.h" #include "kpttaskstatusview.h" #include "kptsplitterview.h" #include "kptpertresult.h" #include "kptinsertfiledlg.h" #include "kptloadsharedprojectsdialog.h" #include "kpthtmlview.h" #include "about/aboutpage.h" #include "kptlocaleconfigmoneydialog.h" #include "kptflatproxymodel.h" #include "kpttaskstatusmodel.h" #include "kptworkpackagemergedialog.h" #include "Help.h" #include "performance/PerformanceStatusView.h" #include "performance/ProjectStatusView.h" #include "reportsgenerator/ReportsGeneratorView.h" #ifdef PLAN_USE_KREPORT #include "reports/reportview.h" #include "reports/reportdata.h" #endif #include "kptviewlistdialog.h" #include "kptviewlistdocker.h" #include "kptviewlist.h" #include "kptschedulesdocker.h" #include "kptpart.h" #include "kptdebug.h" #include "calligraplansettings.h" #include "kptprintingcontrolprivate.h" // #include "KPtViewAdaptor.h" #include using namespace KPlato; View::View(KoPart *part, MainDocument *doc, QWidget *parent) : KoView(part, doc, parent), m_currentEstimateType( Estimate::Use_Expected ), m_scheduleActionGroup( new QActionGroup( this ) ), m_readWrite( false ), m_defaultView(1), m_partpart (part) { //debugPlan; new Help(KPlatoSettings::contextPath(), KPlatoSettings::contextLanguage()); doc->registerView( this ); setComponentName(Factory::global().componentName(), Factory::global().componentDisplayName()); if ( !doc->isReadWrite() ) setXMLFile( "calligraplan_readonly.rc" ); else setXMLFile( "calligraplan.rc" ); // new ViewAdaptor( this ); m_sp = new QSplitter( this ); QVBoxLayout *layout = new QVBoxLayout( this ); layout->setMargin(0); layout->addWidget( m_sp ); ViewListDocker *docker = 0; if ( mainWindow() == 0 ) { // Don't use docker if embedded m_viewlist = new ViewListWidget(doc, m_sp); m_viewlist->setProject( &( getProject() ) ); connect( m_viewlist, &ViewListWidget::selectionChanged, this, &View::slotSelectionChanged); connect( this, &View::currentScheduleManagerChanged, m_viewlist, &ViewListWidget::setSelectedSchedule); connect( m_viewlist, &ViewListWidget::updateViewInfo, this, &View::slotUpdateViewInfo); } else { ViewListDockerFactory vl(this); docker = static_cast(mainWindow()->createDockWidget(&vl)); if (docker->view() != this) { docker->setView(this); } m_viewlist = docker->viewList(); #if 0 //SchedulesDocker SchedulesDockerFactory sdf; SchedulesDocker *sd = dynamic_cast( createDockWidget( &sdf ) ); Q_ASSERT( sd ); sd->setProject( &getProject() ); connect(sd, SIGNAL(selectionChanged(KPlato::ScheduleManager*)), SLOT(slotSelectionChanged(KPlato::ScheduleManager*))); connect(this, &View::currentScheduleManagerChanged, sd, SLOT(setSelectedSchedule(KPlato::ScheduleManager*))); #endif } m_tab = new QStackedWidget( m_sp ); //////////////////////////////////////////////////////////////////////////////////////////////////// // Add sub views createIntroductionView(); // The menu items // ------ File actionCreateTemplate = new QAction(koIcon("document-save-as-template"), i18n( "Create Project Template..." ), this); actionCollection()->addAction("file_createtemplate", actionCreateTemplate ); connect( actionCreateTemplate, SIGNAL(triggered(bool)), SLOT(slotCreateTemplate()) ); actionCreateNewProject = new QAction( i18n( "Create New Project..." ), this ); actionCollection()->addAction("file_createnewproject", actionCreateNewProject ); connect( actionCreateNewProject, &QAction::triggered, this, &View::slotCreateNewProject ); // ------ Edit actionCut = actionCollection()->addAction(KStandardAction::Cut, "edit_cut", this, SLOT(slotEditCut())); actionCopy = actionCollection()->addAction(KStandardAction::Copy, "edit_copy", this, SLOT(slotEditCopy())); actionPaste = actionCollection()->addAction(KStandardAction::Paste, "edit_paste", this, SLOT(slotEditPaste())); // ------ View actionCollection()->addAction( KStandardAction::Redisplay, "view_refresh" , this, SLOT(slotRefreshView()) ); actionViewSelector = new KToggleAction(i18n("Show Selector"), this); actionCollection()->addAction("view_show_selector", actionViewSelector ); connect( actionViewSelector, &QAction::triggered, this, &View::slotViewSelector ); // ------ Insert // ------ Project actionEditMainProject = new QAction(koIcon("view-time-schedule-edit"), i18n("Edit Main Project..."), this); actionCollection()->addAction("project_edit", actionEditMainProject ); connect( actionEditMainProject, &QAction::triggered, this, &View::slotProjectEdit ); actionEditStandardWorktime = new QAction(koIcon("configure"), i18n("Define Estimate Conversions..."), this); actionCollection()->addAction("project_worktime", actionEditStandardWorktime ); connect( actionEditStandardWorktime, &QAction::triggered, this, &View::slotProjectWorktime ); actionDefineWBS = new QAction(koIcon("configure"), i18n("Define WBS Pattern..."), this); actionCollection()->addAction("tools_define_wbs", actionDefineWBS ); connect( actionDefineWBS, &QAction::triggered, this, &View::slotDefineWBS ); actionCurrencyConfig = new QAction(koIcon("configure"), i18n("Define Currency..."), this); actionCollection()->addAction( "config_currency", actionCurrencyConfig ); connect( actionCurrencyConfig, &QAction::triggered, this, &View::slotCurrencyConfig ); QAction *actionProjectDescription = new QAction(koIcon("document-edit"), i18n("Edit Description..."), this); actionCollection()->addAction( "edit_project_description", actionProjectDescription ); connect( actionProjectDescription, &QAction::triggered, this, &View::slotOpenProjectDescription ); // ------ Tools actionInsertFile = new QAction(koIcon("document-import"), i18n("Insert Project File..."), this); actionCollection()->addAction("insert_file", actionInsertFile ); connect( actionInsertFile, &QAction::triggered, this, &View::slotInsertFile ); actionLoadSharedProjects = new QAction(koIcon("document-import"), i18n("Load Shared Projects..."), this); actionCollection()->addAction("load_shared_projects", actionLoadSharedProjects ); connect( actionLoadSharedProjects, &QAction::triggered, this, &View::slotLoadSharedProjects ); #ifdef PLAN_USE_KREPORT actionOpenReportFile = new QAction(koIcon("document-open"), i18n("Open Report Definition File..."), this); actionCollection()->addAction( "reportdesigner_open_file", actionOpenReportFile ); connect( actionOpenReportFile, QAction::triggered, this, &View::slotOpenReportFile); #endif // ------ Help actionIntroduction = new QAction(koIcon("dialog-information"), i18n("Introduction to Plan"), this); actionCollection()->addAction("plan_introduction", actionIntroduction ); connect( actionIntroduction, &QAction::triggered, this, &View::slotIntroduction ); // ------ Popup actionOpenNode = new QAction(koIcon("document-edit"), i18n("Edit..."), this); actionCollection()->addAction("node_properties", actionOpenNode ); connect( actionOpenNode, &QAction::triggered, this, &View::slotOpenCurrentNode ); actionTaskProgress = new QAction(koIcon("document-edit"), i18n("Progress..."), this); actionCollection()->addAction("task_progress", actionTaskProgress ); connect( actionTaskProgress, &QAction::triggered, this, &View::slotTaskProgress ); actionDeleteTask = new QAction(koIcon("edit-delete"), i18n("Delete Task"), this); actionCollection()->addAction("delete_task", actionDeleteTask ); connect( actionDeleteTask, &QAction::triggered, this, &View::slotDeleteCurrentTask ); actionTaskDescription = new QAction(koIcon("document-edit"), i18n("Description..."), this); actionCollection()->addAction("task_description", actionTaskDescription ); connect( actionTaskDescription, &QAction::triggered, this, &View::slotTaskDescription ); actionDocuments = new QAction(koIcon("document-edit"), i18n("Documents..."), this); actionCollection()->addAction("task_documents", actionDocuments ); connect( actionDocuments, &QAction::triggered, this, &View::slotDocuments ); actionIndentTask = new QAction(koIcon("format-indent-more"), i18n("Indent Task"), this); actionCollection()->addAction("indent_task", actionIndentTask ); connect( actionIndentTask, &QAction::triggered, this, &View::slotIndentTask ); actionUnindentTask= new QAction(koIcon("format-indent-less"), i18n("Unindent Task"), this); actionCollection()->addAction("unindent_task", actionUnindentTask ); connect( actionUnindentTask, &QAction::triggered, this, &View::slotUnindentTask ); actionMoveTaskUp = new QAction(koIcon("arrow-up"), i18n("Move Task Up"), this); actionCollection()->addAction("move_task_up", actionMoveTaskUp ); connect( actionMoveTaskUp, &QAction::triggered, this, &View::slotMoveTaskUp ); actionMoveTaskDown = new QAction(koIcon("arrow-down"), i18n("Move Task Down"), this); actionCollection()->addAction("move_task_down", actionMoveTaskDown ); connect( actionMoveTaskDown, &QAction::triggered, this, &View::slotMoveTaskDown ); actionEditResource = new QAction(koIcon("document-edit"), i18n("Edit Resource..."), this); actionCollection()->addAction("edit_resource", actionEditResource ); connect( actionEditResource, &QAction::triggered, this, &View::slotEditCurrentResource ); actionEditRelation = new QAction(koIcon("document-edit"), i18n("Edit Dependency..."), this); actionCollection()->addAction("edit_dependency", actionEditRelation ); connect( actionEditRelation, &QAction::triggered, this, &View::slotModifyCurrentRelation ); actionDeleteRelation = new QAction(koIcon("edit-delete"), i18n("Delete Dependency"), this); actionCollection()->addAction("delete_dependency", actionDeleteRelation ); connect( actionDeleteRelation, &QAction::triggered, this, &View::slotDeleteRelation ); // Viewlist popup connect( m_viewlist, &ViewListWidget::createView, this, &View::slotCreateView ); m_workPackageButton = new QToolButton(this); m_workPackageButton->hide(); m_workPackageButton->setIcon(koIcon("application-x-vnd.kde.plan.work")); m_workPackageButton->setText(i18n("Work Packages...")); m_workPackageButton->setToolTip(i18nc("@info:tooltip", "Work packages available")); m_workPackageButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); connect(m_workPackageButton, &QToolButton::clicked, this, &View::openWorkPackageMergeDialog); m_estlabel = new QLabel( "", 0 ); if ( statusBar() ) { addStatusBarItem( m_estlabel, 0, true ); } connect( &getProject(), &Project::scheduleManagerAdded, this, &View::slotScheduleAdded ); connect( &getProject(), &Project::scheduleManagerRemoved, this, &View::slotScheduleRemoved ); connect( &getProject(), &Project::scheduleManagersSwapped, this, &View::slotScheduleSwapped ); connect( &getProject(), &Project::sigCalculationFinished, this, &View::slotScheduleCalculated ); slotPlugScheduleActions(); connect( doc, &MainDocument::changed, this, &View::slotUpdate ); connect( m_scheduleActionGroup, &QActionGroup::triggered, this, &View::slotViewSchedule ); connect( getPart(), &MainDocument::workPackageLoaded, this, &View::slotWorkPackageLoaded ); // views take time for large projects QTimer::singleShot(0, this, &View::initiateViews); const QList pluginFactories = KoPluginLoader::instantiatePluginFactories(QStringLiteral("calligraplan/extensions")); foreach (KPluginFactory* factory, pluginFactories) { QObject *object = factory->create(this, QVariantList()); KXMLGUIClient *clientPlugin = dynamic_cast(object); if (clientPlugin) { insertChildClient(clientPlugin); } else { // not our/valid plugin, so delete the created object object->deleteLater(); } } //debugPlan<<" end"; } View::~View() { // Disconnect and delete so we do not get called by destroyed() signal const QMap map = m_scheduleActions; // clazy:exclude=qmap-with-pointer-key QMap::const_iterator it; for (it = map.constBegin(); it != map.constEnd(); ++it) { disconnect(it.key(), &QObject::destroyed, this, &View::slotActionDestroyed); m_scheduleActionGroup->removeAction(it.key()); delete it.key(); } ViewBase *view = currentView(); if (view) { // deactivate view to remove dockers etc slotGuiActivated(view, false); } /* removeStatusBarItem( m_estlabel ); delete m_estlabel;*/ } void View::initiateViews() { QApplication::setOverrideCursor( Qt::WaitCursor ); createViews(); connect( m_viewlist, &ViewListWidget::activated, this, &View::slotViewActivated ); // after createViews() !! connect( m_viewlist, &ViewListWidget::viewListItemRemoved, this, &View::slotViewListItemRemoved ); // after createViews() !! connect( m_viewlist, &ViewListWidget::viewListItemInserted, this, &View::slotViewListItemInserted ); ViewListDocker *docker = qobject_cast( m_viewlist->parent() ); if ( docker ) { // after createViews() !! connect( m_viewlist, &ViewListWidget::modified, docker, &ViewListDocker::slotModified); connect( m_viewlist, &ViewListWidget::modified, getPart(), &MainDocument::slotViewlistModified); connect(getPart(), &MainDocument::viewlistModified, docker, &ViewListDocker::updateWindowTitle); } connect( m_tab, &QStackedWidget::currentChanged, this, &View::slotCurrentChanged ); slotSelectDefaultView(); loadContext(); QApplication::restoreOverrideCursor(); } void View::slotCreateNewProject() { debugPlan; if ( KMessageBox::Continue == KMessageBox::warningContinueCancel( this, xi18nc( "@info", "This action cannot be undone." "Create a new Project from the current project " "with new project- and task identities." "Resource- and calendar identities are not changed." "All scheduling information is removed." "Do you want to continue?" ) ) ) { emit currentScheduleManagerChanged(0); getPart()->createNewProject(); slotOpenNode( &getProject() ); } } void View::slotCreateTemplate() { debugPlan; KoFileDialog dlg(nullptr, KoFileDialog::SaveFile, "Create Template"); dlg.setNameFilters(QStringList()<<"Plan Template (*.plant)"); QString file = dlg.filename(); if (!file.isEmpty()) { - QUrl url = QUrl::fromUserInput(file); - koDocument()->exportDocument(url); - qInfo()<setDocument(doc); + doc->disconnect(); // doc shall not handle feedback from openUrl() + doc->setAutoSave(0); //disable + bool ok = koDocument()->exportDocument(QUrl::fromUserInput("file:/" + tmpfile)); + ok &= doc->loadNativeFormat(tmpfile); + if (ok) { + // strip unused data + Project *project = doc->project(); + for (ScheduleManager *sm : project->scheduleManagers()) { + DeleteScheduleManagerCmd c(*project, sm); + c.redo(); + } + } + ok &= doc->saveNativeFormat(file); + + part->deleteLater(); } } void View::createViews() { Context *ctx = getPart()->context(); if ( ctx && ctx->isLoaded() ) { debugPlan<<"isLoaded"; KoXmlNode n = ctx->context().namedItem( "categories" ); if ( n.isNull() ) { warnPlan<<"No categories"; } else { n = n.firstChild(); for ( ; ! n.isNull(); n = n.nextSibling() ) { if ( ! n.isElement() ) { continue; } KoXmlElement e = n.toElement(); if (e.tagName() != "category") { continue; } debugPlan<<"category: "<addCategory( ct, cn ); KoXmlNode n1 = e.firstChild(); for ( ; ! n1.isNull(); n1 = n1.nextSibling() ) { if ( ! n1.isElement() ) { continue; } KoXmlElement e1 = n1.toElement(); if (e1.tagName() != "view") { continue; } ViewBase *v = 0; QString type = e1.attribute( "viewtype" ); QString tag = e1.attribute( "tag" ); QString name = e1.attribute( "name" ); QString tip = e1.attribute( "tooltip" ); v = createView( cat, type, tag, name, tip ); //KoXmlNode settings = e1.namedItem( "settings " ); ???? KoXmlNode settings = e1.firstChild(); for ( ; ! settings.isNull(); settings = settings.nextSibling() ) { if ( settings.nodeName() == "settings" ) { break; } } if ( v && settings.isElement() ) { debugPlan<<" settings"; v->loadContext( settings.toElement() ); } } } } } else { debugPlan<<"Default"; ViewBase *v = 0; ViewListItem *cat; QString ct = "Editors"; cat = m_viewlist->addCategory( ct, defaultCategoryInfo( ct ).name ); createCalendarEditor( cat, "CalendarEditor", QString(), TIP_USE_DEFAULT_TEXT ); createAccountsEditor( cat, "AccountsEditor", QString(), TIP_USE_DEFAULT_TEXT ); v = createResourceEditor( cat, "ResourceEditor", QString(), TIP_USE_DEFAULT_TEXT ); v = createTaskEditor( cat, "TaskEditor", QString(), TIP_USE_DEFAULT_TEXT ); m_defaultView = m_tab->count() - 1; v->showColumns(QList() << NodeModel::NodeName << NodeModel::NodeType << NodeModel::NodeAllocation << NodeModel::NodeEstimateCalendar << NodeModel::NodeEstimate << NodeModel::NodeOptimisticRatio << NodeModel::NodePessimisticRatio << NodeModel::NodeRisk << NodeModel::NodeResponsible << NodeModel::NodeDescription ); v = createTaskEditor( cat, "TaskConstraintEditor", i18n("Task Constraints"), i18n("Edit task scheduling constraints") ); v->showColumns(QList() << NodeModel::NodeName << NodeModel::NodeType << NodeModel::NodePriority << NodeModel::NodeConstraint << NodeModel::NodeConstraintStart << NodeModel::NodeConstraintEnd << NodeModel::NodeDescription ); v = createTaskEditor( cat, "TaskCostEditor", i18n("Task Cost"), i18n("Edit task cost") ); v->showColumns(QList() << NodeModel::NodeName << NodeModel::NodeType << NodeModel::NodeRunningAccount << NodeModel::NodeStartupAccount << NodeModel::NodeStartupCost << NodeModel::NodeShutdownAccount << NodeModel::NodeShutdownCost << NodeModel::NodeDescription ); createDependencyEditor( cat, "DependencyEditor", QString(), TIP_USE_DEFAULT_TEXT ); // Do not show by default // createPertEditor( cat, "PertEditor", QString(), TIP_USE_DEFAULT_TEXT ); createScheduleHandler( cat, "ScheduleHandlerView", QString(), TIP_USE_DEFAULT_TEXT ); ct = "Views"; cat = m_viewlist->addCategory( ct, defaultCategoryInfo( ct ).name ); createGanttView( cat, "GanttView", QString(), TIP_USE_DEFAULT_TEXT ); createMilestoneGanttView( cat, "MilestoneGanttView", QString(), TIP_USE_DEFAULT_TEXT ); createResourceAppointmentsView( cat, "ResourceAppointmentsView", QString(), TIP_USE_DEFAULT_TEXT ); createResourceAppointmentsGanttView( cat, "ResourceAppointmentsGanttView", QString(), TIP_USE_DEFAULT_TEXT ); createAccountsView( cat, "AccountsView", QString(), TIP_USE_DEFAULT_TEXT ); ct = "Execution"; cat = m_viewlist->addCategory( ct, defaultCategoryInfo( ct ).name ); createProjectStatusView( cat, "ProjectStatusView", QString(), TIP_USE_DEFAULT_TEXT ); createPerformanceStatusView( cat, "PerformanceStatusView", QString(), TIP_USE_DEFAULT_TEXT ); v = createTaskStatusView( cat, "TaskStatusView", QString(), TIP_USE_DEFAULT_TEXT ); v = createTaskView( cat, "TaskView", QString(), TIP_USE_DEFAULT_TEXT ); v = createTaskWorkPackageView( cat, "TaskWorkPackageView", QString(), TIP_USE_DEFAULT_TEXT ); ct = "Reports"; cat = m_viewlist->addCategory(ct, defaultCategoryInfo(ct).name); createReportsGeneratorView(cat, "ReportsGeneratorView", i18n("Generate reports"), TIP_USE_DEFAULT_TEXT); #ifdef PLAN_USE_KREPORT // Let user add reports explicitly, we prefer reportsgenerator now // A little hack to get the user started... #if 0 ReportView *rv = qobject_cast( createReportView( cat, "ReportView", i18n( "Task Status Report" ), TIP_USE_DEFAULT_TEXT ) ); if ( rv ) { QDomDocument doc; doc.setContent( standardTaskStatusReport() ); rv->loadXML( doc ); } #endif #endif } } ViewBase *View::createView( ViewListItem *cat, const QString &type, const QString &tag, const QString &name, const QString &tip, int index ) { ViewBase *v = 0; //NOTE: type is the same as classname (so if it is changed...) if ( type == "CalendarEditor" ) { v = createCalendarEditor( cat, tag, name, tip, index ); } else if ( type == "AccountsEditor" ) { v = createAccountsEditor( cat, tag, name, tip, index ); } else if ( type == "ResourceEditor" ) { v = createResourceEditor( cat, tag, name, tip, index ); } else if ( type == "TaskEditor" ) { v = createTaskEditor( cat, tag, name, tip, index ); } else if ( type == "DependencyEditor" ) { v = createDependencyEditor( cat, tag, name, tip, index ); } else if ( type == "PertEditor" ) { v = createPertEditor( cat, tag, name, tip, index ); } else if ( type == "ScheduleEditor" ) { v = createScheduleEditor( cat, tag, name, tip, index ); } else if ( type == "ScheduleHandlerView" ) { v = createScheduleHandler( cat, tag, name, tip, index ); } else if ( type == "ProjectStatusView" ) { v = createProjectStatusView( cat, tag, name, tip, index ); } else if ( type == "TaskStatusView" ) { v = createTaskStatusView( cat, tag, name, tip, index ); } else if ( type == "TaskView" ) { v = createTaskView( cat, tag, name, tip, index ); } else if ( type == "TaskWorkPackageView" ) { v = createTaskWorkPackageView( cat, tag, name, tip, index ); } else if ( type == "GanttView" ) { v = createGanttView( cat, tag, name, tip, index ); } else if ( type == "MilestoneGanttView" ) { v = createMilestoneGanttView( cat, tag, name, tip, index ); } else if ( type == "ResourceAppointmentsView" ) { v = createResourceAppointmentsView( cat, tag, name, tip, index ); } else if ( type == "ResourceAppointmentsGanttView" ) { v = createResourceAppointmentsGanttView( cat, tag, name, tip, index ); } else if ( type == "AccountsView" ) { v = createAccountsView( cat, tag, name, tip, index ); } else if ( type == "PerformanceStatusView" ) { v = createPerformanceStatusView( cat, tag, name, tip, index ); } else if ( type == "ReportsGeneratorView" ) { v = createReportsGeneratorView(cat, tag, name, tip, index); } else if ( type == "ReportView" ) { #ifdef PLAN_USE_KREPORT v = createReportView( cat, tag, name, tip, index ); #endif } else { warnPlan<<"Unknown viewtype: "<type() == ViewListItem::ItemType_SubView ) { itm->setViewInfo( defaultViewInfo( itm->viewType() ) ); } else if ( itm->type() == ViewListItem::ItemType_Category ) { ViewInfo vi = defaultCategoryInfo( itm->tag() ); itm->setViewInfo( vi ); } } ViewInfo View::defaultViewInfo( const QString &type ) const { ViewInfo vi; if ( type == "CalendarEditor" ) { vi.name = i18n( "Work & Vacation" ); vi.tip = xi18nc( "@info:tooltip", "Edit working- and vacation days for resources" ); } else if ( type == "AccountsEditor" ) { vi.name = i18n( "Cost Breakdown Structure" ); vi.tip = xi18nc( "@info:tooltip", "Edit cost breakdown structure." ); } else if ( type == "ResourceEditor" ) { vi.name = i18n( "Resources" ); vi.tip = xi18nc( "@info:tooltip", "Edit resource breakdown structure" ); } else if ( type == "TaskEditor" ) { vi.name = i18n( "Tasks" ); vi.tip = xi18nc( "@info:tooltip", "Edit work breakdown structure" ); } else if ( type == "DependencyEditor" ) { vi.name = i18n( "Dependencies (Graphic)" ); vi.tip = xi18nc( "@info:tooltip", "Edit task dependencies" ); } else if ( type == "PertEditor" ) { vi.name = i18n( "Dependencies (List)" ); vi.tip = xi18nc( "@info:tooltip", "Edit task dependencies" ); } else if ( type == "ScheduleEditor" ) { // This view is not used stand-alone atm vi.name = i18n( "Schedules" ); } else if ( type == "ScheduleHandlerView" ) { vi.name = i18n( "Schedules" ); vi.tip = xi18nc( "@info:tooltip", "Calculate and analyze project schedules" ); } else if ( type == "ProjectStatusView" ) { vi.name = i18n( "Project Performance Chart" ); vi.tip = xi18nc( "@info:tooltip", "View project status information" ); } else if ( type == "TaskStatusView" ) { vi.name = i18n( "Task Status" ); vi.tip = xi18nc( "@info:tooltip", "View task progress information" ); } else if ( type == "TaskView" ) { vi.name = i18n( "Task Execution" ); vi.tip = xi18nc( "@info:tooltip", "View task execution information" ); } else if ( type == "TaskWorkPackageView" ) { vi.name = i18n( "Work Package View" ); vi.tip = xi18nc( "@info:tooltip", "View task work package information" ); } else if ( type == "GanttView" ) { vi.name = i18n( "Gantt" ); vi.tip = xi18nc( "@info:tooltip", "View Gantt chart" ); } else if ( type == "MilestoneGanttView" ) { vi.name = i18n( "Milestone Gantt" ); vi.tip = xi18nc( "@info:tooltip", "View milestone Gantt chart" ); } else if ( type == "ResourceAppointmentsView" ) { vi.name = i18n( "Resource Assignments" ); vi.tip = xi18nc( "@info:tooltip", "View resource assignments in a table" ); } else if ( type == "ResourceAppointmentsGanttView" ) { vi.name = i18n( "Resource Assignments (Gantt)" ); vi.tip = xi18nc( "@info:tooltip", "View resource assignments in Gantt chart" ); } else if ( type == "AccountsView" ) { vi.name = i18n( "Cost Breakdown" ); vi.tip = xi18nc( "@info:tooltip", "View planned and actual cost" ); } else if ( type == "PerformanceStatusView" ) { vi.name = i18n( "Tasks Performance Chart" ); vi.tip = xi18nc( "@info:tooltip", "View tasks performance status information" ); } else if ( type == "ReportsGeneratorView" ) { vi.name = i18n( "Reports Generator" ); vi.tip = xi18nc( "@info:tooltip", "Generate reports" ); } else if ( type == "ReportView" ) { vi.name = i18n( "Report" ); vi.tip = xi18nc( "@info:tooltip", "View report" ); } else { warnPlan<<"Unknown viewtype: "<count()-1) : m_visitedViews.at(m_visitedViews.count() - 2); debugPlan<<"Prev:"<setCurrentIndex(view); return; } if ( url.url().startsWith( QLatin1String( "about:plan" ) ) ) { getPart()->aboutPage().generatePage( v->htmlPart(), url ); return; } } if ( url.scheme() == QLatin1String("help") ) { KHelpClient::invokeHelp( "", url.fileName() ); return; } // try to open the url debugPlan<htmlPart().setJScriptEnabled(false); v->htmlPart().setJavaEnabled(false); v->htmlPart().setMetaRefreshEnabled(false); v->htmlPart().setPluginsEnabled(false); slotOpenUrlRequest( v, QUrl( "about:plan/main" ) ); connect( v, &HtmlView::openUrlRequest, this, &View::slotOpenUrlRequest ); m_tab->addWidget( v ); return v; } ViewBase *View::createResourceAppointmentsGanttView( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index ) { ResourceAppointmentsGanttView *v = new ResourceAppointmentsGanttView(getKoPart(), getPart(), m_tab ); m_tab->addWidget( v ); ViewListItem *i = m_viewlist->addView( cat, tag, name, v, getPart(), "", index ); ViewInfo vi = defaultViewInfo( "ResourceAppointmentsGanttView" ); if ( name.isEmpty() ) { i->setText( 0, vi.name ); } if ( tip == TIP_USE_DEFAULT_TEXT ) { i->setToolTip( 0, vi.tip ); } else { i->setToolTip( 0, tip ); } connect( v, &ViewBase::guiActivated, this, &View::slotGuiActivated ); connect( this, &View::currentScheduleManagerChanged, v, &ResourceAppointmentsGanttView::setScheduleManager); connect( v, &ResourceAppointmentsGanttView::requestPopupMenu, this, &View::slotPopupMenuRequested); v->setProject( &( getProject() ) ); v->setScheduleManager( currentScheduleManager() ); v->updateReadWrite( m_readWrite ); return v; } ViewBase *View::createResourceAppointmentsView( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index ) { ResourceAppointmentsView *v = new ResourceAppointmentsView(getKoPart(), getPart(), m_tab ); m_tab->addWidget( v ); ViewListItem *i = m_viewlist->addView( cat, tag, name, v, getPart(), "", index ); ViewInfo vi = defaultViewInfo( "ResourceAppointmentsView" ); if ( name.isEmpty() ) { i->setText( 0, vi.name ); } if ( tip == TIP_USE_DEFAULT_TEXT ) { i->setToolTip( 0, vi.tip ); } else { i->setToolTip( 0, tip ); } connect( v, &ViewBase::guiActivated, this, &View::slotGuiActivated ); connect( this, &View::currentScheduleManagerChanged, v, &ResourceAppointmentsView::setScheduleManager); connect( v, &ResourceAppointmentsView::requestPopupMenu, this, &View::slotPopupMenuRequested); v->setProject( &( getProject() ) ); v->setScheduleManager( currentScheduleManager() ); v->updateReadWrite( m_readWrite ); return v; } ViewBase *View::createResourceEditor( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index ) { ResourceEditor *resourceeditor = new ResourceEditor(getKoPart(), getPart(), m_tab ); resourceeditor->setViewSplitMode(false); m_tab->addWidget( resourceeditor ); resourceeditor->setProject( &(getProject()) ); ViewListItem *i = m_viewlist->addView( cat, tag, name, resourceeditor, getPart(), "", index ); ViewInfo vi = defaultViewInfo( "ResourceEditor" ); if ( name.isEmpty() ) { i->setText( 0, vi.name ); } if ( tip == TIP_USE_DEFAULT_TEXT ) { i->setToolTip( 0, vi.tip ); } else { i->setToolTip( 0, tip ); } connect( resourceeditor, &ViewBase::guiActivated, this, &View::slotGuiActivated ); connect( resourceeditor, &ResourceEditor::deleteObjectList, this, &View::slotDeleteResourceObjects ); connect( resourceeditor, &ResourceEditor::requestPopupMenu, this, &View::slotPopupMenuRequested); resourceeditor->updateReadWrite( m_readWrite ); return resourceeditor; } ViewBase *View::createTaskEditor( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index ) { TaskEditor *taskeditor = new TaskEditor(getKoPart(), getPart(), m_tab ); taskeditor->setViewSplitMode(false); m_tab->addWidget( taskeditor ); ViewListItem *i = m_viewlist->addView( cat, tag, name, taskeditor, getPart(), "", index ); ViewInfo vi = defaultViewInfo( "TaskEditor" ); if ( name.isEmpty() ) { i->setText( 0, vi.name ); } if ( tip == TIP_USE_DEFAULT_TEXT ) { i->setToolTip( 0, vi.tip ); } else { i->setToolTip( 0, tip ); } taskeditor->setProject( &(getProject()) ); taskeditor->setScheduleManager( currentScheduleManager() ); connect( this, &View::currentScheduleManagerChanged, taskeditor, &TaskEditor::setScheduleManager); connect( taskeditor, &ViewBase::guiActivated, this, &View::slotGuiActivated ); connect( taskeditor, &TaskEditor::addTask, this, &View::slotAddTask ); connect( taskeditor, &TaskEditor::addMilestone, this, &View::slotAddMilestone ); connect( taskeditor, &TaskEditor::addSubtask, this, &View::slotAddSubTask ); connect( taskeditor, &TaskEditor::addSubMilestone, this, &View::slotAddSubMilestone ); connect(taskeditor, &TaskEditor::deleteTaskList, this, &View::slotDeleteTaskList); connect( taskeditor, &TaskEditor::moveTaskUp, this, &View::slotMoveTaskUp ); connect( taskeditor, &TaskEditor::moveTaskDown, this, &View::slotMoveTaskDown ); connect( taskeditor, &TaskEditor::indentTask, this, &View::slotIndentTask ); connect( taskeditor, &TaskEditor::unindentTask, this, &View::slotUnindentTask ); connect(taskeditor, &TaskEditor::saveTaskModule, this, &View::saveTaskModule); connect(taskeditor, &TaskEditor::removeTaskModule, this, &View::removeTaskModule); connect(taskeditor, &ViewBase::openDocument, static_cast(m_partpart), &Part::openTaskModule); connect( taskeditor, &TaskEditor::requestPopupMenu, this, &View::slotPopupMenuRequested); connect( taskeditor, &TaskEditor::openTaskDescription, this, &View::slotOpenTaskDescription); taskeditor->updateReadWrite( m_readWrite ); return taskeditor; } ViewBase *View::createAccountsEditor( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index ) { AccountsEditor *ae = new AccountsEditor(getKoPart(), getPart(), m_tab ); m_tab->addWidget( ae ); ViewListItem *i = m_viewlist->addView( cat, tag, name, ae, getPart(), "", index ); ViewInfo vi = defaultViewInfo( "AccountsEditor" ); if ( name.isEmpty() ) { i->setText( 0, vi.name ); } if ( tip == TIP_USE_DEFAULT_TEXT ) { i->setToolTip( 0, vi.tip ); } else { i->setToolTip( 0, tip ); } ae->draw( getProject() ); connect( ae, &ViewBase::guiActivated, this, &View::slotGuiActivated ); ae->updateReadWrite( m_readWrite ); return ae; } ViewBase *View::createCalendarEditor( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index ) { CalendarEditor *calendareditor = new CalendarEditor(getKoPart(), getPart(), m_tab ); m_tab->addWidget( calendareditor ); ViewListItem *i = m_viewlist->addView( cat, tag, name, calendareditor, getPart(), "", index ); ViewInfo vi = defaultViewInfo( "CalendarEditor" ); if ( name.isEmpty() ) { i->setText( 0, vi.name ); } if ( tip == TIP_USE_DEFAULT_TEXT ) { i->setToolTip( 0, vi.tip ); } else { i->setToolTip( 0, tip ); } calendareditor->draw( getProject() ); connect( calendareditor, &ViewBase::guiActivated, this, &View::slotGuiActivated ); connect( calendareditor, &CalendarEditor::requestPopupMenu, this, &View::slotPopupMenuRequested); calendareditor->updateReadWrite( m_readWrite ); return calendareditor; } ViewBase *View::createScheduleHandler( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index ) { ScheduleHandlerView *handler = new ScheduleHandlerView(getKoPart(), getPart(), m_tab ); m_tab->addWidget( handler ); ViewListItem *i = m_viewlist->addView( cat, tag, name, handler, getPart(), "", index ); ViewInfo vi = defaultViewInfo( "ScheduleHandlerView" ); if ( name.isEmpty() ) { i->setText( 0, vi.name ); } if ( tip == TIP_USE_DEFAULT_TEXT ) { i->setToolTip( 0, vi.tip ); } else { i->setToolTip( 0, tip ); } connect( handler->scheduleEditor(), &ScheduleEditor::addScheduleManager, this, &View::slotAddScheduleManager ); connect( handler->scheduleEditor(), &ScheduleEditor::deleteScheduleManager, this, &View::slotDeleteScheduleManager ); connect( handler->scheduleEditor(), &ScheduleEditor::moveScheduleManager, this, &View::slotMoveScheduleManager); connect( handler->scheduleEditor(), &ScheduleEditor::calculateSchedule, this, &View::slotCalculateSchedule ); connect( handler->scheduleEditor(), &ScheduleEditor::baselineSchedule, this, &View::slotBaselineSchedule ); connect( handler, &ViewBase::guiActivated, this, &View::slotGuiActivated ); connect( this, &View::currentScheduleManagerChanged, handler, &ScheduleHandlerView::currentScheduleManagerChanged ); connect( handler, &ScheduleHandlerView::requestPopupMenu, this, &View::slotPopupMenuRequested); connect(handler, &ScheduleHandlerView::editNode, this, &View::slotOpenNode); connect(handler, &ScheduleHandlerView::editResource, this, &View::slotEditResource); handler->draw( getProject() ); handler->updateReadWrite( m_readWrite ); return handler; } ScheduleEditor *View::createScheduleEditor( QWidget *parent ) { ScheduleEditor *scheduleeditor = new ScheduleEditor(getKoPart(), getPart(), parent ); connect( scheduleeditor, &ScheduleEditor::addScheduleManager, this, &View::slotAddScheduleManager ); connect( scheduleeditor, &ScheduleEditor::deleteScheduleManager, this, &View::slotDeleteScheduleManager ); connect( scheduleeditor, &ScheduleEditor::calculateSchedule, this, &View::slotCalculateSchedule ); connect( scheduleeditor, &ScheduleEditor::baselineSchedule, this, &View::slotBaselineSchedule ); scheduleeditor->updateReadWrite( m_readWrite ); return scheduleeditor; } ViewBase *View::createScheduleEditor( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index ) { ScheduleEditor *scheduleeditor = new ScheduleEditor(getKoPart(), getPart(), m_tab ); m_tab->addWidget( scheduleeditor ); ViewListItem *i = m_viewlist->addView( cat, tag, name, scheduleeditor, getPart(), "", index ); ViewInfo vi = defaultViewInfo( "ScheduleEditor" ); if ( name.isEmpty() ) { i->setText( 0, vi.name ); } if ( tip == TIP_USE_DEFAULT_TEXT ) { i->setToolTip( 0, vi.tip ); } else { i->setToolTip( 0, tip ); } scheduleeditor->setProject( &( getProject() ) ); connect( scheduleeditor, &ViewBase::guiActivated, this, &View::slotGuiActivated ); connect( scheduleeditor, &ScheduleEditor::addScheduleManager, this, &View::slotAddScheduleManager ); connect( scheduleeditor, &ScheduleEditor::deleteScheduleManager, this, &View::slotDeleteScheduleManager ); connect( scheduleeditor, &ScheduleEditor::calculateSchedule, this, &View::slotCalculateSchedule ); connect( scheduleeditor, &ScheduleEditor::baselineSchedule, this, &View::slotBaselineSchedule ); scheduleeditor->updateReadWrite( m_readWrite ); return scheduleeditor; } ViewBase *View::createDependencyEditor( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index ) { DependencyEditor *editor = new DependencyEditor(getKoPart(), getPart(), m_tab ); m_tab->addWidget( editor ); ViewListItem *i = m_viewlist->addView( cat, tag, name, editor, getPart(), "", index ); ViewInfo vi = defaultViewInfo( "DependencyEditor" ); if ( name.isEmpty() ) { i->setText( 0, vi.name ); } if ( tip == TIP_USE_DEFAULT_TEXT ) { i->setToolTip( 0, vi.tip ); } else { i->setToolTip( 0, tip ); } editor->draw( getProject() ); connect( editor, &ViewBase::guiActivated, this, &View::slotGuiActivated ); connect( editor, &DependencyEditor::addRelation, this, &View::slotAddRelation); connect( editor, &DependencyEditor::modifyRelation, this, &View::slotModifyRelation); connect( editor, &DependencyEditor::editRelation, this, &View::slotEditRelation); connect( editor, &DependencyEditor::editNode, this, &View::slotOpenNode); connect( editor, &DependencyEditor::addTask, this, &View::slotAddTask ); connect( editor, &DependencyEditor::addMilestone, this, &View::slotAddMilestone ); connect( editor, &DependencyEditor::addSubMilestone, this, &View::slotAddSubMilestone ); connect( editor, &DependencyEditor::addSubtask, this, &View::slotAddSubTask ); connect( editor, &DependencyEditor::deleteTaskList, this, &View::slotDeleteTaskList); connect( this, &View::currentScheduleManagerChanged, editor, &DependencyEditor::setScheduleManager); connect( editor, &DependencyEditor::requestPopupMenu, this, &View::slotPopupMenuRequested); editor->updateReadWrite( m_readWrite ); editor->setScheduleManager( currentScheduleManager() ); return editor; } ViewBase *View::createPertEditor( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index ) { PertEditor *perteditor = new PertEditor(getKoPart(), getPart(), m_tab ); m_tab->addWidget( perteditor ); ViewListItem *i = m_viewlist->addView( cat, tag, name, perteditor, getPart(), "", index ); ViewInfo vi = defaultViewInfo( "PertEditor" ); if ( name.isEmpty() ) { i->setText( 0, vi.name ); } if ( tip == TIP_USE_DEFAULT_TEXT ) { i->setToolTip( 0, vi.tip ); } else { i->setToolTip( 0, tip ); } perteditor->draw( getProject() ); connect( perteditor, &ViewBase::guiActivated, this, &View::slotGuiActivated ); m_updatePertEditor = true; perteditor->updateReadWrite( m_readWrite ); return perteditor; } ViewBase *View::createProjectStatusView( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index ) { ProjectStatusView *v = new ProjectStatusView(getKoPart(), getPart(), m_tab ); m_tab->addWidget( v ); ViewListItem *i = m_viewlist->addView( cat, tag, name, v, getPart(), "", index ); ViewInfo vi = defaultViewInfo( "ProjectStatusView" ); if ( name.isEmpty() ) { i->setText( 0, vi.name ); } if ( tip == TIP_USE_DEFAULT_TEXT ) { i->setToolTip( 0, vi.tip ); } else { i->setToolTip( 0, tip ); } connect( v, &ViewBase::guiActivated, this, &View::slotGuiActivated ); connect( this, &View::currentScheduleManagerChanged, v, &ProjectStatusView::setScheduleManager); v->updateReadWrite( m_readWrite ); v->setProject( &getProject() ); v->setScheduleManager( currentScheduleManager() ); return v; } ViewBase *View::createPerformanceStatusView( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index ) { PerformanceStatusView *v = new PerformanceStatusView(getKoPart(), getPart(), m_tab ); m_tab->addWidget( v ); ViewListItem *i = m_viewlist->addView( cat, tag, name, v, getPart(), "", index ); ViewInfo vi = defaultViewInfo( "PerformanceStatusView" ); if ( name.isEmpty() ) { i->setText( 0, vi.name ); } if ( tip == TIP_USE_DEFAULT_TEXT ) { i->setToolTip( 0, vi.tip ); } else { i->setToolTip( 0, tip ); } connect( v, &ViewBase::guiActivated, this, &View::slotGuiActivated ); connect( this, &View::currentScheduleManagerChanged, v, &PerformanceStatusView::setScheduleManager); connect( v, &PerformanceStatusView::requestPopupMenu, this, &View::slotPopupMenuRequested); v->updateReadWrite( m_readWrite ); v->setProject( &getProject() ); v->setScheduleManager( currentScheduleManager() ); return v; } ViewBase *View::createTaskStatusView( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index ) { TaskStatusView *taskstatusview = new TaskStatusView(getKoPart(), getPart(), m_tab ); taskstatusview->setViewSplitMode(false); m_tab->addWidget( taskstatusview ); ViewListItem *i = m_viewlist->addView( cat, tag, name, taskstatusview, getPart(), "", index ); ViewInfo vi = defaultViewInfo( "TaskStatusView" ); if ( name.isEmpty() ) { i->setText( 0, vi.name ); } if ( tip == TIP_USE_DEFAULT_TEXT ) { i->setToolTip( 0, vi.tip ); } else { i->setToolTip( 0, tip ); } connect( taskstatusview, &ViewBase::guiActivated, this, &View::slotGuiActivated ); connect( this, &View::currentScheduleManagerChanged, taskstatusview, &TaskStatusView::setScheduleManager); connect( taskstatusview, &TaskStatusView::requestPopupMenu, this, &View::slotPopupMenuRequested); connect( taskstatusview, &TaskStatusView::openTaskDescription, this, &View::slotOpenTaskDescription); taskstatusview->updateReadWrite( m_readWrite ); taskstatusview->draw( getProject() ); taskstatusview->setScheduleManager( currentScheduleManager() ); return taskstatusview; } ViewBase *View::createTaskView( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index ) { TaskView *v = new TaskView(getKoPart(), getPart(), m_tab ); v->setViewSplitMode(false); m_tab->addWidget( v ); ViewListItem *i = m_viewlist->addView( cat, tag, name, v, getPart(), "", index ); ViewInfo vi = defaultViewInfo( "TaskView" ); if ( name.isEmpty() ) { i->setText( 0, vi.name ); } if ( tip == TIP_USE_DEFAULT_TEXT ) { i->setToolTip( 0, vi.tip ); } else { i->setToolTip( 0, tip ); } v->draw( getProject() ); v->setScheduleManager( currentScheduleManager() ); connect( this, &View::currentScheduleManagerChanged, v, &TaskView::setScheduleManager); connect( v, &ViewBase::guiActivated, this, &View::slotGuiActivated ); connect( v, &TaskView::requestPopupMenu, this, &View::slotPopupMenuRequested); connect( v, &TaskView::openTaskDescription, this, &View::slotOpenTaskDescription); v->updateReadWrite( m_readWrite ); return v; } ViewBase *View::createTaskWorkPackageView( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index ) { TaskWorkPackageView *v = new TaskWorkPackageView(getKoPart(), getPart(), m_tab ); v->setViewSplitMode(false); m_tab->addWidget( v ); ViewListItem *i = m_viewlist->addView( cat, tag, name, v, getPart(), "", index ); ViewInfo vi = defaultViewInfo( "TaskWorkPackageView" ); if ( name.isEmpty() ) { i->setText( 0, vi.name ); } if ( tip == TIP_USE_DEFAULT_TEXT ) { i->setToolTip( 0, vi.tip ); } else { i->setToolTip( 0, tip ); } v->setProject( &getProject() ); v->setScheduleManager( currentScheduleManager() ); connect( this, &View::currentScheduleManagerChanged, v, &TaskWorkPackageView::setScheduleManager); connect( v, &ViewBase::guiActivated, this, &View::slotGuiActivated ); connect( v, &TaskWorkPackageView::requestPopupMenu, this, &View::slotPopupMenuRequested); connect( v, &TaskWorkPackageView::mailWorkpackage, this, &View::slotMailWorkpackage ); connect( v, &TaskWorkPackageView::publishWorkpackages, this, &View::slotPublishWorkpackages ); connect(v, &TaskWorkPackageView::openWorkpackages, this, &View::openWorkPackageMergeDialog); connect(this, &View::workPackagesAvailable, v, &TaskWorkPackageView::slotWorkpackagesAvailable); connect(v, &TaskWorkPackageView::checkForWorkPackages, getPart(), &MainDocument::checkForWorkPackages); connect(v, &TaskWorkPackageView::loadWorkPackageUrl, this, &View::loadWorkPackage); connect(v, &TaskWorkPackageView::openTaskDescription, this, &View::slotOpenTaskDescription); v->updateReadWrite( m_readWrite ); return v; } ViewBase *View::createGanttView( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index ) { GanttView *ganttview = new GanttView(getKoPart(), getPart(), m_tab, koDocument()->isReadWrite() ); m_tab->addWidget( ganttview ); ViewListItem *i = m_viewlist->addView( cat, tag, name, ganttview, getPart(), "", index ); ViewInfo vi = defaultViewInfo( "GanttView" ); if ( name.isEmpty() ) { i->setText( 0, vi.name ); } if ( tip == TIP_USE_DEFAULT_TEXT ) { i->setToolTip( 0, vi.tip ); } else { i->setToolTip( 0, tip ); } ganttview->setProject( &( getProject() ) ); ganttview->setScheduleManager( currentScheduleManager() ); connect( ganttview, &ViewBase::guiActivated, this, &View::slotGuiActivated ); /* TODO: Review these connect( ganttview, SIGNAL(addRelation(KPlato::Node*,KPlato::Node*,int)), SLOT(slotAddRelation(KPlato::Node*,KPlato::Node*,int)) ); connect( ganttview, SIGNAL(modifyRelation(KPlato::Relation*,int)), SLOT(slotModifyRelation(KPlato::Relation*,int)) ); connect( ganttview, SIGNAL(modifyRelation(KPlato::Relation*)), SLOT(slotModifyRelation(KPlato::Relation*)) ); connect( ganttview, SIGNAL(itemDoubleClicked()), SLOT(slotOpenNode()) ); connect( ganttview, SIGNAL(itemRenamed(KPlato::Node*,QString)), this, SLOT(slotRenameNode(KPlato::Node*,QString)) );*/ connect( this, &View::currentScheduleManagerChanged, ganttview, &GanttView::setScheduleManager); connect( ganttview, &GanttView::requestPopupMenu, this, &View::slotPopupMenuRequested); connect( ganttview, &GanttView::openTaskDescription, this, &View::slotOpenTaskDescription); ganttview->updateReadWrite( m_readWrite ); return ganttview; } ViewBase *View::createMilestoneGanttView( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index ) { MilestoneGanttView *ganttview = new MilestoneGanttView(getKoPart(), getPart(), m_tab, koDocument()->isReadWrite() ); m_tab->addWidget( ganttview ); ViewListItem *i = m_viewlist->addView( cat, tag, name, ganttview, getPart(), "", index ); ViewInfo vi = defaultViewInfo( "MilestoneGanttView" ); if ( name.isEmpty() ) { i->setText( 0, vi.name ); } if ( tip == TIP_USE_DEFAULT_TEXT ) { i->setToolTip( 0, vi.tip ); } else { i->setToolTip( 0, tip ); } ganttview->setProject( &( getProject() ) ); ganttview->setScheduleManager( currentScheduleManager() ); connect( ganttview, &ViewBase::guiActivated, this, &View::slotGuiActivated ); connect( this, &View::currentScheduleManagerChanged, ganttview, &MilestoneGanttView::setScheduleManager); connect( ganttview, &MilestoneGanttView::requestPopupMenu, this, &View::slotPopupMenuRequested); connect( ganttview, &MilestoneGanttView::openTaskDescription, this, &View::slotOpenTaskDescription); ganttview->updateReadWrite( m_readWrite ); return ganttview; } ViewBase *View::createAccountsView( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index ) { AccountsView *accountsview = new AccountsView(getKoPart(), &getProject(), getPart(), m_tab ); m_tab->addWidget( accountsview ); ViewListItem *i = m_viewlist->addView( cat, tag, name, accountsview, getPart(), "", index ); ViewInfo vi = defaultViewInfo( "AccountsView" ); if ( name.isEmpty() ) { i->setText( 0, vi.name ); } if ( tip == TIP_USE_DEFAULT_TEXT ) { i->setToolTip( 0, vi.tip ); } else { i->setToolTip( 0, tip ); } accountsview->setScheduleManager( currentScheduleManager() ); connect( this, &View::currentScheduleManagerChanged, accountsview, &AccountsView::setScheduleManager); connect( accountsview, &ViewBase::guiActivated, this, &View::slotGuiActivated ); accountsview->updateReadWrite( m_readWrite ); return accountsview; } ViewBase *View::createResourceAssignmentView( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index ) { ResourceAssignmentView *resourceAssignmentView = new ResourceAssignmentView(getKoPart(), getPart(), m_tab ); m_tab->addWidget( resourceAssignmentView ); m_updateResourceAssignmentView = true; ViewListItem *i = m_viewlist->addView( cat, tag, name, resourceAssignmentView, getPart(), "", index ); ViewInfo vi = defaultViewInfo( "ResourceAssignmentView" ); if ( name.isEmpty() ) { i->setText( 0, vi.name ); } if ( tip == TIP_USE_DEFAULT_TEXT ) { i->setToolTip( 0, vi.tip ); } else { i->setToolTip( 0, tip ); } resourceAssignmentView->draw( getProject() ); connect( resourceAssignmentView, &ViewBase::guiActivated, this, &View::slotGuiActivated ); connect( resourceAssignmentView, &ResourceAssignmentView::requestPopupMenu, this, &View::slotPopupMenuRequested); resourceAssignmentView->updateReadWrite( m_readWrite ); return resourceAssignmentView; } ViewBase *View::createReportsGeneratorView(ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index) { ReportsGeneratorView *v = new ReportsGeneratorView(getKoPart(), getPart(), m_tab ); m_tab->addWidget( v ); ViewListItem *i = m_viewlist->addView(cat, tag, name, v, getPart(), "", index); ViewInfo vi = defaultViewInfo( "ReportsGeneratorView" ); if ( name.isEmpty() ) { i->setText( 0, vi.name ); } if ( tip == TIP_USE_DEFAULT_TEXT ) { i->setToolTip( 0, vi.tip ); } else { i->setToolTip( 0, tip ); } v->setProject( &getProject() ); connect( this, &View::currentScheduleManagerChanged, v, &ViewBase::setScheduleManager ); connect( this, &View::currentScheduleManagerChanged, v, &ViewBase::slotRefreshView); v->setScheduleManager( currentScheduleManager() ); connect( v, &ViewBase::guiActivated, this, &View::slotGuiActivated ); connect( v, &ReportsGeneratorView::requestPopupMenu, this, &View::slotPopupMenuRequested); v->updateReadWrite( m_readWrite ); return v; } ViewBase *View::createReportView( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index ) { #ifdef PLAN_USE_KREPORT ReportView *v = new ReportView(getKoPart(), getPart(), m_tab ); m_tab->addWidget( v ); ViewListItem *i = m_viewlist->addView( cat, tag, name, v, getPart(), "", index ); ViewInfo vi = defaultViewInfo( "ReportView" ); if ( name.isEmpty() ) { i->setText( 0, vi.name ); } if ( tip == TIP_USE_DEFAULT_TEXT ) { i->setToolTip( 0, vi.tip ); } else { i->setToolTip( 0, tip ); } v->setProject( &getProject() ); connect( this, &View::currentScheduleManagerChanged, v, &ReportView::setScheduleManager); connect( this, &View::currentScheduleManagerChanged, v, SLOT(slotRefreshView())); v->setScheduleManager( currentScheduleManager() ); connect( v, &ReportView::guiActivated, this, &View::slotGuiActivated); v->updateReadWrite( m_readWrite ); return v; #else Q_UNUSED(cat) Q_UNUSED(tag) Q_UNUSED(name) Q_UNUSED(tip) Q_UNUSED(index) return 0; #endif } Project& View::getProject() const { return getPart() ->getProject(); } KoPrintJob * View::createPrintJob() { KoView *v = qobject_cast( canvas() ); if ( v == 0 ) { return 0; } return v->createPrintJob(); } ViewBase *View::currentView() const { return qobject_cast( m_tab->currentWidget() ); } void View::slotEditCut() { ViewBase *v = currentView(); if ( v ) { v->slotEditCut(); } } void View::slotEditCopy() { ViewBase *v = currentView(); if ( v ) { v->slotEditCopy(); } } void View::slotEditPaste() { ViewBase *v = currentView(); if ( v ) { v->slotEditPaste(); } } void View::slotRefreshView() { ViewBase *v = currentView(); if ( v ) { debugPlan<slotRefreshView(); } } void View::slotViewSelector( bool show ) { //debugPlan; m_viewlist->setVisible( show ); } void View::slotInsertResourcesFile(const QString &file, const QUrl &projects) { getPart()->insertResourcesFile(QUrl(file), projects); } void View::slotInsertFile() { InsertFileDialog *dlg = new InsertFileDialog( getProject(), currentTask(), this ); connect(dlg, &QDialog::finished, this, &View::slotInsertFileFinished); dlg->open(); } void View::slotInsertFileFinished( int result ) { InsertFileDialog *dlg = qobject_cast( sender() ); if ( dlg == 0 ) { return; } if ( result == QDialog::Accepted ) { getPart()->insertFile( dlg->url(), dlg->parentNode(), dlg->afterNode() ); } dlg->deleteLater(); } void View::slotLoadSharedProjects() { LoadSharedProjectsDialog *dlg = new LoadSharedProjectsDialog( getProject(), getPart()->url(), this ); connect(dlg, &QDialog::finished, this, &View::slotLoadSharedProjectsFinished); dlg->open(); } void View::slotLoadSharedProjectsFinished( int result ) { LoadSharedProjectsDialog *dlg = qobject_cast( sender() ); if ( dlg == 0 ) { return; } if ( result == QDialog::Accepted ) { getPart()->insertSharedProjects(dlg->urls()); } dlg->deleteLater(); } void View::slotProjectEdit() { slotOpenNode( &getProject() ); } void View::slotProjectWorktime() { StandardWorktimeDialog *dia = new StandardWorktimeDialog( getProject(), this ); connect(dia, &QDialog::finished, this, &View::slotProjectWorktimeFinished); dia->open(); } void View::slotProjectWorktimeFinished( int result ) { StandardWorktimeDialog *dia = qobject_cast( sender() ); if ( dia == 0 ) { return; } if ( result == QDialog::Accepted) { KUndo2Command * cmd = dia->buildCommand(); if ( cmd ) { //debugPlan<<"Modifying calendar(s)"; getPart() ->addCommand( cmd ); //also executes } } dia->deleteLater(); } void View::slotSelectionChanged( ScheduleManager *sm ) { debugPlan<setChecked( true ); // this doesn't trigger QActionGroup slotViewSchedule( a ); } QList View::sortedActionList() { QMap lst; const QMap map = m_scheduleActions; // clazy:exclude=qmap-with-pointer-key QMap::const_iterator it; for (it = map.constBegin(); it != map.constEnd(); ++it) { lst.insert(it.key()->objectName(), it.key()); } return lst.values(); } void View::slotScheduleSwapped(ScheduleManager *from, ScheduleManager *to) { if (currentScheduleManager() == from) { QAction *a = m_scheduleActions.key(to); if (a) { a->setChecked(true); } } } void View::slotScheduleRemoved( const ScheduleManager *sch ) { debugPlan<name(); QAction *a = 0; QAction *checked = m_scheduleActionGroup->checkedAction(); QMapIterator i( m_scheduleActions ); while (i.hasNext()) { i.next(); if ( i.value() == sch ) { a = i.key(); break; } } if ( a ) { unplugActionList( "view_schedule_list" ); delete a; plugActionList( "view_schedule_list", sortedActionList() ); if ( checked && checked != a ) { checked->setChecked( true ); } else if ( ! m_scheduleActions.isEmpty() ) { m_scheduleActions.firstKey()->setChecked( true ); } } slotViewSchedule( m_scheduleActionGroup->checkedAction() ); } void View::slotScheduleAdded( const ScheduleManager *sch ) { ScheduleManager *s = const_cast( sch ); QAction *checked = m_scheduleActionGroup->checkedAction(); unplugActionList( "view_schedule_list" ); QAction *act = addScheduleAction( s ); plugActionList( "view_schedule_list", sortedActionList() ); if (!currentScheduleManager()) { if ( act ) { act->setChecked( true ); } else if ( ! m_scheduleActions.isEmpty() ) { m_scheduleActions.firstKey()->setChecked( true ); } slotViewSchedule( m_scheduleActionGroup->checkedAction() ); } } void View::slotScheduleCalculated(Project *project, ScheduleManager *manager) { Q_UNUSED(project); if (manager == currentScheduleManager()) { slotViewScheduleManager(manager); } } QAction *View::addScheduleAction( ScheduleManager *sch ) { QAction *act = 0; QString n = sch->name(); act = new KToggleAction( n, this); actionCollection()->addAction(n, act ); m_scheduleActions.insert( act, sch ); m_scheduleActionGroup->addAction( act ); //debugPlan<<"Add:"<name(); m_scheduleActions.remove( static_cast( o ) ); } void View::slotPlugScheduleActions() { ScheduleManager *current = currentScheduleManager(); unplugActionList( "view_schedule_list" ); const QMap map = m_scheduleActions; // clazy:exclude=qmap-with-pointer-key QMap::const_iterator it; for (it = map.constBegin(); it != map.constEnd(); ++it) { m_scheduleActionGroup->removeAction(it.key()); delete it.key(); } m_scheduleActions.clear(); QAction *ca = 0; foreach( ScheduleManager *sm, getProject().allScheduleManagers() ) { QAction *act = addScheduleAction(sm); if (sm == current) { ca = act; } } plugActionList( "view_schedule_list", sortedActionList() ); if ( ca == 0 && m_scheduleActionGroup->actions().count() > 0 ) { ca = m_scheduleActionGroup->actions().constFirst(); } if ( ca ) { ca->setChecked( true ); } slotViewSchedule( ca ); } void View::slotCalculateSchedule( Project *project, ScheduleManager *sm ) { if ( project == 0 || sm == 0 ) { return; } if ( sm->parentManager() && ! sm->parentManager()->isScheduled() ) { // the parent must be scheduled return; } CalculateScheduleCmd *cmd = new CalculateScheduleCmd( *project, sm, kundo2_i18nc("@info:status 1=schedule name", "Calculate %1", sm->name() ) ); getPart() ->addCommand( cmd ); slotUpdate(); } void View::slotRemoveCommands() { while ( ! m_undocommands.isEmpty() ) { m_undocommands.last()->undo(); delete m_undocommands.takeLast(); } } void View::slotBaselineSchedule( Project *project, ScheduleManager *sm ) { if ( project == 0 || sm == 0 ) { return; } if ( ! sm->isBaselined() && project->isBaselined() ) { KMessageBox::sorry( this, i18n( "Cannot baseline. The project is already baselined." ) ); return; } MacroCommand *cmd = nullptr; if ( sm->isBaselined() ) { KMessageBox::ButtonCode res = KMessageBox::warningContinueCancel( this, i18n( "This schedule is baselined. Do you want to remove the baseline?" ) ); if ( res == KMessageBox::Cancel ) { return; } cmd = new MacroCommand(kundo2_i18n("Reset baseline %1", sm->name())); cmd->addCommand(new ResetBaselineScheduleCmd(*sm)); } else { cmd = new MacroCommand(kundo2_i18n( "Baseline %1", sm->name() ) ); if (sm->schedulingMode() == ScheduleManager::AutoMode) { cmd->addCommand(new ModifyScheduleManagerSchedulingModeCmd(*sm, ScheduleManager::ManualMode)); } cmd->addCommand( new BaselineScheduleCmd( *sm, kundo2_i18n( "Baseline %1", sm->name() ) )); } getPart() ->addCommand( cmd ); } void View::slotAddScheduleManager( Project *project ) { if ( project == 0 ) { return; } ScheduleManager *sm = project->createScheduleManager(); AddScheduleManagerCmd *cmd = new AddScheduleManagerCmd( *project, sm, -1, kundo2_i18n( "Add schedule %1", sm->name() ) ); getPart() ->addCommand( cmd ); } void View::slotDeleteScheduleManager( Project *project, ScheduleManager *sm ) { if ( project == 0 || sm == 0) { return; } DeleteScheduleManagerCmd *cmd = new DeleteScheduleManagerCmd( *project, sm, kundo2_i18n( "Delete schedule %1", sm->name() ) ); getPart() ->addCommand( cmd ); } void View::slotMoveScheduleManager( ScheduleManager *sm, ScheduleManager *parent, int index ) { if ( sm == 0 ) { return; } MoveScheduleManagerCmd *cmd = new MoveScheduleManagerCmd( sm, parent, index, kundo2_i18n( "Move schedule %1", sm->name() ) ); getPart() ->addCommand( cmd ); } void View::slotAddSubTask() { Task * node = getProject().createTask( getPart() ->config().taskDefaults() ); SubTaskAddDialog *dia = new SubTaskAddDialog( getProject(), *node, currentNode(), getProject().accounts(), this ); connect(dia, &QDialog::finished, this, &View::slotAddSubTaskFinished); dia->open(); } void View::slotAddSubTaskFinished( int result ) { SubTaskAddDialog *dia = qobject_cast( sender() ); if ( dia == 0 ) { return; } if ( result == QDialog::Accepted) { KUndo2Command *m = dia->buildCommand(); getPart() ->addCommand( m ); // add task to project } dia->deleteLater(); } void View::slotAddTask() { Task * node = getProject().createTask( getPart() ->config().taskDefaults() ); TaskAddDialog *dia = new TaskAddDialog( getProject(), *node, currentNode(), getProject().accounts(), this ); connect(dia, &QDialog::finished, this, &View::slotAddTaskFinished); dia->open(); } void View::slotAddTaskFinished( int result ) { TaskAddDialog *dia = qobject_cast( sender() ); if ( dia == 0 ) { return; } if ( result == QDialog::Accepted) { KUndo2Command *m = dia->buildCommand(); getPart() ->addCommand( m ); // add task to project } dia->deleteLater(); } void View::slotAddMilestone() { Task * node = getProject().createTask(); node->estimate() ->clear(); TaskAddDialog *dia = new TaskAddDialog( getProject(), *node, currentNode(), getProject().accounts(), this ); connect(dia, &QDialog::finished, this, &View::slotAddMilestoneFinished); dia->open(); } void View::slotAddMilestoneFinished( int result ) { TaskAddDialog *dia = qobject_cast( sender() ); if ( dia == 0 ) { return; } if ( result == QDialog::Accepted) { MacroCommand *c = new MacroCommand( kundo2_i18n( "Add milestone" ) ); c->addCommand( dia->buildCommand() ); getPart() ->addCommand( c ); // add task to project } dia->deleteLater(); } void View::slotAddSubMilestone() { Task * node = getProject().createTask(); node->estimate() ->clear(); SubTaskAddDialog *dia = new SubTaskAddDialog( getProject(), *node, currentNode(), getProject().accounts(), this ); connect(dia, &QDialog::finished, this, &View::slotAddSubMilestoneFinished); dia->open(); } void View::slotAddSubMilestoneFinished( int result ) { SubTaskAddDialog *dia = qobject_cast( sender() ); if ( dia == 0 ) { return; } if ( result == QDialog::Accepted) { MacroCommand *c = new MacroCommand( kundo2_i18n( "Add sub-milestone" ) ); c->addCommand( dia->buildCommand() ); getPart() ->addCommand( c ); // add task to project } dia->deleteLater(); } void View::slotDefineWBS() { //debugPlan; Project &p = getProject(); WBSDefinitionDialog *dia = new WBSDefinitionDialog( p, p.wbsDefinition(), this ); connect(dia, &QDialog::finished, this, &View::slotDefineWBSFinished); dia->open(); } void View::slotDefineWBSFinished( int result ) { //debugPlan; WBSDefinitionDialog *dia = qobject_cast( sender() ); if ( dia == 0 ) { return; } if ( result == QDialog::Accepted ) { KUndo2Command *cmd = dia->buildCommand(); if ( cmd ) { getPart()->addCommand( cmd ); } } dia->deleteLater(); } void View::slotIntroduction() { m_tab->setCurrentIndex(0); } Calendar *View::currentCalendar() { ViewBase *v = dynamic_cast( m_tab->currentWidget() ); if ( v == 0 ) { return 0; } return v->currentCalendar(); } Node *View::currentNode() const { ViewBase *v = dynamic_cast( m_tab->currentWidget() ); if ( v == 0 ) { return 0; } Node * task = v->currentNode(); if ( 0 != task ) { return task; } return &( getProject() ); } Task *View::currentTask() const { ViewBase *v = dynamic_cast( m_tab->currentWidget() ); if ( v == 0 ) { return 0; } Node * task = v->currentNode(); if ( task ) { return dynamic_cast( task ); } return 0; } Resource *View::currentResource() { ViewBase *v = dynamic_cast( m_tab->currentWidget() ); if ( v == 0 ) { return 0; } return v->currentResource(); } ResourceGroup *View::currentResourceGroup() { ViewBase *v = dynamic_cast( m_tab->currentWidget() ); if ( v == 0 ) { return 0; } return v->currentResourceGroup(); } void View::slotOpenCurrentNode() { //debugPlan; Node * node = currentNode(); slotOpenNode( node ); } void View::slotOpenNode( Node *node ) { //debugPlan; if ( !node ) return ; switch ( node->type() ) { case Node::Type_Project: { Project * project = static_cast( node ); MainProjectDialog *dia = new MainProjectDialog( *project, this ); connect(dia, &MainProjectDialog::dialogFinished, this, &View::slotProjectEditFinished); connect(dia, &MainProjectDialog::sigLoadSharedResources, this, &View::slotInsertResourcesFile); connect(dia, &MainProjectDialog::loadResourceAssignments, getPart(), &MainDocument::loadResourceAssignments); connect(dia, &MainProjectDialog::clearResourceAssignments, getPart(), &MainDocument::clearResourceAssignments); dia->open(); break; } case Node::Type_Subproject: //TODO break; case Node::Type_Task: { Task *task = static_cast( node ); TaskDialog *dia = new TaskDialog( getProject(), *task, getProject().accounts(), this ); connect(dia, &QDialog::finished, this, &View::slotTaskEditFinished); dia->open(); break; } case Node::Type_Milestone: { // Use the normal task dialog for now. // Maybe milestone should have it's own dialog, but we need to be able to // enter a duration in case we accidentally set a tasks duration to zero // and hence, create a milestone Task *task = static_cast( node ); TaskDialog *dia = new TaskDialog( getProject(), *task, getProject().accounts(), this ); connect(dia, &QDialog::finished, this, &View::slotTaskEditFinished); dia->open(); break; } case Node::Type_Summarytask: { Task *task = dynamic_cast( node ); Q_ASSERT( task ); SummaryTaskDialog *dia = new SummaryTaskDialog( *task, this ); connect(dia, &QDialog::finished, this, &View::slotSummaryTaskEditFinished); dia->open(); break; } default: break; // avoid warnings } } void View::slotProjectEditFinished( int result ) { MainProjectDialog *dia = qobject_cast( sender() ); if ( dia == 0 ) { return; } if ( result == QDialog::Accepted) { KUndo2Command * cmd = dia->buildCommand(); if ( cmd ) { getPart() ->addCommand( cmd ); } } dia->deleteLater(); } void View::slotTaskEditFinished( int result ) { TaskDialog *dia = qobject_cast( sender() ); if ( dia == 0 ) { return; } if ( result == QDialog::Accepted) { KUndo2Command * cmd = dia->buildCommand(); if ( cmd ) { getPart() ->addCommand( cmd ); } } dia->deleteLater(); } void View::slotSummaryTaskEditFinished( int result ) { SummaryTaskDialog *dia = qobject_cast( sender() ); if ( dia == 0 ) { return; } if ( result == QDialog::Accepted) { KUndo2Command * cmd = dia->buildCommand(); if ( cmd ) { getPart() ->addCommand( cmd ); } } dia->deleteLater(); } ScheduleManager *View::currentScheduleManager() const { return m_scheduleActions.value( m_scheduleActionGroup->checkedAction() ); } long View::activeScheduleId() const { ScheduleManager *s = m_scheduleActions.value( m_scheduleActionGroup->checkedAction() ); return s == nullptr || s->expected() == nullptr ? -1 : s->expected()->id(); } void View::setActiveSchedule( long id ) { if ( id != -1 ) { QMap::const_iterator it = m_scheduleActions.constBegin(); for (; it != m_scheduleActions.constEnd(); ++it ) { int mid = it.value()->expected() == nullptr ? -1 : it.value()->expected()->id(); if (mid == id) { it.key()->setChecked( true ); slotViewSchedule( it.key() ); // signal not emitted from group, so trigger it here break; } } } } void View::slotTaskProgress() { //debugPlan; Node * node = currentNode(); if ( !node ) return ; switch ( node->type() ) { case Node::Type_Project: { break; } case Node::Type_Subproject: //TODO break; case Node::Type_Task: { Task *task = dynamic_cast( node ); Q_ASSERT( task ); TaskProgressDialog *dia = new TaskProgressDialog( *task, currentScheduleManager(), getProject().standardWorktime(), this ); connect(dia, &QDialog::finished, this, &View::slotTaskProgressFinished); dia->open(); break; } case Node::Type_Milestone: { Task *task = dynamic_cast( node ); Q_ASSERT( task ); MilestoneProgressDialog *dia = new MilestoneProgressDialog( *task, this ); connect(dia, &QDialog::finished, this, &View::slotMilestoneProgressFinished); dia->open(); break; } case Node::Type_Summarytask: { // TODO break; } default: break; // avoid warnings } } void View::slotTaskProgressFinished( int result ) { TaskProgressDialog *dia = qobject_cast(sender() ); if ( dia == 0 ) { return; } if ( result == QDialog::Accepted) { KUndo2Command * m = dia->buildCommand(); if ( m ) { getPart() ->addCommand( m ); } } dia->deleteLater(); } void View::slotMilestoneProgressFinished( int result ) { MilestoneProgressDialog *dia = qobject_cast(sender() ); if ( dia == 0 ) { return; } if ( result == QDialog::Accepted) { KUndo2Command * m = dia->buildCommand(); if ( m ) { getPart() ->addCommand( m ); } } dia->deleteLater(); } void View::slotOpenProjectDescription() { debugPlan<isReadWrite(); TaskDescriptionDialog *dia = new TaskDescriptionDialog(getProject(), this, !koDocument()->isReadWrite()); connect(dia, &QDialog::finished, this, &View::slotTaskDescriptionFinished); dia->open(); } void View::slotTaskDescription() { slotOpenTaskDescription(!koDocument()->isReadWrite()); } void View::slotOpenTaskDescription(bool ro) { //debugPlan; Node * node = currentNode(); if ( !node ) return ; switch ( node->type() ) { case Node::Type_Subproject: //TODO break; case Node::Type_Project: case Node::Type_Task: case Node::Type_Milestone: case Node::Type_Summarytask: { TaskDescriptionDialog *dia = new TaskDescriptionDialog( *node, this, ro ); connect(dia, &QDialog::finished, this, &View::slotTaskDescriptionFinished); dia->open(); break; } default: break; // avoid warnings } } void View::slotTaskDescriptionFinished( int result ) { TaskDescriptionDialog *dia = qobject_cast(sender() ); if ( dia == 0 ) { return; } if ( result == QDialog::Accepted) { KUndo2Command * m = dia->buildCommand(); if ( m ) { getPart() ->addCommand( m ); } } dia->deleteLater(); } void View::slotDocuments() { //debugPlan; Node * node = currentNode(); if ( !node ) { return ; } switch ( node->type() ) { case Node::Type_Subproject: //TODO break; case Node::Type_Project: case Node::Type_Summarytask: case Node::Type_Task: case Node::Type_Milestone: { DocumentsDialog *dia = new DocumentsDialog(*node, this); connect(dia, &QDialog::finished, this, &View::slotDocumentsFinished); dia->open(); break; } default: break; // avoid warnings } } void View::slotDocumentsFinished( int result ) { DocumentsDialog *dia = qobject_cast(sender() ); if ( dia == 0 ) { return; } if ( result == QDialog::Accepted) { KUndo2Command * m = dia->buildCommand(); if ( m ) { getPart()->addCommand( m ); } } dia->deleteLater(); } void View::slotDeleteTaskList( QList lst ) { //debugPlan; foreach ( Node *n, lst ) { if ( n->isScheduled() ) { KMessageBox::ButtonCode res = KMessageBox::warningContinueCancel( this, i18n( "A task that has been scheduled will be deleted. This will invalidate the schedule." ) ); if ( res == KMessageBox::Cancel ) { return; } break; } } if ( lst.count() == 1 ) { getPart()->addCommand( new NodeDeleteCmd( lst.takeFirst(), kundo2_i18n( "Delete task" ) ) ); return; } int num = 0; MacroCommand *cmd = new MacroCommand( kundo2_i18np( "Delete task", "Delete tasks", lst.count() ) ); while ( !lst.isEmpty() ) { Node *node = lst.takeFirst(); if ( node == 0 || node->parentNode() == 0 ) { debugPlan << ( node ?"Task is main project" :"No current task" ); continue; } bool del = true; foreach ( Node *n, lst ) { if ( node->isChildOf( n ) ) { del = false; // node is going to be deleted when we delete n break; } } if ( del ) { //debugPlan<name(); cmd->addCommand( new NodeDeleteCmd( node, kundo2_i18n( "Delete task" ) ) ); num++; } } if ( num > 0 ) { getPart()->addCommand( cmd ); } else { delete cmd; } } void View::slotDeleteTask( Node *node ) { //debugPlan; if ( node == 0 || node->parentNode() == 0 ) { debugPlan << ( node ?"Task is main project" :"No current task" ); return ; } if ( node->isScheduled() ) { KMessageBox::ButtonCode res = KMessageBox::warningContinueCancel( this, i18n( "This task has been scheduled. This will invalidate the schedule." ) ); if ( res == KMessageBox::Cancel ) { return; } } NodeDeleteCmd *cmd = new NodeDeleteCmd( node, kundo2_i18n( "Delete task" ) ); getPart() ->addCommand( cmd ); } void View::slotDeleteCurrentTask() { //debugPlan; return slotDeleteTask( currentNode() ); } void View::slotIndentTask() { //debugPlan; Node * node = currentNode(); if ( node == 0 || node->parentNode() == 0 ) { debugPlan << ( node ?"Task is main project" :"No current task" ); return ; } if ( getProject().canIndentTask( node ) ) { NodeIndentCmd * cmd = new NodeIndentCmd( *node, kundo2_i18n( "Indent task" ) ); getPart() ->addCommand( cmd ); } } void View::slotUnindentTask() { //debugPlan; Node * node = currentNode(); if ( node == 0 || node->parentNode() == 0 ) { debugPlan << ( node ?"Task is main project" :"No current task" ); return ; } if ( getProject().canUnindentTask( node ) ) { NodeUnindentCmd * cmd = new NodeUnindentCmd( *node, kundo2_i18n( "Unindent task" ) ); getPart() ->addCommand( cmd ); } } void View::slotMoveTaskUp() { //debugPlan; Node * task = currentNode(); if ( 0 == task ) { // is always != 0. At least we would get the Project, but you never know who might change that // so better be careful errorPlan << "No current task" << endl; return ; } if ( Node::Type_Project == task->type() ) { debugPlan <<"The root node cannot be moved up"; return ; } if ( getProject().canMoveTaskUp( task ) ) { NodeMoveUpCmd * cmd = new NodeMoveUpCmd( *task, kundo2_i18n( "Move task up" ) ); getPart() ->addCommand( cmd ); } } void View::slotMoveTaskDown() { //debugPlan; Node * task = currentNode(); if ( 0 == task ) { // is always != 0. At least we would get the Project, but you never know who might change that // so better be careful return ; } if ( Node::Type_Project == task->type() ) { debugPlan <<"The root node cannot be moved down"; return ; } if ( getProject().canMoveTaskDown( task ) ) { NodeMoveDownCmd * cmd = new NodeMoveDownCmd( *task, kundo2_i18n( "Move task down" ) ); getPart() ->addCommand( cmd ); } } void View::openRelationDialog( Node *par, Node *child ) { //debugPlan; Relation * rel = new Relation( par, child ); AddRelationDialog *dia = new AddRelationDialog( getProject(), rel, this ); connect(dia, &QDialog::finished, this, &View::slotAddRelationFinished); dia->open(); } void View::slotAddRelationFinished( int result ) { AddRelationDialog *dia = qobject_cast(sender() ); if ( dia == 0 ) { return; } if ( result == QDialog::Accepted) { KUndo2Command * m = dia->buildCommand(); if ( m ) { getPart() ->addCommand( m ); } } dia->deleteLater(); } void View::slotAddRelation( Node *par, Node *child, int linkType ) { //debugPlan; if ( linkType == Relation::FinishStart || linkType == Relation::StartStart || linkType == Relation::FinishFinish ) { Relation * rel = new Relation( par, child, static_cast( linkType ) ); getPart() ->addCommand( new AddRelationCmd( getProject(), rel, kundo2_i18n( "Add task dependency" ) ) ); } else { openRelationDialog( par, child ); } } void View::slotEditRelation( Relation *rel ) { //debugPlan; ModifyRelationDialog *dia = new ModifyRelationDialog( getProject(), rel, this ); connect(dia, &QDialog::finished, this, &View::slotModifyRelationFinished); dia->open(); } void View::slotModifyRelationFinished( int result ) { ModifyRelationDialog *dia = qobject_cast( sender() ); if ( dia == 0 ) { return ; } if ( result == QDialog::Accepted) { KUndo2Command *cmd = dia->buildCommand(); if ( cmd ) { getPart() ->addCommand( cmd ); } } dia->deleteLater(); } void View::slotModifyRelation( Relation *rel, int linkType ) { //debugPlan; if ( linkType == Relation::FinishStart || linkType == Relation::StartStart || linkType == Relation::FinishFinish ) { getPart() ->addCommand( new ModifyRelationTypeCmd( rel, static_cast( linkType ) ) ); } else { slotEditRelation( rel ); } } void View::slotModifyCurrentRelation() { ViewBase *v = dynamic_cast( m_tab->currentWidget() ); if ( v == 0 ) { return; } Relation *rel = v->currentRelation(); if ( rel ) { slotEditRelation( rel ); } } void View::slotDeleteRelation() { ViewBase *v = dynamic_cast( m_tab->currentWidget() ); if ( v == 0 ) { return; } Relation *rel = v->currentRelation(); if ( rel ) { getPart()->addCommand( new DeleteRelationCmd( getProject(), rel, kundo2_i18n( "Delete task dependency" ) ) ); } } void View::slotEditCurrentResource() { //debugPlan; slotEditResource( currentResource() ); } void View::slotEditResource( Resource *resource ) { if ( resource == 0 ) { return ; } ResourceDialog *dia = new ResourceDialog( getProject(), resource, this ); connect(dia, &QDialog::finished, this, &View::slotEditResourceFinished); dia->open(); } void View::slotEditResourceFinished( int result ) { //debugPlan; ResourceDialog *dia = qobject_cast( sender() ); if ( dia == 0 ) { return ; } if ( result == QDialog::Accepted) { KUndo2Command * cmd = dia->buildCommand(); if ( cmd ) getPart() ->addCommand( cmd ); } dia->deleteLater(); } void View::slotDeleteResource( Resource *resource ) { getPart()->addCommand( new RemoveResourceCmd( resource->parentGroup(), resource, kundo2_i18n( "Delete resource" ) ) ); } void View::slotDeleteResourceGroup( ResourceGroup *group ) { getPart()->addCommand( new RemoveResourceGroupCmd( group->project(), group, kundo2_i18n( "Delete resourcegroup" ) ) ); } void View::slotDeleteResourceObjects( QObjectList lst ) { //debugPlan; foreach ( QObject *o, lst ) { Resource *r = qobject_cast( o ); if ( r && r->isScheduled() ) { KMessageBox::ButtonCode res = KMessageBox::warningContinueCancel( this, i18n( "A resource that has been scheduled will be deleted. This will invalidate the schedule." ) ); if ( res == KMessageBox::Cancel ) { return; } break; } ResourceGroup *g = qobject_cast( o ); if ( g && g->isScheduled() ) { KMessageBox::ButtonCode res = KMessageBox::warningContinueCancel( this, i18n( "A resource that has been scheduled will be deleted. This will invalidate the schedule." ) ); if ( res == KMessageBox::Cancel ) { return; } break; } } if ( lst.count() == 1 ) { Resource *r = qobject_cast( lst.first() ); if ( r ) { slotDeleteResource( r ); } else { ResourceGroup *g = qobject_cast( lst.first() ); if ( g ) { slotDeleteResourceGroup( g ); } } return; } // int num = 0; MacroCommand *cmd = 0, *rc = 0, *gc = 0; foreach ( QObject *o, lst ) { Resource *r = qobject_cast( o ); if ( r ) { if ( rc == 0 ) rc = new MacroCommand( KUndo2MagicString() ); rc->addCommand( new RemoveResourceCmd( r->parentGroup(), r ) ); continue; } ResourceGroup *g = qobject_cast( o ); if ( g ) { if ( gc == 0 ) gc = new MacroCommand( KUndo2MagicString() ); gc->addCommand( new RemoveResourceGroupCmd( g->project(), g ) ); } } if ( rc || gc ) { KUndo2MagicString s; if ( rc && gc ) { s = kundo2_i18n( "Delete resourcegroups and resources" ); } else if ( rc ) { s = kundo2_i18np( "Delete resource", "Delete resources", lst.count() ); } else { s = kundo2_i18np( "Delete resourcegroup", "Delete resourcegroups", lst.count() ); } cmd = new MacroCommand( s ); } if ( rc ) cmd->addCommand( rc ); if ( gc ) cmd->addCommand( gc ); if ( cmd ) getPart()->addCommand( cmd ); } void View::updateReadWrite( bool readwrite ) { m_readWrite = readwrite; m_viewlist->setReadWrite( readwrite ); } MainDocument *View::getPart() const { return ( MainDocument * ) koDocument(); } KoPart *View::getKoPart() const { return m_partpart; } void View::slotConnectNode() { //debugPlan; /* NodeItem *curr = ganttview->currentItem(); if (curr) { debugPlan<<"node="<getNode().name(); }*/ } QMenu * View::popupMenu( const QString& name ) { //debugPlan; if ( factory() ) { return ( ( QMenu* ) factory() ->container( name, this ) ); } debugPlan<<"No factory"; return 0L; } void View::slotUpdate() { //debugPlan<<"calculate="<currentWidget() ); } void View::slotGuiActivated( ViewBase *view, bool activate ) { if ( activate ) { foreach ( DockWidget *ds, view->dockers() ) { m_dockers.append( ds ); ds->activate( mainWindow() ); } if (!m_dockers.isEmpty()) {debugPlan<<"Added dockers:"<deactivate( mainWindow() ); } } } void View::guiActivateEvent( bool activated ) { if ( activated ) { // plug my own actionlists, they may be gone slotPlugScheduleActions(); } // propagate to sub-view ViewBase *v = dynamic_cast( m_tab->currentWidget() ); if ( v ) { v->setGuiActive( activated ); } } void View::slotViewListItemRemoved( ViewListItem *item ) { getPart()->removeViewListItem( this, item ); } void View::removeViewListItem( const ViewListItem *item ) { if ( item == 0 ) { return; } ViewListItem *itm = m_viewlist->findItem( item->tag() ); if ( itm == 0 ) { return; } m_viewlist->removeViewListItem( itm ); return; } void View::slotViewListItemInserted( ViewListItem *item, ViewListItem *parent, int index ) { getPart()->insertViewListItem( this, item, parent, index ); } void View::addViewListItem( const ViewListItem *item, const ViewListItem *parent, int index ) { if ( item == 0 ) { return; } if ( parent == 0 ) { if ( item->type() != ViewListItem::ItemType_Category ) { return; } m_viewlist->blockSignals( true ); ViewListItem *cat = m_viewlist->addCategory( item->tag(), item->text( 0 ) ); cat->setToolTip( 0, item->toolTip( 0 ) ); m_viewlist->blockSignals( false ); return; } ViewListItem *cat = m_viewlist->findCategory( parent->tag() ); if ( cat == 0 ) { return; } m_viewlist->blockSignals( true ); createView( cat, item->viewType(), item->tag(), item->text( 0 ), item->toolTip( 0 ), index ); m_viewlist->blockSignals( false ); } void View::createReportView(const QDomDocument &doc) { #ifdef PLAN_USE_KREPORT QPointer vd = new ViewListReportsDialog( this, *m_viewlist, doc, this ); vd->exec(); // FIXME make non-crash delete vd; #else Q_UNUSED(doc) #endif } void View::slotOpenReportFile() { #ifdef PLAN_USE_KREPORT QFileDialog *dlg = new QFileDialog(this); connect(dlg, &QDialog::finished, &View::slotOpenReportFileFinished(int))); dlg->open(); #endif } void View::slotOpenReportFileFinished( int result ) { #ifdef PLAN_USE_KREPORT QFileDialog *fdlg = qobject_cast( sender() ); if ( fdlg == 0 || result != QDialog::Accepted ) { return; } QString fn = fdlg->selectedFiles().value(0); if ( fn.isEmpty() ) { return; } QFile file( fn ); if ( ! file.open( QIODevice::ReadOnly | QIODevice::Text ) ) { KMessageBox::sorry( this, xi18nc( "@info", "Cannot open file:
%1", fn ) ); return; } QDomDocument doc; doc.setContent( &file ); createReportView(doc); #else Q_UNUSED(result) #endif } void View::slotReportDesignFinished( int /*result */) { #ifdef PLAN_USE_KREPORT if ( sender() ) { sender()->deleteLater(); } #endif } void View::slotCreateView() { ViewListDialog *dlg = new ViewListDialog( this, *m_viewlist, this ); connect(dlg, &QDialog::finished, this, &View::slotCreateViewFinished); dlg->open(); } void View::slotCreateViewFinished( int ) { if ( sender() ) { sender()->deleteLater(); } } void View::slotViewActivated( ViewListItem *item, ViewListItem *prev ) { QApplication::setOverrideCursor( Qt::WaitCursor ); if ( prev && prev->type() == ViewListItem::ItemType_Category && m_viewlist->previousViewItem() ) { // A view is shown anyway... ViewBase *v = qobject_cast( m_viewlist->previousViewItem()->view() ); if ( v ) { factory()->removeClient(v); v->setGuiActive( false ); } } else if ( prev && prev->type() == ViewListItem::ItemType_SubView ) { ViewBase *v = qobject_cast( prev->view() ); if ( v ) { factory()->removeClient(v); v->setGuiActive( false ); } } if ( item && item->type() == ViewListItem::ItemType_SubView ) { //debugPlan<<"Activate:"<setCurrentWidget( item->view() ); // Add sub-view specific gui ViewBase *v = dynamic_cast( m_tab->currentWidget() ); if ( v ) { factory()->addClient(v); v->setGuiActive( true ); } } QApplication::restoreOverrideCursor(); } QWidget *View::canvas() const { return m_tab->currentWidget();//KoView::canvas(); } KoPageLayout View::pageLayout() const { return currentView()->pageLayout(); } void View::setPageLayout(const KoPageLayout &pageLayout) { currentView()->setPageLayout(pageLayout); } QPrintDialog *View::createPrintDialog( KoPrintJob *printJob, QWidget *parent ) { debugPlan<( printJob ); if ( ! job ) { return 0; } QPrintDialog *dia = KoView::createPrintDialog( job, parent ); PrintingDialog *j = dynamic_cast( job ); if ( j ) { new PrintingControlPrivate( j, dia ); } return dia; } void View::slotCurrentChanged( int view ) { m_visitedViews << view; ViewListItem *item = m_viewlist->findItem( qobject_cast( m_tab->currentWidget() ) ); m_viewlist->setCurrentItem( item ); } void View::slotSelectDefaultView() { m_tab->setCurrentIndex(qMin(m_defaultView, m_tab->count()-1)); } void View::updateView( QWidget * ) { QApplication::setOverrideCursor( Qt::WaitCursor ); //setScheduleActionsEnabled(); QWidget *widget2; widget2 = m_viewlist->findView( "ResourceAssignmentView" ); if ( widget2 && m_updateResourceAssignmentView ) static_cast( widget2 ) ->draw( getProject() ); m_updateResourceAssignmentView = false; QApplication::restoreOverrideCursor(); } void View::slotRenameNode( Node *node, const QString& name ) { //debugPlan<type() ) { case Node::Type_Task: s = kundo2_i18n( "Modify task name" ); break; case Node::Type_Milestone: s = kundo2_i18n( "Modify milestone name" ); break; case Node::Type_Summarytask: s = kundo2_i18n( "Modify summarytask name" ); break; case Node::Type_Project: s = kundo2_i18n( "Modify project name" ); break; } NodeModifyNameCmd * cmd = new NodeModifyNameCmd( *node, name, s ); getPart() ->addCommand( cmd ); } } void View::slotPopupMenuRequested( const QString& menuname, const QPoint & pos ) { QMenu * menu = this->popupMenu( menuname ); if ( menu ) { //debugPlan<actions().count(); ViewBase *v = qobject_cast( m_tab->currentWidget() ); //debugPlan< lst; if ( v ) { lst = v->contextActionList(); debugPlan<addSeparator(); foreach ( QAction *a, lst ) { menu->addAction( a ); } } } menu->exec( pos ); foreach ( QAction *a, lst ) { menu->removeAction( a ); } } } void View::slotPopupMenu( const QString& menuname, const QPoint &pos, ViewListItem *item ) { //debugPlan<context(); if ( ctx == 0 || ! ctx->isLoaded() ) { return false; } KoXmlElement n = ctx->context(); QString cv = n.attribute( "current-view" ); if ( ! cv.isEmpty() ) { m_viewlist->setSelected( m_viewlist->findItem( cv ) ); } else debugPlan<<"No current view"; long id = n.attribute( "current-schedule", "-1" ).toLong(); if ( id != -1 ) { setActiveSchedule( id ); } else debugPlan<<"No current schedule"; return true; } void View::saveContext( QDomElement &me ) const { //debugPlan; long id = activeScheduleId(); if ( id != -1 ) { me.setAttribute( "current-schedule", QString::number((qlonglong)id) ); } ViewListItem *item = m_viewlist->findItem( qobject_cast( m_tab->currentWidget() ) ); if ( item ) { me.setAttribute("current-view", item->tag() ); } m_viewlist->save( me ); } void View::loadWorkPackage(Project *project, const QList &urls) { bool loaded = false; for (const QUrl &url : urls) { loaded |= getPart()->loadWorkPackage(*project, url); } if (loaded) { slotWorkPackageLoaded(); } } void View::setLabel( ScheduleManager *sm ) { //debugPlan; Schedule *s = sm == 0 ? 0 : sm->expected(); if ( s && !s->isDeleted() && s->isScheduled() ) { m_estlabel->setText( sm->name() ); return; } m_estlabel->setText( xi18nc( "@info:status", "Not scheduled" ) ); } void View::slotWorkPackageLoaded() { debugPlan<workPackages(); addStatusBarItem(m_workPackageButton, 0, true); emit workPackagesAvailable(true); } void View::openWorkPackageMergeDialog() { WorkPackageMergeDialog *dlg = new WorkPackageMergeDialog(&getProject(), getPart()->workPackages(), this); connect(dlg, &QDialog::finished, this, &View::workPackageMergeDialogFinished); connect(dlg, SIGNAL(terminateWorkPackage(const KPlato::Package*)), getPart(), SLOT(terminateWorkPackage(const KPlato::Package*))); connect(dlg, &WorkPackageMergeDialog::executeCommand, koDocument(), &KoDocument::addCommand); dlg->open(); removeStatusBarItem(m_workPackageButton); emit workPackagesAvailable(false); } void View::workPackageMergeDialogFinished( int result ) { debugPlanWp<<"result:"<( sender() ); Q_ASSERT(dlg); if (!getPart()->workPackages().isEmpty()) { slotWorkPackageLoaded(); } if (dlg) { dlg->deleteLater(); } } void View::slotMailWorkpackage( Node *node, Resource *resource ) { debugPlan; QTemporaryFile tmpfile(QDir::tempPath() + QLatin1String("/calligraplanwork_XXXXXX") + QLatin1String( ".planwork" )); tmpfile.setAutoRemove( false ); if ( ! tmpfile.open() ) { debugPlan<<"Failed to open file"; KMessageBox::error(0, i18n("Failed to open temporary file" ) ); return; } QUrl url = QUrl::fromLocalFile( tmpfile.fileName() ); if ( ! getPart()->saveWorkPackageUrl( url, node, activeScheduleId(), resource ) ) { debugPlan<<"Failed to save to file"; KMessageBox::error(0, xi18nc( "@info", "Failed to save to temporary file:
%1", url.url() ) ); return; } QStringList attachURLs; attachURLs << url.url(); QString to = resource == 0 ? node->leader() : ( resource->name() + " <" + resource->email() + '>' ); QString cc; QString bcc; QString subject = i18n( "Work Package: %1", node->name() ); QString body = i18nc( "1=project name, 2=task name", "%1\n%2", getProject().name(), node->name() ); QString messageFile; KToolInvocation::invokeMailer( to, cc, bcc, subject, body, messageFile, attachURLs ); } void View::slotPublishWorkpackages( const QList &nodes, Resource *resource, bool mailTo ) { debugPlanWp<leader() yet"; return; } bool mail = mailTo; QString body; QStringList attachURLs; QString path; if (getProject().workPackageInfo().publishUrl.isValid()) { path = getProject().workPackageInfo().publishUrl.path(); debugPlanWp<<"publish:"<saveWorkPackageUrl( url, n, activeScheduleId(), resource ) ) { debugPlan<<"Failed to save to file"; KMessageBox::error(0, xi18nc( "@info", "Failed to save to temporary file:
%1", url.url() ) ); return; } attachURLs << url.url(); body += n->name() + '\n'; } if (mail) { debugPlanWp<name() + " <" + resource->email() + '>'; QString subject = i18n( "Work Package for project: %1", getProject().name() ); QString cc; QString bcc; QString messageFile; KToolInvocation::invokeMailer( to, cc, bcc, subject, body, messageFile, attachURLs ); } } void View::slotCurrencyConfig() { LocaleConfigMoneyDialog *dlg = new LocaleConfigMoneyDialog( getProject().locale(), this ); connect(dlg, &QDialog::finished, this, &View::slotCurrencyConfigFinished); dlg->open(); } void View::slotCurrencyConfigFinished( int result ) { LocaleConfigMoneyDialog *dlg = qobject_cast( sender() ); if ( dlg == 0 ) { return; } if ( result == QDialog::Accepted ) { KUndo2Command *c = dlg->buildCommand( getProject() ); if ( c ) { getPart()->addCommand( c ); } } dlg->deleteLater(); } void View::saveTaskModule( const QUrl &url, Project *project ) { - qInfo()<" "" "" "%1" "" "" "predefined" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "") .arg( i18n( "Report" ), i18nc( "Project manager", "Manager:" ), i18n( "Project:" ), i18n( "Task Status Report" ), i18nc( "As in: Page 1 of 2", "of" ), i18n( "Page" ), i18nc( "Task name", "Name" ), i18nc( "Task completion", "Completion (%)" ) ); #endif return s; } diff --git a/src/libs/store/KoXmlReader.cpp b/src/libs/store/KoXmlReader.cpp index 85f9a29f..f380a10f 100644 --- a/src/libs/store/KoXmlReader.cpp +++ b/src/libs/store/KoXmlReader.cpp @@ -1,2362 +1,2376 @@ /* This file is part of the KDE project Copyright (C) 2005-2006 Ariya Hidayat 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 "KoXmlReader.h" #include "KoXmlNS.h" /* This is a memory-efficient DOM implementation for Calligra. See the API documentation for details. IMPORTANT ! * When you change this stuff, make sure it DOES NOT BREAK the test suite. Build tests/koxmlreadertest.cpp and verify it. Many sleepless nights have been sacrificed for this piece of code, do not let those precious hours wasted! * Run koxmlreadertest.cpp WITH Valgrind and make sure NO illegal memory read/write and any type of leak occurs. If you are not familiar with Valgrind then RTFM first and come back again later on. * The public API shall remain as compatible as QDom. * All QDom-compatible methods should behave the same. All QDom-compatible functions should return the same result. In case of doubt, run koxmlreadertest.cpp but uncomment KOXML_USE_QDOM in koxmlreader.h so that the tests are performed with standard QDom. Some differences compared to QDom: - DOM tree in KoXmlDocument is read-only, you can not modify it. This is sufficient for Calligra since the tree is only accessed when loading a document to the application. For saving the document to XML file, use KoXmlWriter. - Because the dynamic loading and unloading, you have to use the nodes (and therefore also elements) carefully since the whole API (just like QDom) is reference-based, not pointer-based. If the parent node is unloaded from memory, the reference is not valid anymore and may give unpredictable result. The easiest way: use the node/element in very short time only. - Comment node (like QDomComment) is not implemented as comments are simply ignored. - DTD, entity and entity reference are not handled. Thus, the associated nodes (like QDomDocumentType, QDomEntity, QDomEntityReference) are also not implemented. - Attribute mapping node is not implemented. But of course, functions to query attributes of an element are available. */ #include #include #ifndef KOXML_USE_QDOM #include #include #include #include #include #include #include #include #include #include #include /* Use more compact representation of in-memory nodes. Advantages: faster iteration, can facilitate real-time compression. Disadvantages: still buggy, eat slightly more memory. */ #define KOXML_COMPACT /* Use real-time compression. Only works in conjunction with KOXML_COMPACT above because otherwise the non-compact layout will slow down everything. */ #define KOXML_COMPRESS // prevent mistake, see above #ifdef KOXML_COMPRESS #ifndef KOXML_COMPACT #error Please enable also KOXML_COMPACT #endif #endif // this is used to quickly get namespaced attribute(s) typedef QPair KoXmlStringPair; class KoQName { public: QString nsURI; QString name; explicit KoQName(const QString& nsURI_, const QString& name_) : nsURI(nsURI_), name(name_) {} bool operator==(const KoQName& qname) const { // local name is more likely to differ, so compare that first return name == qname.name && nsURI == qname.nsURI; } }; uint qHash(const KoQName& qname) { // possibly add a faster hash function that only includes some trailing // part of the nsURI // in case of doubt, use this: // return qHash(qname.nsURI)^qHash(qname.name); return qHash(qname.nsURI)^qHash(qname.name); } // this simplistic hash is rather fast-and-furious. it works because // likely there is very few namespaced attributes per element static inline uint qHash(const KoXmlStringPair &p, uint /*seed*/ = 0) { return qHash(p.second[0].unicode()) ^ 0x1477; // in case of doubt, use this: // return qHash(p.first)^qHash(p.second); } static inline bool operator==(const KoXmlStringPair &a, const KoXmlStringPair &b) { return a.second == b.second && a.first == b.first; } // Older versions of OpenOffice.org used different namespaces. This function // does translate the old namespaces into the new ones. static QString fixNamespace(const QString &nsURI) { static QString office = QString::fromLatin1("http://openoffice.org/2000/office"); static QString text = QString::fromLatin1("http://openoffice.org/2000/text"); static QString style = QString::fromLatin1("http://openoffice.org/2000/style"); static QString fo = QString::fromLatin1("http://www.w3.org/1999/XSL/Format"); static QString table = QString::fromLatin1("http://openoffice.org/2000/table"); static QString drawing = QString::fromLatin1("http://openoffice.org/2000/drawing"); static QString datastyle = QString::fromLatin1("http://openoffice.org/2000/datastyle"); static QString svg = QString::fromLatin1("http://www.w3.org/2000/svg"); static QString chart = QString::fromLatin1("http://openoffice.org/2000/chart"); static QString dr3d = QString::fromLatin1("http://openoffice.org/2000/dr3d"); static QString form = QString::fromLatin1("http://openoffice.org/2000/form"); static QString script = QString::fromLatin1("http://openoffice.org/2000/script"); static QString meta = QString::fromLatin1("http://openoffice.org/2000/meta"); static QString config = QString::fromLatin1("http://openoffice.org/2001/config"); static QString pres = QString::fromLatin1("http://openoffice.org/2000/presentation"); static QString manifest = QString::fromLatin1("http://openoffice.org/2001/manifest"); if (nsURI == text) return KoXmlNS::text; if (nsURI == style) return KoXmlNS::style; if (nsURI == office) return KoXmlNS::office; if (nsURI == fo) return KoXmlNS::fo; if (nsURI == table) return KoXmlNS::table; if (nsURI == drawing) return KoXmlNS::draw; if (nsURI == datastyle) return KoXmlNS::number; if (nsURI == svg) return KoXmlNS::svg; if (nsURI == chart) return KoXmlNS::chart; if (nsURI == dr3d) return KoXmlNS::dr3d; if (nsURI == form) return KoXmlNS::form; if (nsURI == script) return KoXmlNS::script; if (nsURI == meta) return KoXmlNS::meta; if (nsURI == config) return KoXmlNS::config; if (nsURI == pres) return KoXmlNS::presentation; if (nsURI == manifest) return KoXmlNS::manifest; return nsURI; } // ================================================================== // // KoXmlPackedItem // // ================================================================== // 12 bytes on most system 32 bit systems, 16 bytes on 64 bit systems class KoXmlPackedItem { public: bool attr: 1; KoXmlNode::NodeType type: 3; #ifdef KOXML_COMPACT quint32 childStart: 28; #else unsigned depth: 28; #endif unsigned qnameIndex; QString value; // it is important NOT to have a copy constructor, so that growth is optimal // see https://doc.qt.io/qt-5/containers.html#growth-strategies #if 0 KoXmlPackedItem(): attr(false), type(KoXmlNode::NullNode), childStart(0), depth(0) {} #endif }; Q_DECLARE_TYPEINFO(KoXmlPackedItem, Q_MOVABLE_TYPE); #ifdef KOXML_COMPRESS static QDataStream& operator<<(QDataStream& s, const KoXmlPackedItem& item) { quint8 flag = item.attr ? 1 : 0; s << flag; s << (quint8) item.type; s << item.childStart; s << item.qnameIndex; s << item.value; return s; } static QDataStream& operator>>(QDataStream& s, KoXmlPackedItem& item) { quint8 flag; quint8 type; quint32 child; QString value; s >> flag; s >> type; s >> child; s >> item.qnameIndex; s >> value; item.attr = (flag != 0); item.type = (KoXmlNode::NodeType) type; item.childStart = child; item.value = value; return s; } #endif // ================================================================== // // KoXmlPackedDocument // // ================================================================== #ifdef KOXML_COMPRESS #include "KoXmlVector.h" // when number of buffered items reach this, compression will start // small value will give better memory usage at the cost of speed // bigger value will be better in term of speed, but use more memory #define ITEMS_FULL (1*256) typedef KoXmlVector KoXmlPackedGroup; #else typedef QVector KoXmlPackedGroup; #endif // growth strategy: increase every GROUP_GROW_SIZE items // this will override standard QVector's growth strategy #define GROUP_GROW_SHIFT 3 #define GROUP_GROW_SIZE (1 << GROUP_GROW_SHIFT) class KoXmlPackedDocument { public: bool processNamespace; #ifdef KOXML_COMPACT // map given depth to the list of items QHash groups; #else QVector items; #endif QList qnameList; QString docType; private: QHash qnameHash; unsigned cacheQName(const QString& name, const QString& nsURI) { KoQName qname(nsURI, name); const unsigned ii = qnameHash.value(qname, (unsigned)-1); if (ii != (unsigned)-1) return ii; // not yet declared, so we add it unsigned i = qnameList.count(); qnameList.append(qname); qnameHash.insert(qname, i); return i; } QHash valueHash; QStringList valueList; QString cacheValue(const QString& value) { if (value.isEmpty()) return 0; const unsigned& ii = valueHash[value]; if (ii > 0) return valueList[ii]; // not yet declared, so we add it unsigned i = valueList.count(); valueList.append(value); valueHash.insert(value, i); return valueList[i]; } #ifdef KOXML_COMPACT public: const KoXmlPackedItem& itemAt(unsigned depth, unsigned index) { const KoXmlPackedGroup& group = groups[depth]; return group[index]; } unsigned itemCount(unsigned depth) { const KoXmlPackedGroup& group = groups[depth]; return group.count(); } /* NOTE: Function clear, newItem, addElement, addAttribute, addText, addCData, addProcessing are all related. These are all necessary for stateful manipulation of the document. See also the calls to these function from parseDocument(). The state itself is defined by the member variables currentDepth and the groups (see above). */ unsigned currentDepth; KoXmlPackedItem& newItem(unsigned depth) { KoXmlPackedGroup& group = groups[depth]; #ifdef KOXML_COMPRESS KoXmlPackedItem& item = group.newItem(); #else // reserve up front if ((groups.size() % GROUP_GROW_SIZE) == 0) group.reserve(GROUP_GROW_SIZE * (1 + (groups.size() >> GROUP_GROW_SHIFT))); group.resize(group.count() + 1); KoXmlPackedItem& item = group[group.count()-1]; #endif // this is necessary, because intentionally we don't want to have // a constructor for KoXmlPackedItem item.attr = false; item.type = KoXmlNode::NullNode; item.qnameIndex = 0; item.childStart = itemCount(depth + 1); item.value.clear(); return item; } void clear() { currentDepth = 0; qnameHash.clear(); qnameList.clear(); valueHash.clear(); valueList.clear(); groups.clear(); docType.clear(); // first node is root KoXmlPackedItem& rootItem = newItem(0); rootItem.type = KoXmlNode::DocumentNode; } void finish() { // won't be needed anymore qnameHash.clear(); valueHash.clear(); valueList.clear(); // optimize, see documentation on QVector::squeeze for (int d = 0; d < groups.count(); ++d) { KoXmlPackedGroup& group = groups[d]; group.squeeze(); } } // in case namespace processing, 'name' contains the prefix already void addElement(const QString& name, const QString& nsURI) { KoXmlPackedItem& item = newItem(currentDepth + 1); item.type = KoXmlNode::ElementNode; item.qnameIndex = cacheQName(name, nsURI); ++currentDepth; } void closeElement() { --currentDepth; } void addDTD(const QString& dt) { docType = dt; } void addAttribute(const QString& name, const QString& nsURI, const QString& value) { KoXmlPackedItem& item = newItem(currentDepth + 1); item.attr = true; item.qnameIndex = cacheQName(name, nsURI); //item.value = cacheValue( value ); item.value = value; } void addText(const QString& text) { KoXmlPackedItem& item = newItem(currentDepth + 1); item.type = KoXmlNode::TextNode; item.value = text; } void addCData(const QString& text) { KoXmlPackedItem& item = newItem(currentDepth + 1); item.type = KoXmlNode::CDATASectionNode; item.value = text; } void addProcessingInstruction() { KoXmlPackedItem& item = newItem(currentDepth + 1); item.type = KoXmlNode::ProcessingInstructionNode; } public: KoXmlPackedDocument(): processNamespace(false), currentDepth(0) { clear(); } #else private: unsigned elementDepth; public: KoXmlPackedItem& newItem() { unsigned count = items.count() + 512; count = 1024 * (count >> 10); items.reserve(count); items.resize(items.count() + 1); // this is necessary, because intentionally we don't want to have // a constructor for KoXmlPackedItem KoXmlPackedItem& item = items[items.count()-1]; item.attr = false; item.type = KoXmlNode::NullNode; item.qnameIndex = 0; item.depth = 0; return item; } void addElement(const QString& name, const QString& nsURI) { // we are going one level deeper ++elementDepth; KoXmlPackedItem& item = newItem(); item.attr = false; item.type = KoXmlNode::ElementNode; item.depth = elementDepth; item.qnameIndex = cacheQName(name, nsURI); } void closeElement() { // we are going up one level --elementDepth; } void addDTD(const QString& dt) { docType = dt; } void addAttribute(const QString& name, const QString& nsURI, const QString& value) { KoXmlPackedItem& item = newItem(); item.attr = true; item.type = KoXmlNode::NullNode; item.depth = elementDepth; item.qnameIndex = cacheQName(name, nsURI); //item.value = cacheValue( value ); item.value = value; } void addText(const QString& str) { KoXmlPackedItem& item = newItem(); item.attr = false; item.type = KoXmlNode::TextNode; item.depth = elementDepth + 1; item.qnameIndex = 0; item.value = str; } void addCData(const QString& str) { KoXmlPackedItem& item = newItem(); item.attr = false; item.type = KoXmlNode::CDATASectionNode; item.depth = elementDepth + 1; item.qnameIndex = 0; item.value = str; } void addProcessingInstruction() { KoXmlPackedItem& item = newItem(); item.attr = false; item.type = KoXmlNode::ProcessingInstructionNode; item.depth = elementDepth + 1; item.qnameIndex = 0; item.value.clear(); } void clear() { qnameHash.clear(); qnameList.clear(); valueHash.clear(); valueList.clear(); items.clear(); elementDepth = 0; KoXmlPackedItem& rootItem = newItem(); rootItem.attr = false; rootItem.type = KoXmlNode::DocumentNode; rootItem.depth = 0; rootItem.qnameIndex = 0; } void finish() { qnameHash.clear(); valueList.clear(); valueHash.clear(); items.squeeze(); } KoXmlPackedDocument(): processNamespace(false), elementDepth(0) { } #endif }; namespace { class ParseError { public: QString errorMsg; int errorLine; int errorColumn; bool error; ParseError() :errorLine(-1), errorColumn(-1), error(false) {} }; void parseElement(QXmlStreamReader &xml, KoXmlPackedDocument &doc, bool stripSpaces = true); // parse one element as if this were a standalone xml document ParseError parseDocument(QXmlStreamReader &xml, KoXmlPackedDocument &doc, bool stripSpaces = true) { doc.clear(); ParseError error; xml.readNext(); while (!xml.atEnd() && xml.tokenType() != QXmlStreamReader::EndDocument && !xml.hasError()) { switch (xml.tokenType()) { case QXmlStreamReader::StartElement: parseElement(xml, doc, stripSpaces); break; case QXmlStreamReader::DTD: doc.addDTD(xml.dtdName().toString()); break; case QXmlStreamReader::StartDocument: if (!xml.documentEncoding().isEmpty() || !xml.documentVersion().isEmpty()) { doc.addProcessingInstruction(); } break; case QXmlStreamReader::ProcessingInstruction: doc.addProcessingInstruction(); break; default: break; } xml.readNext(); } if (xml.hasError()) { error.error = true; error.errorMsg = xml.errorString(); error.errorColumn = xml.columnNumber(); error.errorLine = xml.lineNumber(); } else { doc.finish(); } return error; } void parseElementContents(QXmlStreamReader &xml, KoXmlPackedDocument &doc) { xml.readNext(); QString ws; while (!xml.atEnd()) { switch (xml.tokenType()) { case QXmlStreamReader::EndElement: // if an element contains only whitespace, put it in the dom if (!ws.isEmpty()) { doc.addText(ws); } return; case QXmlStreamReader::StartElement: // The whitespaces between > and < are also a text element if (!ws.isEmpty()) { doc.addText(ws); ws.clear(); } // Do not strip spaces parseElement(xml, doc, false); break; case QXmlStreamReader::Characters: if (xml.isCDATA()) { doc.addCData(xml.text().toString()); } else if (!xml.isWhitespace()) { doc.addText(xml.text().toString()); } else { ws += xml.text(); } break; case QXmlStreamReader::ProcessingInstruction: doc.addProcessingInstruction(); break; default: break; } xml.readNext(); } } void parseElementContentsStripSpaces(QXmlStreamReader &xml, KoXmlPackedDocument &doc) { xml.readNext(); QString ws; bool sawElement = false; while (!xml.atEnd()) { switch (xml.tokenType()) { case QXmlStreamReader::EndElement: // if an element contains only whitespace, put it in the dom if (!ws.isEmpty() && !sawElement) { doc.addText(ws); } return; case QXmlStreamReader::StartElement: sawElement = true; // Do strip spaces parseElement(xml, doc, true); break; case QXmlStreamReader::Characters: if (xml.isCDATA()) { doc.addCData(xml.text().toString()); } else if (!xml.isWhitespace()) { doc.addText(xml.text().toString()); } else if (!sawElement) { ws += xml.text(); } break; case QXmlStreamReader::ProcessingInstruction: doc.addProcessingInstruction(); break; default: break; } xml.readNext(); } } void parseElement(QXmlStreamReader &xml, KoXmlPackedDocument &doc, bool stripSpaces) { // Unfortunately MSVC fails using QXmlStreamReader::const_iterator // so we apply a for loop instead. https://bugreports.qt.io/browse/QTBUG-45368 doc.addElement(xml.qualifiedName().toString(), fixNamespace(xml.namespaceUri().toString())); QXmlStreamAttributes attr = xml.attributes(); for (int a = 0; a < attr.count(); a++) { doc.addAttribute(attr[a].qualifiedName().toString(), attr[a].namespaceUri().toString(), attr[a].value().toString()); } if (stripSpaces) parseElementContentsStripSpaces(xml, doc); else parseElementContents(xml, doc); // reader.tokenType() is now QXmlStreamReader::EndElement doc.closeElement(); } } // ================================================================== // // KoXmlNodeData // // ================================================================== class KoXmlNodeData { public: explicit KoXmlNodeData(unsigned long initialRefCount = 1); ~KoXmlNodeData(); // generic properties KoXmlNode::NodeType nodeType; bool loaded; #ifdef KOXML_COMPACT unsigned nodeDepth; #endif QString tagName; QString namespaceURI; QString prefix; QString localName; void ref() { ++refCount; } void unref() { if (!--refCount) { delete this; } } // type information QString nodeName() const; // for tree and linked-list KoXmlNodeData* parent; KoXmlNodeData* prev; KoXmlNodeData* next; KoXmlNodeData* first; KoXmlNodeData* last; QString text(); // node manipulation void clear(); // attributes inline void setAttribute(const QString& name, const QString& value); inline QString attribute(const QString& name, const QString& def) const; inline bool hasAttribute(const QString& name) const; inline void setAttributeNS(const QString& nsURI, const QString& name, const QString& value); inline QString attributeNS(const QString& nsURI, const QString& name, const QString& def) const; inline bool hasAttributeNS(const QString& nsURI, const QString& name) const; inline void clearAttributes(); inline QStringList attributeNames() const; inline QList< QPair > attributeFullNames() const; // for text and CDATA QString data() const; // reference from within the packed doc KoXmlPackedDocument* packedDoc; unsigned long nodeIndex; // used when doing on-demand (re)parse void loadChildren(int depth = 1); void unloadChildren(); void dump(); static KoXmlNodeData null; // compatibility void asQDomNode(QDomDocument& ownerDoc) const; private: QHash attr; QHash attrNS; QString textData; // reference counting unsigned long refCount; friend class KoXmlElement; }; KoXmlNodeData KoXmlNodeData::null; KoXmlNodeData::KoXmlNodeData(unsigned long initialRefCount) : nodeType(KoXmlNode::NullNode) , loaded(false) #ifdef KOXML_COMPACT , nodeDepth(0) #endif , parent(0), prev(0), next(0), first(0), last(0) , packedDoc(0), nodeIndex(0) , refCount(initialRefCount) { } KoXmlNodeData::~KoXmlNodeData() { clear(); } void KoXmlNodeData::clear() { if (first) for (KoXmlNodeData* node = first; node ;) { KoXmlNodeData* next = node->next; node->unref(); node = next; } // only document can delete these // normal nodes don't "own" them if (nodeType == KoXmlNode::DocumentNode) delete packedDoc; nodeType = KoXmlNode::NullNode; tagName.clear(); prefix.clear(); namespaceURI.clear(); textData.clear(); packedDoc = 0; attr.clear(); attrNS.clear(); parent = 0; prev = next = 0; first = last = 0; loaded = false; } QString KoXmlNodeData::text() { QString t; loadChildren(); KoXmlNodeData* node = first; while (node) { switch (node->nodeType) { case KoXmlNode::ElementNode: t += node->text(); break; case KoXmlNode::TextNode: t += node->data(); break; case KoXmlNode::CDATASectionNode: t += node->data(); break; default: break; } node = node->next; } return t; } QString KoXmlNodeData::nodeName() const { switch (nodeType) { case KoXmlNode::ElementNode: { QString n(tagName); if (!prefix.isEmpty()) n.prepend(':').prepend(prefix); return n; } break; case KoXmlNode::TextNode: return QLatin1String("#text"); case KoXmlNode::CDATASectionNode: return QLatin1String("#cdata-section"); case KoXmlNode::DocumentNode: return QLatin1String("#document"); case KoXmlNode::DocumentTypeNode: return tagName; default: return QString(); break; } // should not happen return QString(); } void KoXmlNodeData::setAttribute(const QString& name, const QString& value) { attr.insert(name, value); } QString KoXmlNodeData::attribute(const QString& name, const QString& def) const { return attr.value(name, def); } bool KoXmlNodeData::hasAttribute(const QString& name) const { return attr.contains(name); } void KoXmlNodeData::setAttributeNS(const QString& nsURI, const QString& name, const QString& value) { int i = name.indexOf(':'); if (i != -1) { QString localName(name.mid(i + 1)); KoXmlStringPair key(nsURI, localName); attrNS.insert(key, value); } } QString KoXmlNodeData::attributeNS(const QString& nsURI, const QString& name, const QString& def) const { KoXmlStringPair key(nsURI, name); return attrNS.value(key, def); } bool KoXmlNodeData::hasAttributeNS(const QString& nsURI, const QString& name) const { KoXmlStringPair key(nsURI, name); return attrNS.contains(key); } void KoXmlNodeData::clearAttributes() { attr.clear(); attrNS.clear(); } // FIXME how about namespaced attributes ? QStringList KoXmlNodeData::attributeNames() const { QStringList result; result = attr.keys(); return result; } QList< QPair > KoXmlNodeData::attributeFullNames() const { QList< QPair > result; result = attrNS.keys(); return result; } QString KoXmlNodeData::data() const { return textData; } #ifdef KOXML_COMPACT void KoXmlNodeData::loadChildren(int depth) { // sanity check if (!packedDoc) return; // already loaded ? if (loaded && (depth <= 1)) return; // in case depth is different unloadChildren(); KoXmlNodeData* lastDat = 0; unsigned childStop = 0; if (nodeIndex == packedDoc->itemCount(nodeDepth) - 1) childStop = packedDoc->itemCount(nodeDepth + 1); else { const KoXmlPackedItem& next = packedDoc->itemAt(nodeDepth, nodeIndex + 1); childStop = next.childStart; } const KoXmlPackedItem& self = packedDoc->itemAt(nodeDepth, nodeIndex); for (unsigned i = self.childStart; i < childStop; ++i) { const KoXmlPackedItem& item = packedDoc->itemAt(nodeDepth + 1, i); bool textItem = (item.type == KoXmlNode::TextNode); textItem |= (item.type == KoXmlNode::CDATASectionNode); // attribute belongs to this node if (item.attr) { KoQName qname = packedDoc->qnameList[item.qnameIndex]; QString value = item.value; QString prefix; QString qName; // with prefix QString localName; // without prefix, i.e. local name localName = qName = qname.name; int i = qName.indexOf(':'); if (i != -1) prefix = qName.left(i); if (i != -1) localName = qName.mid(i + 1); if (packedDoc->processNamespace) { setAttributeNS(qname.nsURI, qName, value); setAttribute(localName, value); } else setAttribute(qName, value); } else { KoQName qname = packedDoc->qnameList[item.qnameIndex]; QString value = item.value; QString nodeName = qname.name; QString localName; QString prefix; if (packedDoc->processNamespace) { localName = qname.name; int di = qname.name.indexOf(':'); if (di != -1) { localName = qname.name.mid(di + 1); prefix = qname.name.left(di); } nodeName = localName; } // make a node out of this item KoXmlNodeData* dat = new KoXmlNodeData; dat->nodeIndex = i; dat->packedDoc = packedDoc; dat->nodeDepth = nodeDepth + 1; dat->nodeType = item.type; dat->tagName = nodeName; dat->localName = localName; dat->prefix = prefix; dat->namespaceURI = qname.nsURI; dat->parent = this; dat->prev = lastDat; dat->next = 0; dat->first = 0; dat->last = 0; dat->loaded = false; dat->textData = (textItem) ? value : QString(); // adjust our linked-list first = (first) ? first : dat; last = dat; if (lastDat) lastDat->next = dat; lastDat = dat; // recursive if (depth > 1) dat->loadChildren(depth - 1); } } loaded = true; } #else void KoXmlNodeData::loadChildren(int depth) { // sanity check if (!packedDoc) return; // already loaded ? if (loaded && (depth <= 1)) return; // cause we don't know how deep this node's children already loaded are unloadChildren(); KoXmlNodeData* lastDat = 0; int nodeDepth = packedDoc->items[nodeIndex].depth; for (int i = nodeIndex + 1; i < packedDoc->items.count(); ++i) { KoXmlPackedItem& item = packedDoc->items[i]; bool textItem = (item.type == KoXmlNode::TextNode); textItem |= (item.type == KoXmlNode::CDATASectionNode); // element already outside our depth if (!item.attr && (item.type == KoXmlNode::ElementNode)) if (item.depth <= (unsigned)nodeDepth) break; // attribute belongs to this node if (item.attr && (item.depth == (unsigned)nodeDepth)) { KoQName qname = packedDoc->qnameList[item.qnameIndex]; QString value = item.value; QString prefix; QString qName; // with prefix QString localName; // without prefix, i.e. local name localName = qName = qname.name; int i = qName.indexOf(':'); if (i != -1) prefix = qName.left(i); if (i != -1) localName = qName.mid(i + 1); if (packedDoc->processNamespace) { setAttributeNS(qname.nsURI, qName, value); setAttribute(localName, value); } else setAttribute(qname.name, value); } // the child node if (!item.attr) { bool instruction = (item.type == KoXmlNode::ProcessingInstructionNode); bool ok = (textItem || instruction) ? (item.depth == (unsigned)nodeDepth) : (item.depth == (unsigned)nodeDepth + 1); ok = (item.depth == (unsigned)nodeDepth + 1); if (ok) { KoQName qname = packedDoc->qnameList[item.qnameIndex]; QString value = item.value; QString nodeName = qname.name; QString localName; QString prefix; if (packedDoc->processNamespace) { localName = qname.name; int di = qname.name.indexOf(':'); if (di != -1) { localName = qname.name.mid(di + 1); prefix = qname.name.left(di); } nodeName = localName; } // make a node out of this item KoXmlNodeData* dat = new KoXmlNodeData; dat->nodeIndex = i; dat->packedDoc = packedDoc; dat->nodeType = item.type; dat->tagName = nodeName; dat->localName = localName; dat->prefix = prefix; dat->namespaceURI = qname.nsURI; dat->count = 1; dat->parent = this; dat->prev = lastDat; dat->next = 0; dat->first = 0; dat->last = 0; dat->loaded = false; dat->textData = (textItem) ? value : QString(); // adjust our linked-list first = (first) ? first : dat; last = dat; if (lastDat) lastDat->next = dat; lastDat = dat; // recursive if (depth > 1) dat->loadChildren(depth - 1); } } } loaded = true; } #endif void KoXmlNodeData::unloadChildren() { // sanity check if (!packedDoc) return; if (!loaded) return; if (first) for (KoXmlNodeData* node = first; node ;) { KoXmlNodeData* next = node->next; node->unloadChildren(); node->unref(); node = next; } clearAttributes(); loaded = false; first = last = 0; } #ifdef KOXML_COMPACT static void itemAsQDomNode(QDomDocument& ownerDoc, KoXmlPackedDocument* packedDoc, unsigned nodeDepth, unsigned nodeIndex, QDomNode parentNode = QDomNode()) { // sanity check if (!packedDoc) return; const KoXmlPackedItem& self = packedDoc->itemAt(nodeDepth, nodeIndex); unsigned childStop = 0; if (nodeIndex == packedDoc->itemCount(nodeDepth) - 1) childStop = packedDoc->itemCount(nodeDepth + 1); else { const KoXmlPackedItem& next = packedDoc->itemAt(nodeDepth, nodeIndex + 1); childStop = next.childStart; } // nothing to do here if (self.type == KoXmlNode::NullNode) return; // create the element properly if (self.type == KoXmlNode::ElementNode) { QDomElement element; KoQName qname = packedDoc->qnameList[self.qnameIndex]; qname.nsURI = fixNamespace(qname.nsURI); if (packedDoc->processNamespace) element = ownerDoc.createElementNS(qname.nsURI, qname.name); else element = ownerDoc.createElement(qname.name); if ( parentNode.isNull() ) { ownerDoc.appendChild( element ); } else { parentNode.appendChild( element ); } // check all subnodes for attributes for (unsigned i = self.childStart; i < childStop; ++i) { const KoXmlPackedItem& item = packedDoc->itemAt(nodeDepth + 1, i); bool textItem = (item.type == KoXmlNode::TextNode); textItem |= (item.type == KoXmlNode::CDATASectionNode); // attribute belongs to this node if (item.attr) { KoQName qname = packedDoc->qnameList[item.qnameIndex]; qname.nsURI = fixNamespace(qname.nsURI ); QString value = item.value; QString prefix; QString qName; // with prefix QString localName; // without prefix, i.e. local name localName = qName = qname.name; int i = qName.indexOf(':'); if (i != -1) prefix = qName.left(i); if (i != -1) localName = qName.mid(i + 1); if (packedDoc->processNamespace) { element.setAttributeNS(qname.nsURI, qName, value); element.setAttribute(localName, value); } else element.setAttribute(qname.name, value); } else { // add it recursively itemAsQDomNode(ownerDoc, packedDoc, nodeDepth + 1, i, element); } } return; } // create the text node if (self.type == KoXmlNode::TextNode) { QString text = self.value; // FIXME: choose CDATA when the value contains special characters QDomText textNode = ownerDoc.createTextNode(text); if ( parentNode.isNull() ) { ownerDoc.appendChild( textNode ); } else { parentNode.appendChild( textNode ); } return; } // nothing matches? strange... } void KoXmlNodeData::asQDomNode(QDomDocument& ownerDoc) const { itemAsQDomNode(ownerDoc, packedDoc, nodeDepth, nodeIndex); } #else static void itemAsQDomNode(QDomDocument& ownerDoc, KoXmlPackedDocument* packedDoc, unsigned nodeIndex, QDomNode parentNode = QDomNode()) { // sanity check if (!packedDoc) return; KoXmlPackedItem& item = packedDoc->items[nodeIndex]; // nothing to do here if (item.type == KoXmlNode::NullNode) return; // create the element properly if (item.type == KoXmlNode::ElementNode) { QDomElement element; KoQName qname = packedDoc->qnameList[item.qnameIndex]; qname.nsURI = fixNamespace(qname.nsURI); if (packedDoc->processNamespace) element = ownerDoc.createElementNS(qname.nsURI, qname.name); else element = ownerDoc.createElement(qname.name); if ( parentNode.isNull() ) { ownerDoc.appendChild( element ); } else { parentNode.appendChild( element ); } // check all subnodes for attributes int nodeDepth = item.depth; for (int i = nodeIndex + 1; i < packedDoc->items.count(); ++i) { KoXmlPackedItem& item = packedDoc->items[i]; bool textItem = (item.type == KoXmlNode::TextNode); textItem |= (item.type == KoXmlNode::CDATASectionNode); // element already outside our depth if (!item.attr && (item.type == KoXmlNode::ElementNode)) if (item.depth <= (unsigned)nodeDepth) break; // attribute belongs to this node if (item.attr && (item.depth == (unsigned)nodeDepth)) { KoQName qname = packedDoc->qnameList[item.qnameIndex]; qname.nsURI = fixNamespace(qname.nsURI); QString value = item.value; QString prefix; QString qName; // with prefix QString localName; // without prefix, i.e. local name localName = qName = qname.name; int i = qName.indexOf(':'); if (i != -1) prefix = qName.left(i); if (i != -1) localName = qName.mid(i + 1); if (packedDoc->processNamespace) { element.setAttributeNS(qname.nsURI, qName, value); element.setAttribute(localName, value); } else element.setAttribute(qname.name, value); } // direct child of this node if (!item.attr && (item.depth == (unsigned)nodeDepth + 1)) { // add it recursively itemAsQDomNode(ownerDoc, packedDoc, i, element); } } return; } // create the text node if (item.type == KoXmlNode::TextNode) { QString text = item.value; // FIXME: choose CDATA when the value contains special characters QDomText textNode = ownerDoc.createTextNode(text); if ( parentNode.isNull() ) { ownerDoc.appendChild( textNode ); } else { parentNode.appendChild( textNode ); } return; } // nothing matches? strange... } void KoXmlNodeData::asQDomNode(QDomDocument& ownerDoc) const { itemAsQDomNode(ownerDoc, packedDoc, nodeIndex); } #endif void KoXmlNodeData::dump() { printf("NodeData %p\n", (void*)this); printf(" nodeIndex: %d\n", (int)nodeIndex); printf(" packedDoc: %p\n", (void*)packedDoc); printf(" nodeType : %d\n", (int)nodeType); printf(" tagName: %s\n", qPrintable(tagName)); printf(" namespaceURI: %s\n", qPrintable(namespaceURI)); printf(" prefix: %s\n", qPrintable(prefix)); printf(" localName: %s\n", qPrintable(localName)); printf(" parent : %p\n", (void*)parent); printf(" prev : %p\n", (void*)prev); printf(" next : %p\n", (void*)next); printf(" first : %p\n", (void*)first); printf(" last : %p\n", (void*)last); printf(" refCount: %ld\n", refCount); if (loaded) printf(" loaded: TRUE\n"); else printf(" loaded: FALSE\n"); } // ================================================================== // // KoXmlNodeData // // ================================================================== class KoXmlDocumentData : public KoXmlNodeData { public: KoXmlDocumentData(unsigned long initialRefCount = 1); ~KoXmlDocumentData(); bool setContent(QXmlStreamReader *reader, QString* errorMsg = 0, int* errorLine = 0, int* errorColumn = 0); KoXmlDocumentType dt; bool emptyDocument :1; // to read the xml with or without spaces bool stripSpaces :1; }; #define KOXMLDOCDATA(d) static_cast(d) KoXmlDocumentData::KoXmlDocumentData(unsigned long initialRefCount) : KoXmlNodeData(initialRefCount) , emptyDocument(true) , stripSpaces(true) { } KoXmlDocumentData::~KoXmlDocumentData() { } bool KoXmlDocumentData::setContent(QXmlStreamReader* reader, QString* errorMsg, int* errorLine, int* errorColumn) { // sanity checks if (!reader) return false; if (nodeType != KoXmlNode::DocumentNode) return false; clear(); nodeType = KoXmlNode::DocumentNode; packedDoc = new KoXmlPackedDocument; packedDoc->processNamespace = reader->namespaceProcessing(); ParseError error = parseDocument(*reader, *packedDoc, stripSpaces); if (error.error) { // parsing error has occurred if (errorMsg) *errorMsg = error.errorMsg; if (errorLine) *errorLine = error.errorLine; if (errorColumn) *errorColumn = error.errorColumn; return false; } // initially load loadChildren(); KoXmlNodeData *typeData = new KoXmlNodeData(0); typeData->nodeType = KoXmlNode::DocumentTypeNode; typeData->tagName = packedDoc->docType; typeData->parent = this; dt = KoXmlDocumentType(typeData); return true; } // ================================================================== // // KoXmlNode // // ================================================================== // Creates a null node KoXmlNode::KoXmlNode() { d = &KoXmlNodeData::null; d->ref(); } // Destroys this node KoXmlNode::~KoXmlNode() { d->unref(); } // Creates a copy of another node KoXmlNode::KoXmlNode(const KoXmlNode& node) { d = node.d; d->ref(); } // Creates a node for specific implementation KoXmlNode::KoXmlNode(KoXmlNodeData* data) { d = data; data->ref(); } // Creates a shallow copy of another node KoXmlNode& KoXmlNode::operator=(const KoXmlNode & node) { if (this != &node) { d->unref(); d = node.d; d->ref(); } return *this; } // Note: two null nodes are always equal bool KoXmlNode::operator==(const KoXmlNode& node) const { if (isNull() && node.isNull()) return true; return(d == node.d); } // Note: two null nodes are always equal bool KoXmlNode::operator!=(const KoXmlNode& node) const { if (isNull() && !node.isNull()) return true; if (!isNull() && node.isNull()) return true; if (isNull() && node.isNull()) return false; return(d != node.d); } KoXmlNode::NodeType KoXmlNode::nodeType() const { return d->nodeType; } bool KoXmlNode::isNull() const { return d->nodeType == NullNode; } bool KoXmlNode::isElement() const { return d->nodeType == ElementNode; } bool KoXmlNode::isText() const { return (d->nodeType == TextNode) || isCDATASection(); } bool KoXmlNode::isCDATASection() const { return d->nodeType == CDATASectionNode; } bool KoXmlNode::isDocument() const { return d->nodeType == DocumentNode; } bool KoXmlNode::isDocumentType() const { return d->nodeType == DocumentTypeNode; } void KoXmlNode::clear() { d->unref(); d = new KoXmlNodeData; } QString KoXmlNode::nodeName() const { return d->nodeName(); } QString KoXmlNode::prefix() const { return isElement() ? d->prefix : QString(); } QString KoXmlNode::namespaceURI() const { return isElement() ? d->namespaceURI : QString(); } QString KoXmlNode::localName() const { return isElement() ? d->localName : QString(); } KoXmlDocument KoXmlNode::ownerDocument() const { KoXmlNodeData* node = d; while (node->parent) node = node->parent; if (node->nodeType == DocumentNode) { return KoXmlDocument(static_cast(node)); } return KoXmlDocument(); } KoXmlNode KoXmlNode::parentNode() const { return d->parent ? KoXmlNode(d->parent) : KoXmlNode(); } bool KoXmlNode::hasChildNodes() const { if (isText()) return false; if (!d->loaded) d->loadChildren(); return d->first != 0 ; } int KoXmlNode::childNodesCount() const { if (isText()) return 0; if (!d->loaded) d->loadChildren(); KoXmlNodeData* node = d->first; int count = 0; while (node) { ++count; node = node->next; } return count; } QStringList KoXmlNode::attributeNames() const { if (!d->loaded) d->loadChildren(); return d->attributeNames(); } QList< QPair > KoXmlNode::attributeFullNames() const { if (!d->loaded) d->loadChildren(); return d->attributeFullNames(); } KoXmlNode KoXmlNode::firstChild() const { if (!d->loaded) d->loadChildren(); return d->first ? KoXmlNode(d->first) : KoXmlNode(); } KoXmlElement KoXmlNode::firstChildElement() const { KoXmlElement element; forEachElement (element, (*this)) { return element; } return KoXmlElement(); } KoXmlNode KoXmlNode::lastChild() const { if (!d->loaded) d->loadChildren(); return d->last ? KoXmlNode(d->last) : KoXmlNode(); } KoXmlNode KoXmlNode::nextSibling() const { return d->next ? KoXmlNode(d->next) : KoXmlNode(); } KoXmlNode KoXmlNode::previousSibling() const { return d->prev ? KoXmlNode(d->prev) : KoXmlNode(); } KoXmlNode KoXmlNode::namedItem(const QString& name) const { if (!d->loaded) d->loadChildren(); for (KoXmlNodeData* node = d->first; node; node = node->next) { if (node->nodeName() == name) return KoXmlNode(node); } // not found return KoXmlNode(); } KoXmlNode KoXmlNode::namedItemNS(const QString& nsURI, const QString& name) const { if (!d->loaded) d->loadChildren(); for (KoXmlNodeData* node = d->first; node; node = node->next) { if (node->nodeType == KoXmlNode::ElementNode && node->localName == name && node->namespaceURI == nsURI ) { return KoXmlNode(node); } } // not found return KoXmlNode(); } KoXmlNode KoXmlNode::namedItemNS(const QString& nsURI, const QString& name, KoXmlNamedItemType type) const { if (!d->loaded) d->loadChildren(); for (KoXmlNodeData* node = d->first; node; node = node->next) { if (node->nodeType != KoXmlNode::ElementNode) continue; if (node->localName == name && node->namespaceURI == nsURI) { return KoXmlNode(node); } bool isPrelude = false; switch (type) { case KoXmlTextContentPrelude: isPrelude = (node->localName == "tracked-changes" && node->namespaceURI == KoXmlNS::text) || (node->localName == "variable-decls" && node->namespaceURI == KoXmlNS::text) || (node->localName == "user-field-decls" && node->namespaceURI == KoXmlNS::text) || (node->localName == "user-field-decl" && node->namespaceURI == KoXmlNS::text) || (node->localName == "sequence-decls" && node->namespaceURI == KoXmlNS::text) || (node->localName == "sequence-decl" && node->namespaceURI == KoXmlNS::text) || (node->localName == "dde-connection-decls" && node->namespaceURI == KoXmlNS::text) || (node->localName == "alphabetical-index-auto-mark-file" && node->namespaceURI == KoXmlNS::text) || (node->localName == "forms" && node->namespaceURI == KoXmlNS::office); break; } if (!isPrelude) { return KoXmlNode(); // no TextContentPrelude means it follows TextContentMain, so stop here. } } // not found return KoXmlNode(); } KoXmlElement KoXmlNode::toElement() const { return isElement() ? KoXmlElement(d) : KoXmlElement(); } KoXmlText KoXmlNode::toText() const { return isText() ? KoXmlText(d) : KoXmlText(); } KoXmlCDATASection KoXmlNode::toCDATASection() const { return isCDATASection() ? KoXmlCDATASection(d) : KoXmlCDATASection(); } KoXmlDocument KoXmlNode::toDocument() const { if (isDocument()) { return KoXmlDocument(static_cast(d)); } return KoXmlDocument(); } void KoXmlNode::load(int depth) { d->loadChildren(depth); } void KoXmlNode::unload() { d->unloadChildren(); } void KoXmlNode::asQDomNode(QDomDocument& ownerDoc) const { Q_ASSERT(!isDocument()); d->asQDomNode(ownerDoc); } // ================================================================== // // KoXmlElement // // ================================================================== // Creates an empty element KoXmlElement::KoXmlElement(): KoXmlNode() { } KoXmlElement::~KoXmlElement() { } // Creates a shallow copy of another element KoXmlElement::KoXmlElement(const KoXmlElement& element): KoXmlNode(element.d) { } KoXmlElement::KoXmlElement(KoXmlNodeData* data): KoXmlNode(data) { } // Copies another element KoXmlElement& KoXmlElement::operator=(const KoXmlElement & element) { KoXmlNode::operator=(element); return *this; } bool KoXmlElement::operator== (const KoXmlElement& element) const { if (isNull() || element.isNull()) return false; return (d == element.d); } bool KoXmlElement::operator!= (const KoXmlElement& element) const { if (isNull() && element.isNull()) return false; if (isNull() || element.isNull()) return true; return (d != element.d); } QString KoXmlElement::tagName() const { return isElement() ? d->tagName : QString(); } QString KoXmlElement::text() const { return d->text(); } QString KoXmlElement::attribute(const QString& name) const { if (!isElement()) return QString(); if (!d->loaded) d->loadChildren(); return d->attribute(name, QString()); } QString KoXmlElement::attribute(const QString& name, const QString& defaultValue) const { if (!isElement()) return defaultValue; if (!d->loaded) d->loadChildren(); return d->attribute(name, defaultValue); } QString KoXmlElement::attributeNS(const QString& namespaceURI, const QString& localName, const QString& defaultValue) const { if (!isElement()) return defaultValue; if (!d->loaded) d->loadChildren(); KoXmlStringPair key(namespaceURI, localName); return d->attrNS.value(key, defaultValue); // return d->attributeNS( namespaceURI, localName, defaultValue ); } bool KoXmlElement::hasAttribute(const QString& name) const { if (!d->loaded) d->loadChildren(); return isElement() ? d->hasAttribute(name) : false; } bool KoXmlElement::hasAttributeNS(const QString& namespaceURI, const QString& localName) const { if (!d->loaded) d->loadChildren(); return isElement() ? d->hasAttributeNS(namespaceURI, localName) : false; } // ================================================================== // // KoXmlText // // ================================================================== KoXmlText::KoXmlText(): KoXmlNode() { } KoXmlText::~KoXmlText() { } KoXmlText::KoXmlText(const KoXmlText& text): KoXmlNode(text.d) { } KoXmlText::KoXmlText(KoXmlNodeData* data): KoXmlNode(data) { } bool KoXmlText::isText() const { return true; } QString KoXmlText::data() const { return d->data(); } KoXmlText& KoXmlText::operator=(const KoXmlText & element) { KoXmlNode::operator=(element); return *this; } // ================================================================== // // KoXmlCDATASection // // ================================================================== KoXmlCDATASection::KoXmlCDATASection(): KoXmlText() { } KoXmlCDATASection::KoXmlCDATASection(const KoXmlCDATASection& cdata) : KoXmlText(cdata) { } KoXmlCDATASection::~KoXmlCDATASection() { } KoXmlCDATASection::KoXmlCDATASection(KoXmlNodeData* cdata): KoXmlText(cdata) { } bool KoXmlCDATASection::isCDATASection() const { return true; } KoXmlCDATASection& KoXmlCDATASection::operator=(const KoXmlCDATASection & cdata) { KoXmlNode::operator=(cdata); return *this; } // ================================================================== // // KoXmlDocumentType // // ================================================================== KoXmlDocumentType::KoXmlDocumentType(): KoXmlNode() { } KoXmlDocumentType::~KoXmlDocumentType() { } KoXmlDocumentType::KoXmlDocumentType(const KoXmlDocumentType& dt): KoXmlNode(dt.d) { } QString KoXmlDocumentType::name() const { return nodeName(); } KoXmlDocumentType::KoXmlDocumentType(KoXmlNodeData* dt): KoXmlNode(dt) { } KoXmlDocumentType& KoXmlDocumentType::operator=(const KoXmlDocumentType & dt) { KoXmlNode::operator=(dt); return *this; } // ================================================================== // // KoXmlDocument // // ================================================================== KoXmlDocument::KoXmlDocument(bool stripSpaces): KoXmlNode(new KoXmlDocumentData(0)) { KOXMLDOCDATA(d)->emptyDocument = false; KOXMLDOCDATA(d)->stripSpaces = stripSpaces; } KoXmlDocument::~KoXmlDocument() { } KoXmlDocument::KoXmlDocument(KoXmlDocumentData* data): KoXmlNode(data) { KOXMLDOCDATA(d)->emptyDocument = true; } // Creates a copy of another document KoXmlDocument::KoXmlDocument(const KoXmlDocument& doc): KoXmlNode(doc.d) { } // Creates a shallow copy of another document KoXmlDocument& KoXmlDocument::operator=(const KoXmlDocument & doc) { KoXmlNode::operator=(doc); return *this; } // Checks if this document and doc are equals bool KoXmlDocument::operator==(const KoXmlDocument& doc) const { return(d == doc.d); } // Checks if this document and doc are not equals bool KoXmlDocument::operator!=(const KoXmlDocument& doc) const { return(d != doc.d); } KoXmlElement KoXmlDocument::documentElement() const { if (!d->loaded) d->loadChildren(); for (KoXmlNodeData* node = d->first; node; node = node->next) { if (node->nodeType == KoXmlNode::ElementNode) { return KoXmlElement(node); } } return KoXmlElement(); } KoXmlDocumentType KoXmlDocument::doctype() const { return KOXMLDOCDATA(d)->dt; } QString KoXmlDocument::nodeName() const { return (KOXMLDOCDATA(d)->emptyDocument) ? QString::fromLatin1("#document") : QString(); } void KoXmlDocument::clear() { d->unref(); KoXmlDocumentData *dat = new KoXmlDocumentData; dat->emptyDocument = false; d = dat; } namespace { /* Use an entity resolver that ignores undefined entities and simply returns an empty string for them. */ class DumbEntityResolver : public QXmlStreamEntityResolver { public: QString resolveUndeclaredEntity ( const QString &) override { return ""; } }; } bool KoXmlDocument::setContent(QXmlStreamReader *reader, QString* errorMsg, int* errorLine, int* errorColumn) { if (d->nodeType != KoXmlNode::DocumentNode) { const bool stripSpaces = KOXMLDOCDATA(d)->stripSpaces; d->unref(); KoXmlDocumentData *dat = new KoXmlDocumentData; dat->nodeType = KoXmlNode::DocumentNode; dat->stripSpaces = stripSpaces; d = dat; } const bool result = KOXMLDOCDATA(d)->setContent(reader, errorMsg, errorLine, errorColumn); return result; } // no namespace processing bool KoXmlDocument::setContent(QIODevice* device, QString* errorMsg, int* errorLine, int* errorColumn) { return setContent(device, false, errorMsg, errorLine, errorColumn); } bool KoXmlDocument::setContent(QIODevice* device, bool namespaceProcessing, QString* errorMsg, int* errorLine, int* errorColumn) { if (d->nodeType != KoXmlNode::DocumentNode) { const bool stripSpaces = KOXMLDOCDATA(d)->stripSpaces; d->unref(); KoXmlDocumentData *dat = new KoXmlDocumentData; dat->nodeType = KoXmlNode::DocumentNode; dat->stripSpaces = stripSpaces; d = dat; } if (!device->isOpen()) device->open(QIODevice::ReadOnly); QXmlStreamReader reader(device); reader.setNamespaceProcessing(namespaceProcessing); DumbEntityResolver entityResolver; reader.setEntityResolver(&entityResolver); const bool result = KOXMLDOCDATA(d)->setContent(&reader, errorMsg, errorLine, errorColumn); return result; } bool KoXmlDocument::setContent(const QByteArray& text, bool namespaceProcessing, QString *errorMsg, int *errorLine, int *errorColumn) { QBuffer buffer; buffer.setData(text); return setContent(&buffer, namespaceProcessing, errorMsg, errorLine, errorColumn); } bool KoXmlDocument::setContent(const QString& text, bool namespaceProcessing, QString *errorMsg, int *errorLine, int *errorColumn) { if (d->nodeType != KoXmlNode::DocumentNode) { const bool stripSpaces = KOXMLDOCDATA(d)->stripSpaces; d->unref(); KoXmlDocumentData *dat = new KoXmlDocumentData; dat->nodeType = KoXmlNode::DocumentNode; dat->stripSpaces = stripSpaces; d = dat; } QXmlStreamReader reader(text); reader.setNamespaceProcessing(namespaceProcessing); DumbEntityResolver entityResolver; reader.setEntityResolver(&entityResolver); const bool result = KOXMLDOCDATA(d)->setContent(&reader, errorMsg, errorLine, errorColumn); return result; } bool KoXmlDocument::setContent(const QString& text, QString *errorMsg, int *errorLine, int *errorColumn) { return setContent(text, false, errorMsg, errorLine, errorColumn); } void KoXmlDocument::setWhitespaceStripping(bool stripSpaces) { KOXMLDOCDATA(d)->stripSpaces = stripSpaces; } #endif // ================================================================== // // functions in KoXml namespace // // ================================================================== KoXmlElement KoXml::namedItemNS(const KoXmlNode& node, const QString& nsURI, const QString& localName) { #ifdef KOXML_USE_QDOM // David's solution for namedItemNS, only for QDom stuff KoXmlNode n = node.firstChild(); for (; !n.isNull(); n = n.nextSibling()) { if (n.isElement() && n.localName() == localName && n.namespaceURI() == nsURI) return n.toElement(); } return KoXmlElement(); #else return node.namedItemNS(nsURI, localName).toElement(); #endif } KoXmlElement KoXml::namedItemNS(const KoXmlNode& node, const QString& nsURI, const QString& localName, KoXmlNamedItemType type) { #ifdef KOXML_USE_QDOM Q_ASSERT(false); return namedItemNS(node, nsURI, localName); #else return node.namedItemNS(nsURI, localName, type).toElement(); #endif } void KoXml::load(KoXmlNode& node, int depth) { #ifdef KOXML_USE_QDOM // do nothing, QDom has no on-demand loading Q_UNUSED(node); Q_UNUSED(depth); #else node.load(depth); #endif } void KoXml::unload(KoXmlNode& node) { #ifdef KOXML_USE_QDOM // do nothing, QDom has no on-demand unloading Q_UNUSED(node); #else node.unload(); #endif } int KoXml::childNodesCount(const KoXmlNode& node) { #ifdef KOXML_USE_QDOM return node.childNodes().count(); #else // compatibility function, because no need to implement // a class like QDomNodeList return node.childNodesCount(); #endif } QStringList KoXml::attributeNames(const KoXmlNode& node) { #ifdef KOXML_USE_QDOM QStringList result; QDomNamedNodeMap attrMap = node.attributes(); for (int i = 0; i < attrMap.count(); ++i) result += attrMap.item(i).toAttr().name(); return result; #else // compatibility function, because no need to implement // a class like QDomNamedNodeMap return node.attributeNames(); #endif } void KoXml::asQDomNode(QDomDocument& ownerDoc, const KoXmlNode& node) { Q_ASSERT(!node.isDocument()); #ifdef KOXML_USE_QDOM ownerDoc.appendChild(ownerDoc.importNode(node)); #else node.asQDomNode(ownerDoc); #endif } void KoXml::asQDomElement(QDomDocument &ownerDoc, const KoXmlElement& element) { KoXml::asQDomNode(ownerDoc, element); } QDomDocument KoXml::asQDomDocument(const KoXmlDocument& document) { #ifdef KOXML_USE_QDOM return document; #else QDomDocument qdoc( document.nodeName() ); if ( document.hasChildNodes() ) { for ( KoXmlNode n = document.firstChild(); ! n.isNull(); n = n.nextSibling() ) { KoXml::asQDomNode(qdoc, n); } } return qdoc; #endif } +void KoXml::toQDomDocument(const KoXmlDocument& document, QDomDocument& qdoc) +{ +#ifdef KOXML_USE_QDOM + Q_UNUSED(document); + Q_UNUSED(qdoc) +#else + if ( document.hasChildNodes() ) { + for ( KoXmlNode n = document.firstChild(); ! n.isNull(); n = n.nextSibling() ) { + KoXml::asQDomNode(qdoc, n); + } + } +#endif +} + bool KoXml::setDocument(KoXmlDocument& doc, QIODevice* device, bool namespaceProcessing, QString* errorMsg, int* errorLine, int* errorColumn) { QXmlStreamReader reader(device); reader.setNamespaceProcessing(namespaceProcessing); bool result = doc.setContent(&reader, errorMsg, errorLine, errorColumn); return result; } diff --git a/src/libs/store/KoXmlReader.h b/src/libs/store/KoXmlReader.h index 03a43180..29a9583d 100644 --- a/src/libs/store/KoXmlReader.h +++ b/src/libs/store/KoXmlReader.h @@ -1,469 +1,475 @@ /* This file is part of the KDE project Copyright (C) 2005-2006 Ariya Hidayat 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 KO_XMLREADER_H #define KO_XMLREADER_H // KOXML_USE_QDOM is defined there #include "KoXmlReaderForward.h" #include "kostore_export.h" #include #include #include class QIODevice; #ifdef KOXML_USE_QDOM typedef QDomNode KoXmlNode; typedef QDomElement KoXmlElement; typedef QDomText KoXmlText; typedef QDomCDATASection KoXmlCDATASection; typedef QDomDocumentType KoXmlDocumentType; typedef QDomDocument KoXmlDocument; #else class QString; class QXmlStreamReader; class KoXmlNode; class KoXmlText; class KoXmlCDATASection; class KoXmlDocumentType; class KoXmlDocument; class KoXmlNodeData; class KoXmlDocumentData; /** * The office-text-content-prelude type. */ enum KoXmlNamedItemType { KoXmlTextContentPrelude ///< office-text-content-prelude //KoXmlTextContentMain, ///< office-text-content-main //KoXmlTextContentEpilogue ///< office-text-content-epilogue }; /** * KoXmlNode represents a node in a DOM tree. * * KoXmlNode is a base class for KoXmlElement, KoXmlText. * Often, these subclasses are used for getting the data instead of KoXmlNode. * However, as base class, KoXmlNode is very helpful when for example iterating * all child nodes within one parent node. * * KoXmlNode implements an explicit sharing, a node shares its data with * other copies (if exist). * * XXX: DO NOT ADD CONVENIENCE API HERE BECAUSE THIS CLASS MUST REMAIN COMPATIBLE WITH QDOMNODE! * * @author Ariya Hidayat */ class KOSTORE_EXPORT KoXmlNode { public: enum NodeType { NullNode = 0, ElementNode, TextNode, CDATASectionNode, ProcessingInstructionNode, DocumentNode, DocumentTypeNode }; KoXmlNode(); KoXmlNode(const KoXmlNode& node); KoXmlNode& operator=(const KoXmlNode& node); bool operator== (const KoXmlNode&) const; bool operator!= (const KoXmlNode&) const; virtual ~KoXmlNode(); virtual KoXmlNode::NodeType nodeType() const; virtual bool isNull() const; virtual bool isElement() const; virtual bool isText() const; virtual bool isCDATASection() const; virtual bool isDocument() const; virtual bool isDocumentType() const; virtual void clear(); KoXmlElement toElement() const; KoXmlText toText() const; KoXmlCDATASection toCDATASection() const; KoXmlDocument toDocument() const; virtual QString nodeName() const; virtual QString namespaceURI() const; virtual QString prefix() const; virtual QString localName() const; KoXmlDocument ownerDocument() const; KoXmlNode parentNode() const; bool hasChildNodes() const; KoXmlNode firstChild() const; KoXmlNode lastChild() const; KoXmlNode nextSibling() const; KoXmlNode previousSibling() const; KoXmlElement firstChildElement() const; // equivalent to node.childNodes().count() if node is a QDomNode instance int childNodesCount() const; // workaround to get and iterate over all attributes QStringList attributeNames() const; QList< QPair > attributeFullNames() const; KoXmlNode namedItem(const QString& name) const; KoXmlNode namedItemNS(const QString& nsURI, const QString& name) const; KoXmlNode namedItemNS(const QString& nsURI, const QString& name, KoXmlNamedItemType type) const; /** * Loads all child nodes (if any) of this node. Normally you do not need * to call this function as the child nodes will be automatically * loaded when necessary. */ void load(int depth = 1); /** * Releases all child nodes of this node. */ void unload(); // compatibility /** * @internal do not call directly * Use KoXml::asQDomDocument(), KoXml::asQDomElement() or KoXml::asQDomNode() instead */ void asQDomNode(QDomDocument& ownerDoc) const; protected: KoXmlNodeData* d; explicit KoXmlNode(KoXmlNodeData*); }; /** * KoXmlElement represents a tag element in a DOM tree. * * KoXmlElement holds information about an XML tag, along with its attributes. * * @author Ariya Hidayat */ class KOSTORE_EXPORT KoXmlElement: public KoXmlNode { public: KoXmlElement(); KoXmlElement(const KoXmlElement& element); KoXmlElement& operator=(const KoXmlElement& element); ~KoXmlElement() override; bool operator== (const KoXmlElement&) const; bool operator!= (const KoXmlElement&) const; QString tagName() const; QString text() const; QString attribute(const QString& name) const; QString attribute(const QString& name, const QString& defaultValue) const; QString attributeNS(const QString& namespaceURI, const QString& localName, const QString& defaultValue = QString()) const; bool hasAttribute(const QString& name) const; bool hasAttributeNS(const QString& namespaceURI, const QString& localName) const; private: friend class KoXmlNode; friend class KoXmlDocument; explicit KoXmlElement(KoXmlNodeData*); }; /** * KoXmlText represents a text in a DOM tree. * @author Ariya Hidayat */ class KOSTORE_EXPORT KoXmlText: public KoXmlNode { public: KoXmlText(); KoXmlText(const KoXmlText& text); KoXmlText& operator=(const KoXmlText& text); ~KoXmlText() override; QString data() const; bool isText() const override; private: friend class KoXmlNode; friend class KoXmlCDATASection; friend class KoXmlDocument; explicit KoXmlText(KoXmlNodeData*); }; /** * KoXmlCDATASection represents a CDATA section in a DOM tree. * @author Ariya Hidayat */ class KOSTORE_EXPORT KoXmlCDATASection: public KoXmlText { public: KoXmlCDATASection(); KoXmlCDATASection(const KoXmlCDATASection& cdata); KoXmlCDATASection& operator=(const KoXmlCDATASection& cdata); ~KoXmlCDATASection() override; bool isCDATASection() const override; private: friend class KoXmlNode; friend class KoXmlDocument; explicit KoXmlCDATASection(KoXmlNodeData*); }; /** * KoXmlDocumentType represents the DTD of the document. At the moment, * it can used only to get the document type, i.e. no support for * entities etc. * * @author Ariya Hidayat */ class KOSTORE_EXPORT KoXmlDocumentType: public KoXmlNode { public: KoXmlDocumentType(); KoXmlDocumentType(const KoXmlDocumentType&); KoXmlDocumentType& operator=(const KoXmlDocumentType&); ~KoXmlDocumentType() override; QString name() const; private: friend class KoXmlNode; friend class KoXmlDocument; friend class KoXmlDocumentData; explicit KoXmlDocumentType(KoXmlNodeData*); }; /** * KoXmlDocument represents an XML document, structured in a DOM tree. * * KoXmlDocument is designed to be memory efficient. Unlike QDomDocument from * Qt's XML module, KoXmlDocument does not store all nodes in the DOM tree. * Some nodes will be loaded and parsed on-demand only. * * KoXmlDocument is read-only, you can not modify its content. * * @author Ariya Hidayat */ class KOSTORE_EXPORT KoXmlDocument: public KoXmlNode { public: explicit KoXmlDocument(bool stripSpaces = false); KoXmlDocument(const KoXmlDocument& node); KoXmlDocument& operator=(const KoXmlDocument& node); bool operator==(const KoXmlDocument&) const; bool operator!=(const KoXmlDocument&) const; ~KoXmlDocument() override; KoXmlElement documentElement() const; KoXmlDocumentType doctype() const; QString nodeName() const override; void clear() override; bool setContent(QIODevice* device, bool namespaceProcessing, QString* errorMsg = 0, int* errorLine = 0, int* errorColumn = 0); bool setContent(QIODevice* device, QString* errorMsg = 0, int* errorLine = 0, int* errorColumn = 0); bool setContent(QXmlStreamReader *reader, QString* errorMsg = 0, int* errorLine = 0, int* errorColumn = 0); bool setContent(const QByteArray& text, bool namespaceProcessing, QString *errorMsg = 0, int *errorLine = 0, int *errorColumn = 0); bool setContent(const QString& text, bool namespaceProcessing, QString *errorMsg = 0, int *errorLine = 0, int *errorColumn = 0); // no namespace processing bool setContent(const QString& text, QString *errorMsg = 0, int *errorLine = 0, int *errorColumn = 0); /** * Change the way an XMLDocument will be read: * if stripSpaces = true then a will only have one child * if stripSpaces = false then a will have 3 children. */ void setWhitespaceStripping(bool stripSpaces); private: friend class KoXmlNode; explicit KoXmlDocument(KoXmlDocumentData*); }; #endif // KOXML_USE_QDOM /** * This namespace contains a few convenience functions to simplify code using QDom * (when loading OASIS documents, in particular). * * To find the child element with a given name, use KoXml::namedItemNS. * * To find all child elements with a given name, use * \code * QDomElement e; * forEachElement( e, parent ) * { * if ( e.localName() == "..." && e.namespaceURI() == KoXmlNS::... ) * { * ... * } * } * \endcode * Note that this means you don't ever need to use QDomNode nor toElement anymore! * Also note that localName is the part without the prefix, this is the whole point * of namespace-aware methods. * * To find the attribute with a given name, use QDomElement::attributeNS. * * Do not use getElementsByTagNameNS, it's recursive (which is never needed in Calligra). * Do not use tagName() or nodeName() or prefix(), since the prefix isn't fixed. * * @author David Faure */ namespace KoXml { /** * A namespace-aware version of QDomNode::namedItem(), * which also takes care of casting to a QDomElement. * * Use this when a domelement is known to have only *one* child element * with a given tagname. * * Note: do *NOT* use getElementsByTagNameNS, it's recursive! */ KOSTORE_EXPORT KoXmlElement namedItemNS(const KoXmlNode& node, const QString& nsURI, const QString& localName); /** * A namespace-aware version of QDomNode::namedItem(). * which also takes care of casting to a QDomElement. * * Use this when you like to return the first or an invalid * KoXmlElement with a known type. * * This is an optimized version of the namedItemNS above to * give fast access to certain sections of the document using * the office-text-content-prelude condition as @a KoXmlNamedItemType . */ KOSTORE_EXPORT KoXmlElement namedItemNS(const KoXmlNode& node, const QString& nsURI, const QString& localName, KoXmlNamedItemType type); /** * Explicitly load child nodes of specified node, up to given depth. * This function has no effect if QDom is used. */ KOSTORE_EXPORT void load(KoXmlNode& node, int depth = 1); /** * Unload child nodes of specified node. * This function has no effect if QDom is used. */ KOSTORE_EXPORT void unload(KoXmlNode& node); /** * Get the number of child nodes of specified node. */ KOSTORE_EXPORT int childNodesCount(const KoXmlNode& node); /** * Return the name of all attributes of specified node. */ KOSTORE_EXPORT QStringList attributeNames(const KoXmlNode& node); /** * Convert KoXmlNode classes to the corresponding QDom classes, which has * @p ownerDoc as the owner document (QDomDocument instance). * The converted @p node (and its children) are added to ownerDoc. * * NOTE: * - If ownerDoc is not empty, this may fail, @see QDomDocument * - @p node must not be a KoXmlDocument, use asQDomDocument() * * @see asQDomDocument, asQDomElement */ KOSTORE_EXPORT void asQDomNode(QDomDocument& ownerDoc, const KoXmlNode& node); /** * Convert KoXmlNode classes to the corresponding QDom classes, which has * @p ownerDoc as the owner document (QDomDocument instance). * The converted @p element (and its children) is added to ownerDoc. * * NOTE: If ownerDoc is not empty, this may fail, @see QDomDocument * */ KOSTORE_EXPORT void asQDomElement(QDomDocument& ownerDoc, const KoXmlElement& element); /** * Converts the whole @p document into a QDomDocument * If KOXML_USE_QDOM is defined, just returns @p document */ KOSTORE_EXPORT QDomDocument asQDomDocument(const KoXmlDocument& document); +/** + * Converts the whole @p document into the qdoc + * If KOXML_USE_QDOM is defined, does nothing + */ +KOSTORE_EXPORT void toQDomDocument(const KoXmlDocument& document, QDomDocument &qdoc); + /* * Load an XML document from specified device to a document. You can of * course use it with QFile (which inherits QIODevice). * This is much more memory efficient than standard QDomDocument::setContent * because the data from the device is buffered, unlike * QDomDocument::setContent which just loads everything in memory. * * Note: it is assumed that the XML uses UTF-8 encoding. */ KOSTORE_EXPORT bool setDocument(KoXmlDocument& doc, QIODevice* device, bool namespaceProcessing, QString* errorMsg = 0, int* errorLine = 0, int* errorColumn = 0); } /** * \def forEachElement( elem, parent ) * \brief Loop through all child elements of \p parent. * This convenience macro is used to implement the forEachElement loop. * The \p elem parameter is a name of a QDomElement variable and the \p parent * is the name of the parent element. For example: * * \code * QDomElement e; * forEachElement( e, parent ) * { * kDebug() << e.localName() << " element found."; * ... * } * \endcode */ #define forEachElement( elem, parent ) \ for ( KoXmlNode _node = parent.firstChild(); !_node.isNull(); _node = _node.nextSibling() ) \ if ( ( elem = _node.toElement() ).isNull() ) {} else #endif // KO_XMLREADER_H