diff --git a/src/workpackage/taskworkpackagemodel.cpp b/src/workpackage/taskworkpackagemodel.cpp index 4010f305..7fde4bc5 100644 --- a/src/workpackage/taskworkpackagemodel.cpp +++ b/src/workpackage/taskworkpackagemodel.cpp @@ -1,710 +1,714 @@ /* This file is part of the KDE project Copyright (C) 2009, 2011, 2012 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 "taskworkpackagemodel.h" #include "part.h" #include "workpackage.h" #include "kptglobal.h" #include "kptresource.h" #include "kptproject.h" #include "kpttask.h" #include "kptcommand.h" #include "kptitemmodelbase.h" #include "kpttaskcompletedelegate.h" #include #include #include #include #include #include "debugarea.h" using namespace KPlato; namespace KPlatoWork { TaskWorkPackageModel::TaskWorkPackageModel( Part *part, QObject *parent ) : ItemModelBase( parent ), m_part( part ) { connect( part, &Part::workPackageAdded, this, &TaskWorkPackageModel::addWorkPackage ); connect( part, &Part::workPackageRemoved, this, &TaskWorkPackageModel::removeWorkPackage ); } Qt::ItemFlags TaskWorkPackageModel::flags( const QModelIndex &index ) const { Qt::ItemFlags flags = QAbstractItemModel::flags( index ); flags &= ~( Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled ); Node *n = nodeForIndex( index ); if ( n == 0 ) { return flags; } if ( n->type() != Node::Type_Task && n->type() != Node::Type_Milestone ) { return flags; } Task *t = static_cast( n ); if ( ! t->completion().isStarted() ) { switch ( index.column() ) { case NodeActualStart: flags |= Qt::ItemIsEditable; break; case NodeCompleted: flags |= Qt::ItemIsEditable; break; default: break; } } else if ( ! t->completion().isFinished() ) { // task is running switch ( index.column() ) { case NodeActualFinish: case NodeCompleted: case NodeRemainingEffort: case NodeActualEffort: flags |= Qt::ItemIsEditable; break; default: break; } } return flags; } void TaskWorkPackageModel::slotNodeToBeInserted( Node *parent, int row ) { //debugPlanWork<name()<<"; "<parentNode()->name()<<"-->"<name(); endInsertRows(); } void TaskWorkPackageModel::slotNodeToBeRemoved( Node *node ) { //debugPlanWork<name(); int row = indexForNode( node ).row(); beginRemoveRows( indexForNode( node->parentNode() ), row, row ); } void TaskWorkPackageModel::slotNodeRemoved( Node */*node*/ ) { //debugPlanWork<name(); endRemoveRows(); } void TaskWorkPackageModel::slotNodeChanged( Node *node ) { if ( node == 0 || node->type() == Node::Type_Project ) { return; } int row = indexForNode( node ).row(); debugPlanWork<name()<parentNode() ), createIndex( row, columnCount()-1, node->parentNode() ) ); } void TaskWorkPackageModel::slotDocumentAdded( Node *node, Document */*doc*/, int row ) { QModelIndex parent = indexForNode( node ); if ( parent.isValid() ) { beginInsertRows( parent, row, row ); endInsertRows(); } } void TaskWorkPackageModel::slotDocumentRemoved( Node *node, Document */*doc*/, int row ) { QModelIndex parent = indexForNode( node ); if ( parent.isValid() ) { beginRemoveRows( parent, row, row ); endRemoveRows(); } } void TaskWorkPackageModel::slotDocumentChanged( Node *node, Document */*doc*/, int row ) { QModelIndex parent = indexForNode( node ); if ( parent.isValid() ) { emit dataChanged( index( row, 0, parent ), index( row, columnCount( parent ), parent ) ); } } void TaskWorkPackageModel::addWorkPackage( WorkPackage *package, int row ) { beginInsertRows( QModelIndex(), row, row ); Project *project = package->project(); endInsertRows(); if ( project ) { connect( project, &KPlato::Project::nodeChanged, this, &TaskWorkPackageModel::slotNodeChanged ); connect( project, &KPlato::Project::nodeToBeAdded, this, &TaskWorkPackageModel::slotNodeToBeInserted ); connect( project, &KPlato::Project::nodeToBeRemoved, this, &TaskWorkPackageModel::slotNodeToBeRemoved ); connect( project, &KPlato::Project::nodeAdded, this, &TaskWorkPackageModel::slotNodeInserted ); connect( project, &KPlato::Project::nodeRemoved, this, &TaskWorkPackageModel::slotNodeRemoved ); connect(project, &KPlato::Project::documentAdded, this, &TaskWorkPackageModel::slotDocumentAdded); connect(project, &KPlato::Project::documentRemoved, this, &TaskWorkPackageModel::slotDocumentRemoved); connect(project, &KPlato::Project::documentChanged, this, &TaskWorkPackageModel::slotDocumentChanged); } } void TaskWorkPackageModel::removeWorkPackage( WorkPackage *package, int row ) { beginRemoveRows( QModelIndex(), row, row ); Project *project = package->project(); debugPlanWork<project(); if ( project ) { disconnect( project, &KPlato::Project::nodeChanged, this, &TaskWorkPackageModel::slotNodeChanged ); disconnect( project, &KPlato::Project::nodeToBeAdded, this, &TaskWorkPackageModel::slotNodeToBeInserted ); disconnect( project, &KPlato::Project::nodeToBeRemoved, this, &TaskWorkPackageModel::slotNodeToBeRemoved ); disconnect( project, &KPlato::Project::nodeAdded, this, &TaskWorkPackageModel::slotNodeInserted ); disconnect( project, &KPlato::Project::nodeRemoved, this, &TaskWorkPackageModel::slotNodeRemoved ); disconnect(project, &KPlato::Project::documentAdded, this, &TaskWorkPackageModel::slotDocumentAdded); disconnect(project, &KPlato::Project::documentRemoved, this, &TaskWorkPackageModel::slotDocumentRemoved); disconnect(project, &KPlato::Project::documentChanged, this, &TaskWorkPackageModel::slotDocumentChanged); } endRemoveRows(); } QVariant TaskWorkPackageModel::name( const Resource *r, int role ) const { switch ( role ) { case Qt::DisplayRole: case Qt::EditRole: case Qt::ToolTipRole: return r->name(); case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant TaskWorkPackageModel::email( const Resource *r, int role ) const { switch ( role ) { case Qt::DisplayRole: case Qt::EditRole: case Qt::ToolTipRole: return r->email(); case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant TaskWorkPackageModel::projectName( const Node *node, int role ) const { switch ( role ) { case Qt::DisplayRole: case Qt::EditRole: case Qt::ToolTipRole: { const Node *proj = node->projectNode(); return proj == 0 ? QVariant() : proj->name(); } case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant TaskWorkPackageModel::projectManager( const Node *node, int role ) const { switch ( role ) { case Qt::DisplayRole: case Qt::EditRole: case Qt::ToolTipRole: { const Node *proj = node->projectNode(); return proj == 0 ? QVariant() : proj->leader(); } case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } int TaskWorkPackageModel::rowCount( const QModelIndex &parent ) const { if ( ! parent.isValid() ) { //debugPlanWork<workPackageCount(); return m_part->workPackageCount(); // == no of nodes (1 node pr wp) } Node *n = nodeForIndex( parent ); if ( n ) { //debugPlanWork<documents().count(); return n->documents().count(); } //debugPlanWork<%2%3", wp->wbsCode(), n->name(), nodeData(n, NodeDescription, Qt::DisplayRole).toString()); + } return nodeData( n, index.column(), role ); } Document *doc = documentForIndex( index ); if ( doc ) { return documentData( doc, index.column(), role ); } return QVariant(); } QVariant TaskWorkPackageModel::actualStart( Node *n, int role ) const { QVariant v = m_nodemodel.startedTime( n, role ); if ( role == Qt::EditRole && ! v.toDateTime().isValid() ) { v = QDateTime::currentDateTime(); } return v; } QVariant TaskWorkPackageModel::actualFinish( Node *n, int role ) const { QVariant v = m_nodemodel.finishedTime( n, role ); if ( role == Qt::EditRole && ! v.toDateTime().isValid() ) { v = QDateTime::currentDateTime(); } return v; } QVariant TaskWorkPackageModel::plannedEffort( Node *n, int role ) const { switch ( role ) { case Qt::DisplayRole: case Qt::ToolTipRole: { Duration v = n->plannedEffort( CURRENTSCHEDULE, ECCT_EffortWork ); return v.format(); } default: break; } return QVariant(); } QVariant TaskWorkPackageModel::status( Node *n, int role ) const { return m_nodemodel.status( n, role ); } QVariant TaskWorkPackageModel::nodeData( Node *n, int column, int role ) const { if ( role >= Qt::UserRole ) { // debugPlanWork<name()<type() ) { case Node::Type_Task: return KGantt::TypeTask; default: break; } break; case KGantt::StartTimeRole: debugPlanWork<name()<<"start:"<startTime(); return m_nodemodel.data( n, NodeModel::NodeStartTime, Qt::EditRole ); case KGantt::EndTimeRole: debugPlanWork<name()<<"end:"<endTime(); return m_nodemodel.data( n, NodeModel::NodeEndTime, Qt::EditRole ); default: break; } } switch ( column ) { case NodeName: return m_nodemodel.data( n, NodeModel::NodeName, role ); case NodeType: return m_nodemodel.data( n, NodeModel::NodeType, role ); case NodeResponsible: return m_nodemodel.data( n, NodeModel::NodeResponsible, role ); case NodeDescription: return m_nodemodel.data( n, NodeModel::NodeDescription, role ); // After scheduling case NodeStartTime: return m_nodemodel.data( n, NodeModel::NodeStartTime, role ); case NodeEndTime: return m_nodemodel.data( n, NodeModel::NodeEndTime, role ); case NodeAssignments: return m_nodemodel.data( n, NodeModel::NodeAssignments, role ); // Completion case NodeCompleted: return m_nodemodel.data( n, NodeModel::NodeCompleted, role ); case NodeActualEffort: return m_nodemodel.data( n, NodeModel::NodeActualEffort, role ); case NodeRemainingEffort: return m_nodemodel.data( n, NodeModel::NodeRemainingEffort, role ); case NodePlannedEffort: return plannedEffort( n, role ); case NodeActualStart: return actualStart( n, role ); case NodeStarted: return m_nodemodel.data( n, NodeModel::NodeStarted, role ); case NodeActualFinish: return actualFinish( n, role ); case NodeFinished: return m_nodemodel.data( n, NodeModel::NodeFinished, role ); case NodeStatus: return status( n, role ); case NodeStatusNote: return m_nodemodel.data( n, NodeModel::NodeStatusNote, role ); case ProjectName: return projectName( n, role ); case ProjectManager: return projectManager( n, role ); default: //debugPlanWork<<"Invalid column number: "<url().fileName()<name(); case NodeType: return doc->typeToString( doc->type(), true ); case NodeStatusNote: return doc->status(); default: return ""; } } else if ( role == Qt::ToolTipRole ) { switch ( column ) { case NodeName: return doc->typeToString( doc->type(), true ); default: break; } } return QVariant(); } bool TaskWorkPackageModel::setCompletion( Node *node, const QVariant &value, int role ) { if ( role != Qt::EditRole ) { return false; } if ( node->type() == Node::Type_Task ) { Completion &c = static_cast( node )->completion(); QDate date = qMax( c.entryDate(), QDate::currentDate() ); QDateTime dt( date, QTime::currentTime() ); // xgettext: no-c-format MacroCommand *m = new MacroCommand( kundo2_i18n( "Modify completion" ) ); if ( ! c.isStarted() ) { m->addCommand( new ModifyCompletionStartedCmd( c, true ) ); m->addCommand( new ModifyCompletionStartTimeCmd( c, dt ) ); } m->addCommand( new ModifyCompletionPercentFinishedCmd( c, date, value.toInt() ) ); if ( value.toInt() == 100 ) { m->addCommand( new ModifyCompletionFinishedCmd( c, true ) ); m->addCommand( new ModifyCompletionFinishTimeCmd( c, dt ) ); } bool newentry = c.entryDate() < date; emit executeCommand( m ); // also adds a new entry if necessary if ( newentry ) { // new entry so calculate used/remaining based on completion Duration planned = static_cast( node )->plannedEffort( m_nodemodel.id() ); Duration actual = ( planned * value.toInt() ) / 100; debugPlanWork<execute(); m->addCommand( cmd ); cmd = new ModifyCompletionRemainingEffortCmd( c, date, planned - actual ); cmd->execute(); m->addCommand( cmd ); } else if ( c.isFinished() && c.remainingEffort() != 0 ) { ModifyCompletionRemainingEffortCmd *cmd = new ModifyCompletionRemainingEffortCmd( c, date, Duration::zeroDuration ); cmd->execute(); m->addCommand( cmd ); } return true; } if ( node->type() == Node::Type_Milestone ) { Completion &c = static_cast( node )->completion(); if ( value.toInt() > 0 ) { QDateTime dt = QDateTime::currentDateTime(); QDate date = dt.date(); MacroCommand *m = new MacroCommand( kundo2_i18n( "Set finished" ) ); m->addCommand( new ModifyCompletionStartedCmd( c, true ) ); m->addCommand( new ModifyCompletionStartTimeCmd( c, dt ) ); m->addCommand( new ModifyCompletionFinishedCmd( c, true ) ); m->addCommand( new ModifyCompletionFinishTimeCmd( c, dt ) ); m->addCommand( new ModifyCompletionPercentFinishedCmd( c, date, 100 ) ); emit executeCommand( m ); // also adds a new entry if necessary return true; } return false; } return false; } bool TaskWorkPackageModel::setRemainingEffort( Node *node, const QVariant &value, int role ) { if ( role == Qt::EditRole && node->type() == Node::Type_Task ) { Task *t = static_cast( node ); double d( value.toList()[0].toDouble() ); Duration::Unit unit = static_cast( value.toList()[1].toInt() ); Duration dur( d, unit ); emit executeCommand( new ModifyCompletionRemainingEffortCmd( t->completion(), QDate::currentDate(), dur, kundo2_i18n( "Modify remaining effort" ) ) ); return true; } return false; } bool TaskWorkPackageModel::setActualEffort( Node *node, const QVariant &value, int role ) { if ( role == Qt::EditRole && node->type() == Node::Type_Task ) { Task *t = static_cast( node ); double d( value.toList()[0].toDouble() ); Duration::Unit unit = static_cast( value.toList()[1].toInt() ); Duration dur( d, unit ); emit executeCommand( new ModifyCompletionActualEffortCmd( t->completion(), QDate::currentDate(), dur, kundo2_i18n( "Modify actual effort" ) ) ); return true; } return false; } bool TaskWorkPackageModel::setStartedTime( Node *node, const QVariant &value, int role ) { switch ( role ) { case Qt::EditRole: { Task *t = qobject_cast( node ); if ( t == 0 ) { return false; } MacroCommand *m = new MacroCommand( kundo2_noi18n(headerData( NodeModel::NodeActualStart, Qt::Horizontal, Qt::DisplayRole ).toString()) ); //FIXME: proper description when string freeze is lifted if ( ! t->completion().isStarted() ) { m->addCommand( new ModifyCompletionStartedCmd( t->completion(), true ) ); } m->addCommand( new ModifyCompletionStartTimeCmd( t->completion(), value.toDateTime() ) ); if ( t->type() == Node::Type_Milestone ) { m->addCommand( new ModifyCompletionFinishedCmd( t->completion(), true ) ); m->addCommand( new ModifyCompletionFinishTimeCmd( t->completion(), value.toDateTime() ) ); if ( t->completion().percentFinished() < 100 ) { Completion::Entry *e = new Completion::Entry( 100, Duration::zeroDuration, Duration::zeroDuration ); m->addCommand( new AddCompletionEntryCmd( t->completion(), value.toDate(), e ) ); } } emit executeCommand( m ); return true; } } return false; } bool TaskWorkPackageModel::setFinishedTime( Node *node, const QVariant &value, int role ) { switch ( role ) { case Qt::EditRole: { Task *t = qobject_cast( node ); if ( t == 0 ) { return false; } MacroCommand *m = new MacroCommand( kundo2_noi18n(headerData( NodeModel::NodeActualFinish, Qt::Horizontal, Qt::DisplayRole ).toString()) ); //FIXME: proper description when string freeze is lifted if ( ! t->completion().isFinished() ) { m->addCommand( new ModifyCompletionFinishedCmd( t->completion(), true ) ); if ( t->completion().percentFinished() < 100 ) { QDate lastdate = t->completion().entryDate(); if ( ! lastdate.isValid() || lastdate < value.toDate() ) { Completion::Entry *e = new Completion::Entry( 100, Duration::zeroDuration, Duration::zeroDuration ); m->addCommand( new AddCompletionEntryCmd( t->completion(), value.toDate(), e ) ); } else { Completion::Entry *e = new Completion::Entry( *( t->completion().entry( lastdate ) ) ); e->percentFinished = 100; m->addCommand( new ModifyCompletionEntryCmd( t->completion(), lastdate, e ) ); } } } m->addCommand( new ModifyCompletionFinishTimeCmd( t->completion(), value.toDateTime() ) ); if ( t->type() == Node::Type_Milestone ) { m->addCommand( new ModifyCompletionStartedCmd( t->completion(), true ) ); m->addCommand( new ModifyCompletionStartTimeCmd( t->completion(), value.toDateTime() ) ); } emit executeCommand( m ); return true; } } return false; } bool TaskWorkPackageModel::setData( const QModelIndex &index, const QVariant &value, int role ) { if ( ! index.isValid() ) { return ItemModelBase::setData( index, value, role ); } switch ( index.column() ) { case NodeCompleted: return setCompletion( nodeForIndex( index ), value, role ); case NodeRemainingEffort: return setRemainingEffort( nodeForIndex( index ), value, role ); case NodeActualEffort: return setActualEffort( nodeForIndex( index ), value, role ); case NodeActualStart: return setStartedTime( nodeForIndex( index ), value, role ); case NodeActualFinish: return setFinishedTime( nodeForIndex( index ), value, role ); default: break; } return false; } QVariant TaskWorkPackageModel::headerData( int section, Qt::Orientation orientation, int role ) const { if ( orientation == Qt::Vertical ) { return section; } if ( role == Qt::DisplayRole ) { switch ( section ) { case NodeName: return i18n( "Name" ); case NodeType: return i18n( "Type" ); case NodeResponsible: return i18n( "Responsible" ); case NodeDescription: return i18n( "Description" ); // After scheduling case NodeStartTime: return i18n( "Planned Start" ); case NodeEndTime: return i18n( "Planned Finish" ); case NodeAssignments: return i18n( "Resource Assignments" ); // Completion case NodeCompleted: return i18n( "Completion" ); case NodeActualEffort: return i18n( "Actual Effort" ); case NodeRemainingEffort: return i18n( "Remaining Effort" ); case NodePlannedEffort: return i18n( "Planned Effort" ); case NodeActualStart: return i18n( "Actual Start" ); case NodeStarted: return i18n( "Started" ); case NodeActualFinish: return i18n( "Actual Finish" ); case NodeFinished: return i18n( "Finished" ); case NodeStatus: return i18nc( "@title:column", "Status" ); case NodeStatusNote: return i18n( "Note" ); case ProjectName: return i18n( "Project Name" ); case ProjectManager: return i18n( "Project Manager" ); default: //debugPlanWork<<"Invalid column number: "<node()->name(); return wp->node(); } return 0; } Document *TaskWorkPackageModel::documentForIndex( const QModelIndex &index ) const { if ( index.isValid() ) { Node *parent = ptrToNode( index ); if ( parent && index.row() < parent->documents().count() ) { //debugPlanWork<name(); return parent->documents().value( index.row() ); } } return 0; } QModelIndex TaskWorkPackageModel::indexForNode( Node *node ) const { WorkPackage *p = m_part->workPackage( node ); if ( p == 0 ) { return QModelIndex(); } return createIndex( m_part->indexOf( p ), 0, p ); } WorkPackage *TaskWorkPackageModel::workPackage( int index ) const { return m_part->workPackage( index ); } QAbstractItemDelegate *TaskWorkPackageModel::createDelegate( int column, QWidget *parent ) const { switch ( column ) { case NodeCompleted: return new TaskCompleteDelegate( parent ); case NodeRemainingEffort: return new DurationSpinBoxDelegate( parent ); case NodeActualEffort: return new DurationSpinBoxDelegate( parent ); case NodeActualStart: return new DateTimeCalendarDelegate( parent ); case NodeActualFinish: return new DateTimeCalendarDelegate( parent ); default: break; } return 0; } WorkPackage *TaskWorkPackageModel::ptrToWorkPackage( const QModelIndex &idx ) const { return qobject_cast( static_cast( idx.internalPointer() ) ); } Node *TaskWorkPackageModel::ptrToNode( const QModelIndex &idx ) const { return qobject_cast( static_cast( idx.internalPointer() ) ); } bool TaskWorkPackageModel::isNode( const QModelIndex &idx ) const { // a node index: ptr is WorkPackage* return qobject_cast( static_cast( idx.internalPointer() ) ) != 0; } bool TaskWorkPackageModel::isDocument( const QModelIndex &idx ) const { // a document index: ptr is Node* return qobject_cast( static_cast( idx.internalPointer() ) ) != 0; } } //namespace KPlato diff --git a/src/workpackage/workpackage.cpp b/src/workpackage/workpackage.cpp index 6e916687..20900945 100644 --- a/src/workpackage/workpackage.cpp +++ b/src/workpackage/workpackage.cpp @@ -1,834 +1,862 @@ /* This file is part of the KDE project Copyright (C) 2009, 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 "workpackage.h" #include "KPlatoXmlLoader.h" //NOTE: this file should probably be moved #include "part.h" #include "kptglobal.h" #include "kptnode.h" #include "kptproject.h" #include "kptdocuments.h" #include "kptcommand.h" #include "kptxmlloaderobject.h" #include "XmlSaveContext.h" #include "kptconfigbase.h" #include "kptcommonstrings.h" #include #include #include #include #include #include #include #include #include #include #include "debugarea.h" using namespace KPlato; namespace KPlatoWork { WorkPackage::WorkPackage( bool fromProjectStore ) : m_project( new Project() ), m_fromProjectStore( fromProjectStore ), m_modified( false) { m_project->setConfig( &m_config ); } WorkPackage::WorkPackage( Project *project, bool fromProjectStore ) : m_project( project ), m_fromProjectStore( fromProjectStore ), m_modified( false) { Q_ASSERT( project ); Q_ASSERT ( project->childNode( 0 ) ); m_project->setConfig( &m_config ); if ( ! project->scheduleManagers().isEmpty() ) { // should be only one manager, so just get the first const QList &lst = m_project->scheduleManagers(); project->setCurrentSchedule(lst.first()->scheduleId()); } connect( project, &KPlato::Project::projectChanged, this, &WorkPackage::projectChanged ); } WorkPackage::~WorkPackage() { delete m_project; qDeleteAll( m_childdocs ); } void WorkPackage::setSettings( const WorkPackageSettings &settings ) { if ( m_settings != settings ) { m_settings = settings; setModified( true ); } } //TODO find a way to know when changes are undone void WorkPackage::projectChanged() { debugPlanWork; setModified( true ); } bool WorkPackage::addChild( Part */*part*/, const Document *doc ) { DocumentChild *ch = findChild( doc ); if ( ch ) { if ( ch->isOpen() ) { KMessageBox::error( 0, i18n( "Document is already open" ) ); return false; } } else { ch = new DocumentChild( this ); if ( ! ch->setDoc( doc ) ) { delete ch; return false; } } if ( ! ch->editDoc() ) { delete ch; return false; } if ( ! m_childdocs.contains( ch ) ) { m_childdocs.append( ch ); connect( ch, &DocumentChild::fileModified, this, &WorkPackage::slotChildModified ); } return true; } void WorkPackage::slotChildModified( bool mod ) { debugPlanWork<documents().contains( doc ) : false; } DocumentChild *WorkPackage::findChild( const Document *doc ) const { foreach ( DocumentChild *c, m_childdocs ) { if ( c->doc() == doc ) { return c; } } return 0; } bool WorkPackage::loadXML( const KoXmlElement &element, XMLLoaderObject &status ) { bool ok = false; + QString wbsCode = "Unknown"; KoXmlNode n = element.firstChild(); for ( ; ! n.isNull(); n = n.nextSibling() ) { if ( ! n.isElement() ) { continue; } KoXmlElement e = n.toElement(); debugPlanWork<load( e, status ) ) ) { status.addMsg( XMLLoaderObject::Errors, "Loading of work package failed" ); KMessageBox::error( 0, i18n( "Failed to load project: %1" , m_project->name() ) ); + } else { + KoXmlElement te = e.namedItem("task").toElement(); + if (!te.isNull()) { + wbsCode = te.attribute("wbs", "empty"); + } } } } if ( ok ) { KoXmlNode n = element.firstChild(); for ( ; ! n.isNull(); n = n.nextSibling() ) { if ( ! n.isElement() ) { continue; } KoXmlElement e = n.toElement(); debugPlanWork<( m_project->childNode( 0 ) ); t->workPackage().setOwnerName( e.attribute( "owner" ) ); t->workPackage().setOwnerId( e.attribute( "owner-id" ) ); m_sendUrl = QUrl(e.attribute("save-url")); m_fetchUrl = QUrl(e.attribute("load-url")); + m_wbsCode = wbsCode; Resource *r = m_project->findResource( t->workPackage().ownerId() ); if ( r == 0 ) { debugPlanWork<<"Cannot find resource id!!"<workPackage().ownerId()<workPackage().ownerName(); } debugPlanWork<<"is this me?"<workPackage().ownerName(); KoXmlNode ch = e.firstChild(); for ( ; ! ch.isNull(); ch = ch.nextSibling() ) { if ( ! ch.isElement() ) { continue; } KoXmlElement el = ch.toElement(); debugPlanWork<scheduleManagers().isEmpty() ) { // should be only one manager const QList &lst = m_project->scheduleManagers(); m_project->setCurrentSchedule(lst.first()->scheduleId()); } return ok; } bool WorkPackage::loadKPlatoXML( const KoXmlElement &element, XMLLoaderObject &status ) { bool ok = false; KoXmlNode n = element.firstChild(); for ( ; ! n.isNull(); n = n.nextSibling() ) { if ( ! n.isElement() ) { continue; } KoXmlElement e = n.toElement(); debugPlanWork<name() ) ); } } } if ( ok ) { KoXmlNode n = element.firstChild(); for ( ; ! n.isNull(); n = n.nextSibling() ) { if ( ! n.isElement() ) { continue; } KoXmlElement e = n.toElement(); debugPlanWork<( m_project->childNode( 0 ) ); t->workPackage().setOwnerName( e.attribute( "owner" ) ); t->workPackage().setOwnerId( e.attribute( "owner-id" ) ); Resource *r = m_project->findResource( t->workPackage().ownerId() ); if ( r == 0 ) { debugPlanWork<<"Cannot find resource id!!"<workPackage().ownerId()<workPackage().ownerName(); } debugPlanWork<<"is this me?"<workPackage().ownerName(); KoXmlNode ch = e.firstChild(); for ( ; ! ch.isNull(); ch = ch.nextSibling() ) { if ( ! ch.isElement() ) { continue; } KoXmlElement el = ch.toElement(); debugPlanWork<scheduleManagers().isEmpty() ) { // should be only one manager const QList &lst = m_project->scheduleManagers(); m_project->setCurrentSchedule(lst.first()->scheduleId()); } return ok; } bool WorkPackage::saveToStream( QIODevice * dev ) { QDomDocument doc = saveXML(); // 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()) warnPlanWork << "wrote " << nwritten << "- expected" << s.size(); return nwritten == (int)s.size(); } bool WorkPackage::saveNativeFormat( Part */*part*/, const QString &path ) { if ( path.isEmpty() ) { KMessageBox::error( 0, i18n("Cannot save to empty filename") ); return false; } debugPlanWork<name()<bad()) { KMessageBox::error( 0, i18n("Could not create the file for saving") ); delete store; return false; } if (store->open("root")) { KoStoreDevice dev(store); if ( ! saveToStream(&dev) || ! store->close() ) { debugPlanWork << "saveToStream failed"; delete store; return false; } } else { KMessageBox::error( 0, i18n("Not able to write '%1'. Partition full?", QString("maindoc.xml") ) ); delete store; return false; } if (!completeSaving(store)) { delete store; return false; } if (!store->finalize()) { delete store; return false; } // Success delete store; m_modified = false; return true; } bool WorkPackage::completeSaving( KoStore *store ) { debugPlanWork; KoStore *oldstore = KoStore::createStore( filePath(), KoStore::Read, "", KoStore::Zip ); if ( oldstore->bad() ) { KMessageBox::error( 0, i18n( "Failed to open store:\n %1", filePath() ) ); return false; } if (oldstore->hasFile( "documentinfo.xml" ) ) { copyFile( oldstore, store, "documentinfo.xml" ); } if (oldstore->hasFile( "preview.png" ) ) { copyFile( oldstore, store, "preview.png" ); } // First get all open documents debugPlanWork<saveToStore( store ) ) { } } // Then get new files foreach ( const Document *doc, node()->documents().documents() ) { if ( m_newdocs.contains( doc ) ) { store->addLocalFile( m_newdocs[ doc ].path(), doc->url().fileName() ); m_newdocs.remove( doc ); // TODO remove temp file ?? } } // Then get files from the old store copied to the new store foreach ( Document *doc, node()->documents().documents() ) { if ( doc->sendAs() != Document::SendAs_Copy ) { continue; } if ( ! store->hasFile( doc->url().fileName() ) ) { copyFile( oldstore, store, doc->url().fileName() ); } } return true; } QString WorkPackage::fileName( const Part *part ) const { Q_UNUSED(part); if ( m_project == 0 ) { warnPlanWork<<"No project in this package"; return QString(); } Node *n = node(); if ( n == 0 ) { warnPlanWork<<"No node in this project"; return QString(); } QString projectName = m_project->name().remove( ' ' ); // FIXME: workaround: KoResourcePaths::saveLocation( "projects", projectName + '/' ); const QString path = KoResourcePaths::saveLocation( "appdata", "projects/" + projectName + '/' ); QString wpName = n->name(); wpName = QString( wpName.remove( ' ' ).replace('/', '_') + '_' + n->id() + ".planwork" ); return path + wpName; } void WorkPackage::removeFile() { QFile file( m_filePath ); if ( ! file.exists() ) { warnPlanWork<<"No project in this package"; return; } file.remove(); } void WorkPackage::saveToProjects( Part *part ) { debugPlanWork; QString path = fileName( part ); debugPlanWork<name(); if ( saveNativeFormat( part, path ) ) { m_fromProjectStore = true; m_filePath = path; } else { KMessageBox::error( 0, i18n( "Cannot save to projects store:\n%1" , path ) ); } return; } bool WorkPackage::isModified() const { if ( m_modified ) { return true; } foreach ( DocumentChild *ch, m_childdocs ) { if ( ch->isModified() || ch->isFileModified() ) { return true; } } return false; } QString WorkPackage::name() const { QString n; Task *t = task(); return t ? t->name() : QString(); } Node *WorkPackage::node() const { return m_project == 0 ? 0 : m_project->childNode( 0 ); } Task *WorkPackage::task() const { Task *task = qobject_cast( node() ); Q_ASSERT( task ); return task; } bool WorkPackage::removeDocument( Part *part, Document *doc ) { Node *n = node(); if ( n == 0 ) { return false; } part->addCommand( new DocumentRemoveCmd( n->documents(), doc, UndoText::removeDocument() ) ); return true; } bool WorkPackage::copyFile( KoStore *from, KoStore *to, const QString &filename ) { QByteArray data; if ( ! from->extractFile( filename , data ) ) { KMessageBox::error( 0, i18n( "Failed read file:\n %1", filename ) ); return false; } if ( ! to->addDataToFile( data, filename ) ) { KMessageBox::error( 0, i18n( "Failed write file:\n %1", filename ) ); return false; } debugPlanWork<<"Copied file:"<( node() ); if ( t ) { wp.setAttribute( "owner", t->workPackage().ownerName() ); wp.setAttribute( "owner-id", t->workPackage().ownerId() ); } doc.appendChild( wp ); m_project->save( doc, XmlSaveContext() ); return document; } void WorkPackage::merge( Part *part, const WorkPackage *wp, KoStore *store ) { debugPlanWork; const Node *from = wp->node(); Node *to = node(); MacroCommand *m = new MacroCommand( kundo2_i18n( "Merge data" ) ); + if (m_wbsCode != wp->wbsCode()) { + m->addCommand(new ModifyWbsCodeCmd(this, wp->wbsCode())); + } if ( to->name() != from->name() ) { m->addCommand( new NodeModifyNameCmd( *to, from->name() ) ); } if ( to->description() != from->description() ) { m->addCommand( new NodeModifyDescriptionCmd( *to, from->description() ) ); } if ( to->startTime() != from->startTime() && from->startTime().isValid() ) { m->addCommand( new NodeModifyStartTimeCmd( *to, from->startTime() ) ); } if ( to->endTime() != from->endTime() && from->endTime().isValid() ) { m->addCommand( new NodeModifyEndTimeCmd( *to, from->endTime() ) ); } if ( to->leader() != from->leader() ) { m->addCommand( new NodeModifyLeaderCmd( *to, from->leader() ) ); } if ( from->type() == Node::Type_Task && from->type() == Node::Type_Task ) { if ( static_cast( to )->workPackage().ownerId() != static_cast( from )->workPackage().ownerId() ) { debugPlanWork<<"merge:"<<"different owners"<( from )->workPackage().ownerName()<( to )->workPackage().ownerName(); if ( static_cast( to )->workPackage().ownerId().isEmpty() ) { //TODO cmd static_cast( to )->workPackage().setOwnerId( static_cast( from )->workPackage().ownerId() ); static_cast( to )->workPackage().setOwnerName( static_cast( from )->workPackage().ownerName() ); } } foreach ( Document *doc, from->documents().documents() ) { Document *org = to->documents().findDocument( doc->url() ); if ( org ) { // TODO: also handle modified type, sendas // update ? what if open, modified ... if ( doc->type() == Document::Type_Product ) { //### FIXME. user feedback warnPlanWork<<"We do not update existing deliverables (except name change)"; if ( doc->name() != org->name() ) { m->addCommand( new DocumentModifyNameCmd( org, doc->name() ) ); } } else { if ( doc->name() != org->name() ) { m->addCommand( new DocumentModifyNameCmd( org, doc->name() ) ); } if ( doc->sendAs() != org->sendAs() ) { m->addCommand( new DocumentModifySendAsCmd( org, doc->sendAs() ) ); } if ( doc->sendAs() == Document::SendAs_Copy ) { debugPlanWork<<"Update existing doc:"<url(); openNewDocument( org, store ); } } } else { debugPlanWork<<"new document:"<typeToString(doc->type())<url(); Document *newdoc = new Document( *doc ); m->addCommand( new DocumentAddCmd( to->documents(), newdoc ) ); if ( doc->sendAs() == Document::SendAs_Copy ) { debugPlanWork<<"Copy file"; openNewDocument( newdoc, store ); } } } } const Project *fromProject = wp->project(); Project *toProject = m_project; const ScheduleManager *fromSm = fromProject->scheduleManagers().value( 0 ); Q_ASSERT( fromSm ); ScheduleManager *toSm = toProject->scheduleManagers().value( 0 ); Q_ASSERT( toSm ); if ( fromSm->managerId() != toSm->managerId() || fromSm->scheduleId() != toSm->scheduleId() ) { // rescheduled, update schedules m->addCommand( new CopySchedulesCmd( *fromProject, *toProject ) ); } if ( m->isEmpty() ) { delete m; } else { part->addCommand( m ); } } void WorkPackage::openNewDocument( const Document *doc, KoStore *store ) { const QUrl url = extractFile( doc, store ); if ( url.url().isEmpty() ) { KMessageBox::error( 0, i18n( "Could not extract document from storage:
%1", doc->url().path() ) ); return; } if ( ! url.isValid() ) { KMessageBox::error( 0, i18n( "Invalid URL:
%1", url.path() ) ); return; } m_newdocs.insert( doc, url ); } int WorkPackage::queryClose( Part *part ) { debugPlanWork<name(); QStringList lst; if ( ! m_childdocs.isEmpty() ) { foreach ( DocumentChild *ch, m_childdocs ) { if ( ch->isOpen() && ch->doc()->sendAs() == Document::SendAs_Copy ) { lst << ch->doc()->url().fileName(); } } } if ( ! lst.isEmpty() ) { KMessageBox::ButtonCode result = KMessageBox::warningContinueCancelList( 0, i18np( "

The work package '%2' has an open document.

Data may be lost if you continue.

", "

The work package '%2' has open documents.

Data may be lost if you continue.

", lst.count(), name ), lst ); switch (result) { case KMessageBox::Continue: { debugPlanWork<<"Continue"; break; } default: // case KMessageBox::Cancel : debugPlanWork<<"Cancel"; return KMessageBox::Cancel; break; } } if ( ! isModified() ) { return KMessageBox::Yes; } KMessageBox::ButtonCode res = KMessageBox::warningYesNoCancel( 0, i18n("

The work package '%1' has been modified.

Do you want to save it?

", name), QString(), KStandardGuiItem::save(), KStandardGuiItem::discard()); switch (res) { case KMessageBox::Yes: { debugPlanWork<<"Yes"; saveToProjects( part ); break; } case KMessageBox::No: debugPlanWork<<"No"; break; default: // case KMessageBox::Cancel : debugPlanWork<<"Cancel"; break; } return res; } QUrl WorkPackage::extractFile( const Document *doc ) { KoStore *store = KoStore::createStore( m_filePath, KoStore::Read, "", KoStore::Zip ); if ( store->bad() ) { KMessageBox::error( 0, i18n( "

Work package '%1'

Could not open store:

%2

", node()->name(), m_filePath ) ); delete store; return QUrl(); } const QUrl url = extractFile( doc, store ); delete store; return url; } QUrl WorkPackage::extractFile( const Document *doc, KoStore *store ) { //FIXME: should use a special tmp dir QString tmp = QDir::tempPath() + QLatin1Char('/') + doc->url().fileName(); const QUrl url = QUrl::fromLocalFile( tmp ); debugPlanWork<<"Extract: "<url().fileName()<<" -> "<extractFile( doc->url().fileName(), url.path() ) ) { KMessageBox::error( 0, i18n( "

Work package '%1'

Could not extract file:

%2

", node()->name(), doc->url().fileName() ) ); return QUrl(); } return url; } QString WorkPackage::id() const { QString id; if ( node() ) { id = m_project->id() + node()->id(); } return id; } //-------------------------------- PackageRemoveCmd::PackageRemoveCmd( Part *part, WorkPackage *value, const KUndo2MagicString& name ) : NamedCommand( name ), m_part( part ), m_value( value ), m_mine( false ) { } PackageRemoveCmd::~PackageRemoveCmd() { if ( m_mine ) { m_value->removeFile(); delete m_value; } } void PackageRemoveCmd::execute() { m_part->removeWorkPackage( m_value ); m_mine = true; } void PackageRemoveCmd::unexecute() { m_part->addWorkPackage( m_value ); m_mine = false; } //--------------------- CopySchedulesCmd::CopySchedulesCmd( const Project &fromProject, Project &toProject, const KUndo2MagicString &name ) : NamedCommand( name ), m_project( toProject ) { QDomDocument olddoc; QDomElement e = olddoc.createElement( "old" ); olddoc.appendChild( e ); toProject.save( e, XmlSaveContext() ); m_olddoc = olddoc.toString(); QDomDocument newdoc; e = newdoc.createElement( "new" ); newdoc.appendChild( e ); fromProject.save( e, XmlSaveContext() ); m_newdoc = newdoc.toString(); } void CopySchedulesCmd::execute() { load( m_newdoc ); } void CopySchedulesCmd::unexecute() { load( m_olddoc ); } void CopySchedulesCmd::load( const QString &doc ) { clearSchedules(); KoXmlDocument d; d.setContent( doc ); KoXmlElement proj = d.documentElement().namedItem( "project").toElement(); Q_ASSERT( ! proj.isNull() ); KoXmlElement task = proj.namedItem( "task").toElement(); Q_ASSERT( ! task.isNull() ); KoXmlElement ts = task.namedItem( "schedules").namedItem( "schedule").toElement(); Q_ASSERT( ! ts.isNull() ); KoXmlElement ps = proj.namedItem( "schedules").namedItem( "plan" ).toElement(); Q_ASSERT( ! ps.isNull() ); XMLLoaderObject status; status.setProject( &m_project ); status.setVersion( PLAN_FILE_SYNTAX_VERSION ); // task first NodeSchedule *ns = new NodeSchedule(); if ( ns->loadXML( ts, status ) ) { debugPlanWork<name()<type()<id(); ns->setNode( m_project.childNode( 0 ) ); m_project.childNode( 0 )->addSchedule( ns ); } else { Q_ASSERT( false ); delete ns; } // schedule manager next (includes main schedule and resource schedule) ScheduleManager *sm = new ScheduleManager( m_project ); if ( sm->loadXML( ps, status ) ) { m_project.addScheduleManager( sm ); } else { Q_ASSERT( false ); delete sm; } if ( sm ) { m_project.setCurrentSchedule( sm->scheduleId() ); } m_project.childNode( 0 )->changed(); } void CopySchedulesCmd::clearSchedules() { foreach ( Schedule *s, m_project.schedules() ) { m_project.takeSchedule( s ); } foreach ( Schedule *s, m_project.childNode( 0 )->schedules() ) { foreach ( Appointment *a, s->appointments() ) { if ( a->resource() && a->resource()->resource() ) { a->resource()->resource()->takeSchedule( a->resource() ); } } m_project.childNode( 0 )->takeSchedule( s ); } foreach ( ScheduleManager *sm, m_project.scheduleManagers() ) { m_project.takeScheduleManager( sm ); delete sm; } } +//--------------------- +ModifyWbsCodeCmd::ModifyWbsCodeCmd(WorkPackage *wp, QString wbsCode, const KUndo2MagicString &name) + : NamedCommand(name) + , m_wp(wp) + , m_old(wp->wbsCode()) + , m_new(wbsCode) +{ +} + +void ModifyWbsCodeCmd::execute() +{ + m_wp->setWbsCode(m_new); +} + +void ModifyWbsCodeCmd::unexecute() +{ + m_wp->setWbsCode(m_old); +} } //KPlatoWork namespace QDebug operator<<(QDebug dbg, const KPlatoWork::WorkPackage *wp) { if (!wp) { return dbg.noquote() << "WorkPackage[0x0]"; } return dbg << *wp; } QDebug operator<<(QDebug dbg, const KPlatoWork::WorkPackage &wp) { dbg.noquote() << "WorkPackage["; dbg << wp.id(); dbg << wp.name(); dbg << ']'; return dbg; } diff --git a/src/workpackage/workpackage.h b/src/workpackage/workpackage.h index 7d75c1f8..2cf32ca0 100644 --- a/src/workpackage/workpackage.h +++ b/src/workpackage/workpackage.h @@ -1,212 +1,231 @@ /* This file is part of the KDE project Copyright (C) 2009 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. */ #ifndef KPLATOWORK_WORKPACKAGE_H #define KPLATOWORK_WORKPACKAGE_H #include "kptxmlloaderobject.h" #include "kptcommand.h" #include "kpttask.h" #include #include #include #include class KoStore; class QDomDocument; namespace KPlato { class Project; class Document; class XMLLoaderObject; } using namespace KPlato; /// The main namespace for KPlato WorkPackage Handler namespace KPlatoWork { class Part; class WorkPackage; class DocumentChild; /** A work package consists of a Project node and one Task node along with scheduling information and assigned resources. */ class WorkPackage : public QObject { Q_OBJECT public: explicit WorkPackage(bool fromProjectStore); WorkPackage( Project *project, bool fromProjectStore ); ~WorkPackage(); /// @return Package name QString name() const; DocumentChild *findChild( const Document *doc ) const; /// Called when loading a work package. Saves to Project store. /// Asks to save/overwrite if already there. /// Does nothing if opened from Projects store. void saveToProjects( Part *part ); bool contains( const DocumentChild* child ) const { return m_childdocs.contains( const_cast( child ) ); } QList childDocs() { return m_childdocs; } bool addChild( Part *part, const Document *doc ); void removeChild( DocumentChild *child ); bool contains( const Document *doc ) const; QString nodeId() const; /// Load the Plan work package document bool loadXML( const KoXmlElement &element, XMLLoaderObject &status ); /// Load the old KPlato work package file format bool loadKPlatoXML( const KoXmlElement &element, XMLLoaderObject &status ); QDomDocument saveXML(); bool saveNativeFormat( Part *part, const QString &path ); bool saveDocumentsToStore( KoStore *store ); bool completeSaving( KoStore *store ); Node *node() const; Task *task() const; Project *project() const { return m_project; } /// Remove document @p doc bool removeDocument( Part *part, Document *doc ); /// Set the file path to this package void setFilePath( const QString &name ) { m_filePath = name; } /// Return the file path to this package QString filePath() const { return m_filePath; } /// Construct file path to projects store QString fileName( const Part *part ) const; /// Remove work package file void removeFile(); /// Merge data from work package @p wp void merge( Part *part, const WorkPackage *wp, KoStore *store ); bool isModified() const; int queryClose( Part *part ); QUrl extractFile( const Document *doc ); QUrl extractFile( const Document *doc, KoStore *store ); QString id() const; bool isValid() const { return m_project && node(); } WorkPackageSettings &settings() { return m_settings; } void setSettings( const WorkPackageSettings &settings ); QMap newDocuments() const { return m_newdocs; } void removeNewDocument( const Document *doc ) { m_newdocs.remove( doc ); } QUrl sendUrl() const { return m_sendUrl; } QUrl fetchUrl() const { return m_fetchUrl; } + QString wbsCode() const { return m_wbsCode; } + void setWbsCode(const QString &wbsCode) { m_wbsCode = wbsCode; } + Q_SIGNALS: void modified( bool ); void saveWorkPackage(KPlatoWork::WorkPackage* ); public Q_SLOTS: void setModified( bool on ) { m_modified = on; } protected Q_SLOTS: void projectChanged(); void slotChildModified( bool mod ); protected: /// Copy file @p filename from old store @p from, to the new store @p to bool copyFile( KoStore *from, KoStore *to, const QString &filename ); bool saveToStream( QIODevice * dev ); void openNewDocument( const Document *doc, KoStore *store ); protected: Project *m_project; QString m_filePath; bool m_fromProjectStore; QList m_childdocs; QMap m_newdocs; /// new documents that does not exists in the project store (yet) QUrl m_sendUrl; /// Where to put the package. If not valid, transmit by mail QUrl m_fetchUrl; /// Plan will store package here bool m_modified; + QString m_wbsCode; WorkPackageSettings m_settings; ConfigBase m_config; }; //----------------------------- class PackageRemoveCmd : public NamedCommand { public: PackageRemoveCmd( Part *part, WorkPackage *value, const KUndo2MagicString &name = KUndo2MagicString() ); ~PackageRemoveCmd(); void execute(); void unexecute(); private: Part *m_part; WorkPackage *m_value; bool m_mine; }; //----------------------------- class CopySchedulesCmd : public NamedCommand { public: CopySchedulesCmd( const Project &fromProject, Project &toProject, const KUndo2MagicString &name = KUndo2MagicString() ); void execute(); void unexecute(); private: void load( const QString &doc ); void clean( const QDomDocument &doc ); void clearSchedules(); private: Project &m_project; QString m_olddoc; QString m_newdoc; }; +//----------------------------- +class ModifyWbsCodeCmd : public NamedCommand +{ +public: + ModifyWbsCodeCmd(WorkPackage *wp, QString wbsCode, const KUndo2MagicString &name = KUndo2MagicString()); + + void execute(); + void unexecute(); + +private: + WorkPackage *m_wp; + QString m_old; + QString m_new; +}; + } //KPlatoWork namespace QDebug operator<<(QDebug dbg, const KPlatoWork::WorkPackage *wp); QDebug operator<<(QDebug dbg, const KPlatoWork::WorkPackage &wp); #endif