diff --git a/plan/kptmaindocument.cpp b/plan/kptmaindocument.cpp index e1e039df27f..6d86b03edda 100644 --- a/plan/kptmaindocument.cpp +++ b/plan/kptmaindocument.cpp @@ -1,1462 +1,1475 @@ /* 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 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. */ #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 "kptcommand.h" #include "calligraplansettings.h" #include "kpttask.h" #include "KPlatoXmlLoader.h" #include "kptpackage.h" #include "kptworkpackagemergedialog.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) { Q_ASSERT(part); setAlwaysAllowSaving(true); m_config.setReadWrite( true ); 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); QTimer::singleShot ( 5000, this, SLOT(autoCheckForWorkPackages()) ); } MainDocument::~MainDocument() { qDeleteAll( m_schedulerPlugins ); if ( m_project ) { m_project->deref(); // deletes if last user } qDeleteAll( m_mergedPackages ); delete m_context; } 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, SIGNAL(pluginLoaded(QString,SchedulerPlugin*)), this, SLOT(addSchedulerPlugin(QString,SchedulerPlugin*))); loader->loadAllPlugins(); } void MainDocument::addSchedulerPlugin( const QString &key, SchedulerPlugin *plugin) { debugPlan<setConfig( m_config ); } void MainDocument::setProject( Project *project ) { if ( m_project ) { disconnect( m_project, SIGNAL(projectChanged()), this, SIGNAL(changed()) ); delete m_project; } m_project = project; if ( m_project ) { connect( m_project, SIGNAL(projectChanged()), this, SIGNAL(changed()) ); // m_project->setConfig( config() ); m_project->setSchedulerPlugins( m_schedulerPlugins ); } m_aboutPage.setProject( project ); emit changed(); } 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 ); 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 ); 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; QDomDocument document( "plan" ); document.appendChild( document.createProcessingInstruction( "xml", "version=\"1.0\" encoding=\"UTF-8\"" ) ); QDomElement doc = document.createElement( "plan" ); doc.setAttribute( "editor", "Plan" ); doc.setAttribute( "mime", "application/x-vnd.kde.plan" ); doc.setAttribute( "version", PLAN_FILE_SYNTAX_VERSION ); document.appendChild( doc ); // Save the project m_project->save( doc ); return document; } QDomDocument MainDocument::saveWorkPackageXML( const Node *node, long id, Resource *resource ) { debugPlan; QDomDocument document( "plan" ); document.appendChild( document.createProcessingInstruction( "xml", "version=\"1.0\" encoding=\"UTF-8\"" ) ); QDomElement doc = document.createElement( "planwork" ); doc.setAttribute( "editor", "Plan" ); doc.setAttribute( "mime", "application/x-vnd.kde.plan.work" ); doc.setAttribute( "version", PLANWORK_FILE_SYNTAX_VERSION ); doc.setAttribute( "plan-version", PLAN_FILE_SYNTAX_VERSION ); document.appendChild( doc ); // Work package info QDomElement wp = document.createElement( "workpackage" ); if ( resource ) { wp.setAttribute( "owner", resource->name() ); wp.setAttribute( "owner-id", resource->id() ); } wp.setAttribute( "time-tag", QDateTime::currentDateTime().toString( Qt::ISODate ) ); 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() ) { warnPlan<<"wrote:"<m_specialOutputFlag == SaveEncrypted ) { backend = KoStore::Encrypted; debugPlan <<"Saving using encrypted backend."; }*/ #endif QByteArray mimeType = "application/x-vnd.kde.plan.work"; debugPlan <<"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() ) { debugPlan <<"saveToStream failed"; delete store; return false; } node->documents().saveToStore( store ); debugPlan <<"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 ) { //debugPlan<<_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 ) { debugPlan<bad() ) { // d->lastErrorMessage = i18n( "Not a valid Calligra file: %1", file ); debugPlan<<"bad store"<open( "root" ) ) { // "old" file format (maindoc.xml) // i18n( "File does not have a maindoc.xml: %1", file ); debugPlan<<"No root"<device(), &errorMsg, &errorLine, &errorColumn ); if ( ! ok ) { errorPlan << "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 ); } 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() ) { debugPlan << "No mime type specified!"; setErrorMessage( i18n( "Invalid document. No mimetype specified." ) ); return 0; } else if ( value == "application/x-vnd.kde.kplato.work" ) { m_xmlLoader.setMimetype( value ); m_xmlLoader.setWorkVersion( plan.attribute( "version", "0.0.0" ) ); proj = new Project( m_config ); KPlatoXmlLoader loader( m_xmlLoader, proj ); ok = loader.loadWorkpackage( plan ); if ( ! ok ) { setErrorMessage( loader.errorMessage() ); delete proj; return 0; } package = loader.package(); package->timeTag = QDateTime::fromString( loader.timeTag(), Qt::ISODate ); } else if ( value != "application/x-vnd.kde.plan.work" ) { debugPlan << "Unknown mime type " << value; setErrorMessage( i18n( "Invalid document. Expected mimetype application/x-vnd.kde.plan.work, got %1", value ) ); return 0; } else { QString syntaxVersion = plan.attribute( "version", "0.0.0" ); m_xmlLoader.setWorkVersion( syntaxVersion ); if ( syntaxVersion > 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" ); //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 ); } debugPlan<<"Task set:"<task->name(); } m_xmlLoader.stopLoad(); } if ( ok && proj->id() == project.id() && proj->childNode( 0 ) ) { ok = project.nodeDict().contains( proj->childNode( 0 )->id() ); if ( ok && m_mergedPackages.contains( package->timeTag ) ) { ok = false; // already merged } if ( ok && package->timeTag.isValid() && ! m_mergedPackages.contains( package->timeTag ) ) { m_mergedPackages[ package->timeTag ] = proj; // register this for next time } if ( ok && ! package->timeTag.isValid() ) { warnPlan<<"Work package is not time tagged:"<childNode( 0 )->name()<url; ok = false; } } if ( ! ok ) { delete proj; delete package; return 0; } Q_ASSERT( package ); 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()<<"->"<numChildren() == 0 ) { return; } m_checkingForWorkPackages = true; if ( ! keep ) { qDeleteAll( m_mergedPackages ); m_mergedPackages.clear(); } QDir dir( m_config.retrieveUrl().path(), "*.planwork" ); m_infoList = dir.entryInfoList( QDir::Files | QDir::Readable, QDir::Time ); checkForWorkPackage(); return; } void MainDocument::checkForWorkPackage() { if ( ! m_infoList.isEmpty() ) { loadWorkPackage( *m_project, QUrl::fromLocalFile( m_infoList.takeLast().absoluteFilePath() ) ); if ( ! m_infoList.isEmpty() ) { QTimer::singleShot ( 0, this, SLOT(checkForWorkPackage()) ); return; } // all files read // remove other projects QMutableMapIterator it( m_workpackages ); while ( it.hasNext() ) { it.next(); Package *package = it.value(); if ( package->project->id() != m_project->id() ) { delete package->project; delete package; it.remove(); } } // Merge our workpackages if ( ! m_workpackages.isEmpty() ) { WorkPackageMergeDialog *dlg = new WorkPackageMergeDialog( i18n( "New work packages detected. Merge data with existing tasks?" ), m_workpackages ); connect(dlg, SIGNAL(finished(int)), SLOT(workPackageMergeDialogFinished(int))); dlg->show(); dlg->raise(); dlg->activateWindow(); } } } void MainDocument::workPackageMergeDialogFinished( int result ) { WorkPackageMergeDialog *dlg = qobject_cast( sender() ); if ( dlg == 0 ) { return; } if ( result == KoDialog::Yes ) { // merge the oldest first foreach( int i, dlg->checkedList() ) { mergeWorkPackage( m_workpackages.values().at( i ) ); } // 'Yes' was hit so terminate all packages foreach( const Package *p, m_workpackages.values() ) { terminateWorkPackage( p ); } } qDeleteAll( m_workpackages ); m_workpackages.clear(); m_checkingForWorkPackages = false; dlg->deleteLater(); } void MainDocument::mergeWorkPackages() { foreach ( Package *package, m_workpackages ) { mergeWorkPackage( package ); } } void MainDocument::terminateWorkPackage( const Package *package ) { QFile file( package->url.path() ); if ( ! file.exists() ) { return; } if ( KPlatoSettings::deleteFile() || KPlatoSettings::saveUrl().isEmpty() ) { file.remove(); } else if ( KPlatoSettings::saveFile() && ! KPlatoSettings::saveUrl().isEmpty() ) { QDir dir( KPlatoSettings::saveUrl().path() ); if ( ! dir.exists() ) { if ( ! dir.mkpath( dir.path() ) ) { //TODO message debugPlan<<"Could not create directory:"<project); if ( proj.id() == m_project->id() && proj.childNode( 0 ) ) { const Task *from = package->task; Task *to = package->toTask; if ( to && from ) { mergeWorkPackage( to, from, package ); } } } void MainDocument::mergeWorkPackage( Task *to, const Task *from, const Package *package ) { Resource *resource = m_project->findResource( package->ownerId ); if ( resource == 0 ) { KMessageBox::error( 0, i18n( "The package owner '%1' is not a resource in this project. You must handle this manually.", package->ownerName ) ); return; } MacroCommand *cmd = new MacroCommand( kundo2_noi18n("Merge workpackage") ); Completion &org = to->completion(); const Completion &curr = from->completion(); if ( package->settings.progress ) { if ( org.isStarted() != curr.isStarted() ) { cmd->addCommand( new ModifyCompletionStartedCmd(org, curr.isStarted() ) ); } if ( org.isFinished() != curr.isFinished() ) { cmd->addCommand( new ModifyCompletionFinishedCmd( org, curr.isFinished() ) ); } if ( org.startTime() != curr.startTime() ) { cmd->addCommand( new ModifyCompletionStartTimeCmd( org, curr.startTime() ) ); } if ( org.finishTime() != curr.finishTime() ) { cmd->addCommand( new ModifyCompletionFinishTimeCmd( org, curr.finishTime() ) ); } // TODO: review how/if to merge data from different resources // remove entries foreach ( const QDate &d, org.entries().keys() ) { if ( ! curr.entries().contains( d ) ) { debugPlan<<"remove entry "<addCommand( new RemoveCompletionEntryCmd( org, d ) ); } } // add new entries / modify existing foreach ( const QDate &d, curr.entries().keys() ) { if ( org.entries().contains( d ) && curr.entry( d ) == org.entry( d ) ) { continue; } Completion::Entry *e = new Completion::Entry( *( curr.entry( d ) ) ); cmd->addCommand( new ModifyCompletionEntryCmd( org, d, e ) ); } } if ( package->settings.usedEffort ) { Completion::UsedEffort *ue = new Completion::UsedEffort(); Completion::Entry prev; Completion::EntryList::ConstIterator entriesIt = curr.entries().constBegin(); const Completion::EntryList::ConstIterator entriesEnd = curr.entries().constEnd(); for (; entriesIt != entriesEnd; ++entriesIt) { const QDate &d = entriesIt.key(); const Completion::Entry &e = *entriesIt.value(); // set used effort from date entry and remove used effort from date entry Completion::UsedEffort::ActualEffort effort( e.totalPerformed - prev.totalPerformed ); ue->setEffort( d, effort ); prev = e; } cmd->addCommand( new AddCompletionUsedEffortCmd( org, resource, ue ) ); } bool docsaved = false; if ( package->settings.documents ) { //TODO: handle remote files QMap::const_iterator it = package->documents.constBegin(); QMap::const_iterator end = package->documents.constEnd(); for ( ; it != end; ++it ) { const QUrl src = QUrl::fromLocalFile(it.key()); KIO::CopyJob *job = KIO::move( src, it.value(), KIO::Overwrite ); if ( job->exec() ) { docsaved = true; //TODO: async debugPlan<<"Moved file:"<isEmpty() ) { KMessageBox::information( 0, i18n( "Nothing to save from this package" ) ); } // add a copy to our tasks list of transmitted packages WorkPackage *wp = new WorkPackage( from->workPackage() ); wp->setParentTask( to ); if ( ! wp->transmitionTime().isValid() ) { wp->setTransmitionTime( package->timeTag ); } wp->setTransmitionStatus( WorkPackage::TS_Receive ); cmd->addCommand( new WorkPackageAddCmd( m_project, to, wp ) ); addCommand( cmd ); } void MainDocument::paintContent( QPainter &, const QRect &) { // Don't embed this app!!! } void MainDocument::slotViewDestroyed() { } void MainDocument::setLoadingTemplate(bool loading) { m_loadingTemplate = loading; } void MainDocument::setLoadingSharedResourcesTemplate(bool loading) { m_loadingSharedResourcesTemplate = loading; } bool MainDocument::completeLoading( KoStore *store ) { // If we get here the new project is loaded and set if (m_loadingSharedProject) { // this file is loaded by another project // to read resource appointments, // so we must not load any extra stuff return true; } if ( m_loadingTemplate ) { //debugPlan<<"Loading template, generate unique ids"; m_project->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()) { QUrl url = QUrl::fromLocalFile(m_project->sharedResourcesFile()); if (url.isValid()) { insertResourcesFile(url, m_project->sharedProjectsUrl()); } } 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 simmulate 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 ) { 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, SIGNAL(completed()), SLOT(insertFileCompleted())); connect(doc, SIGNAL(canceled(QString)), SLOT(insertFileCancelled(QString))); 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 ); part->setDocument( doc ); doc->disconnect(); // doc shall not handle feedback from openUrl() doc->setAutoSave( 0 ); //disable connect(doc, SIGNAL(completed()), SLOT(insertResourcesFileCompleted())); connect(doc, SIGNAL(canceled(QString)), SLOT(insertFileCancelled(QString))); doc->openUrl( url ); } void MainDocument::insertResourcesFileCompleted() { debugPlan<( 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::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:"<resourceList()) { r->clearExternalAppointments(); } } void MainDocument::slotInsertSharedProject() { debugPlan<setDocument( doc ); doc->disconnect(); // doc shall not handle feedback from openUrl() doc->setAutoSave( 0 ); //disable doc->m_loadingSharedProject = true; connect(doc, SIGNAL(completed()), SLOT(insertSharedProjectCompleted())); connect(doc, SIGNAL(canceled(QString)), SLOT(insertSharedProjectCancelled(QString))); doc->openUrl(m_sharedProjectsFiles.takeFirst()); } void MainDocument::insertSharedProjectCompleted() { debugPlan<( sender() ); if (doc) { Project &p = doc->getProject(); debugPlan<id()<<"Loaded project:"<id()) { - for (Resource *r : p.resourceList()) { - Resource *res = m_project->resource(r->id()); - debugPlan<<"Resource:"<name()<<"->"<isShared()) { - for (const Appointment *a : r->appointments()) { - Appointment *app = new Appointment(*a); - app->setAuxcilliaryInfo(p.name()); - res->addExternalAppointment(p.id(), app); - debugPlan<name()<<"added:"<auxcilliaryInfo()<id() && p.isScheduled(ANYSCHEDULED)) { + // FIXME: improve! + // find a suitable schedule + ScheduleManager *sm = 0; + for (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) { + for (Resource *r : p.resourceList()) { + Resource *res = m_project->resource(r->id()); + if (res && res->isShared()) { + for (const Appointment *a : r->appointments(sm->scheduleId())) { + Appointment *app = new Appointment(*a); + app->setAuxcilliaryInfo(p.name()); + res->addExternalAppointment(p.id(), app); + debugPlan<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 ) { debugPlan<( 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) { debugPlan<<&project; // Just in case, remove stuff not related to resources for (Node *n : project.childNodeIterator()) { delete n; } for (ScheduleManager *m : project.allScheduleManagers()) { delete m; } // Mark all resources / groups as shared for (ResourceGroup *g : project.resourceGroups()) { g->setShared(true); } for (Resource *r : project.resourceList()) { r->setShared(true); } // Mark all calendars shared for (Calendar *c : project.allCalendars()) { c->setShared(true); } // check if any shared stuff has been removed QList removedGroups; QList removedResources; QList removedCalendars; QStringList removed; for (ResourceGroup *g : m_project->resourceGroups()) { if (g->isShared() && !project.findResourceGroup(g->id())) { removedGroups << g; removed << i18n("Group: %1", g->name()); } } for (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 : 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 : removedResources) { RemoveResourceCmd cmd(r->parentGroup(), r); cmd.redo(); } for (ResourceGroup *g : 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 : removedCalendars) { CalendarRemoveCmd cmd(m_project, c); cmd.redo(); } break; case KMessageBox::No: // Convert for (Resource *r : removedResources) { r->setShared(false); m_project->removeResourceId(r->id()); r->setId(m_project->uniqueResourceId()); m_project->insertResourceId(r->id(), r); } for (ResourceGroup *g : removedGroups) { g->setShared(false); m_project->removeResourceGroupId(g->id()); g->setId(m_project->uniqueResourceGroupId()); m_project->insertResourceGroupId(g->id(), g); } for (Calendar *c : 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 existsing objects for (ResourceGroup *g : project.resourceGroups()) { ResourceGroup *group = m_project->findResourceGroup(g->id()); if (group) { group->setName(g->name()); group->setType(g->type()); } } for (Resource *r : project.resourceList()) { Resource *resource = m_project->findResource(r->id()); if (resource) { 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()); } } for (Calendar *c : project.allCalendars()) { Calendar *calendar = m_project->findCalendar(c->id()); if (calendar) { *calendar = *c; } } // insert new objects 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<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 *c = 0; if (inweek) { c = week; qDebug()<addCalendar(c, week); qDebug()<addCalendar(c); qDebug()<setHolidayRegion(KPlatoSettings::region()); } #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->generateUniqueIds(); 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().values() ) { r->takeSchedule( s ); delete s; } } } } //KPlato namespace