1 /* This file is part of the KDE project
2  Copyright (C) 2001 Thomas zander <zander@kde.org>
3  Copyright (C) 2004 - 2010, 2012 Dag Andersen <danders@get2net.dk>
4  Copyright (C) 2007 Florian Piquemal <flotueur@yahoo.fr>
5  Copyright (C) 2007 Alexis Ménard <darktears31@gmail.com>
6  Copyright (C) 2019 Dag Andersen <danders@get2net.dk>
7 
8  This library is free software; you can redistribute it and/or
9  modify it under the terms of the GNU Library General Public
10  License as published by the Free Software Foundation; either
11  version 2 of the License, or (at your option) any later version.
12 
13  This library is distributed in the hope that it will be useful,
14  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  Library General Public License for more details.
17 
18  You should have received a copy of the GNU Library General Public License
19  along with this library; see the file COPYING.LIB.  If not, write to
20  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 * Boston, MA 02110-1301, USA.
22 */
23 
24 // clazy:excludeall=qstring-arg
25 #include "kptproject.h"
26 
27 #include "kptlocale.h"
28 #include "kptappointment.h"
29 #include "kpttask.h"
30 #include "kptdatetime.h"
31 #include "kpteffortcostmap.h"
32 #include "kptschedule.h"
33 #include "kptwbsdefinition.h"
34 #include "kptxmlloaderobject.h"
35 #include "XmlSaveContext.h"
36 #include "kptschedulerplugin.h"
37 #include "kptdebug.h"
38 
39 #include <KoXmlReader.h>
40 
41 #include <krandom.h>
42 #include <KFormat>
43 #include <KLocalizedString>
44 
45 #include <QDateTime>
46 #include <QLocale>
47 
48 namespace KPlato
49 {
50 
generateId()51 QString generateId()
52 {
53     return QDateTime::currentDateTime().toString(QStringLiteral("yyyyMMddHHmmss")) + KRandom::randomString(10);
54 }
55 
Project(Node * parent)56 Project::Project(Node *parent)
57         : Node(parent),
58         m_accounts(*this),
59         m_defaultCalendar(0),
60         m_config(&emptyConfig),
61         m_schedulerPlugins(),
62         m_useSharedResources(false),
63         m_sharedResourcesLoaded(false),
64         m_loadProjectsAtStartup(false),
65         m_useLocalTaskModules(true)
66 {
67     //debugPlan<<"("<<this<<")";
68     init();
69 }
70 
Project(ConfigBase & config,Node * parent)71 Project::Project(ConfigBase &config, Node *parent)
72         : Node(parent),
73         m_accounts(*this),
74         m_defaultCalendar(0),
75         m_config(&config),
76         m_schedulerPlugins(),
77         m_useSharedResources(false),
78         m_sharedResourcesLoaded(false),
79         m_loadProjectsAtStartup(false),
80         m_useLocalTaskModules(true)
81 {
82     debugPlan<<"("<<this<<")";
83     init();
84     m_config->setDefaultValues(*this);
85 }
86 
Project(ConfigBase & config,bool useDefaultValues,Node * parent)87 Project::Project(ConfigBase &config, bool useDefaultValues, Node *parent)
88         : Node(parent),
89         m_accounts(*this),
90         m_defaultCalendar(0),
91         m_config(&config),
92         m_schedulerPlugins(),
93         m_useSharedResources(false),
94         m_sharedResourcesLoaded(false),
95         m_loadProjectsAtStartup(false),
96         m_useLocalTaskModules(true)
97 {
98     debugPlan<<"("<<this<<")";
99     init();
100     if (useDefaultValues) {
101         m_config->setDefaultValues(*this);
102     }
103 }
104 
init()105 void Project::init()
106 {
107     m_refCount = 1; // always used by creator
108 
109     m_constraint = Node::MustStartOn;
110     m_standardWorktime = new StandardWorktime();
111     m_timeZone = QTimeZone::systemTimeZone(); // local timezone as default
112     //debugPlan<<m_timeZone;
113     if (m_parent == 0) {
114         // set sensible defaults for a project wo parent
115         m_constraintStartTime = DateTime(QDate::currentDate());
116         m_constraintEndTime = m_constraintStartTime.addYears(2);
117     }
118 }
119 
deref()120 void Project::deref()
121 {
122     --m_refCount;
123     Q_ASSERT(m_refCount >= 0);
124     if (m_refCount <= 0) {
125         emit aboutToBeDeleted();
126         deleteLater();
127     }
128 }
129 
~Project()130 Project::~Project()
131 {
132     debugPlan<<"("<<this<<")";
133     disconnect();
134     for(Node *n : qAsConst(nodeIdDict)) {
135         n->blockChanged();
136     }
137     for (Resource *r : qAsConst(resourceIdDict)) {
138         r->blockChanged();
139     }
140     for (ResourceGroup *g : qAsConst(resourceGroupIdDict)) {
141         g->blockChanged();
142     }
143     delete m_standardWorktime;
144     while (!m_resourceGroups.isEmpty())
145         delete m_resourceGroups.takeFirst();
146     while (!m_calendars.isEmpty())
147         delete m_calendars.takeFirst();
148     while (!m_managers.isEmpty())
149         delete m_managers.takeFirst();
150 
151     m_config = 0; //not mine, don't delete
152 }
153 
type() const154 int Project::type() const { return Node::Type_Project; }
155 
generateUniqueNodeIds()156 void Project::generateUniqueNodeIds()
157 {
158     foreach (Node *n, nodeIdDict) {
159         debugPlan<<n->name()<<"old"<<n->id();
160         QString uid = uniqueNodeId();
161         nodeIdDict.remove(n->id());
162         n->setId(uid);
163         nodeIdDict[ uid ] = n;
164         debugPlan<<n->name()<<"new"<<n->id();
165     }
166 }
167 
generateUniqueIds()168 void Project::generateUniqueIds()
169 {
170     generateUniqueNodeIds();
171 
172     foreach (ResourceGroup *g, resourceGroupIdDict) {
173         if (g->isShared()) {
174             continue;
175         }
176         resourceGroupIdDict.remove(g->id());
177         g->setId(uniqueResourceGroupId());
178         resourceGroupIdDict[ g->id() ] = g;
179     }
180     foreach (Resource *r, resourceIdDict) {
181         if (r->isShared()) {
182             continue;
183         }
184         resourceIdDict.remove(r->id());
185         r->setId(uniqueResourceId());
186         resourceIdDict[ r->id() ] = r;
187     }
188     foreach (Calendar *c, calendarIdDict) {
189         if (c->isShared()) {
190             continue;
191         }
192         calendarIdDict.remove(c->id());
193         c->setId(uniqueCalendarId());
194         calendarIdDict[ c->id() ] = c;
195     }
196 }
197 
calculate(Schedule * schedule,const DateTime & dt)198 void Project::calculate(Schedule *schedule, const DateTime &dt)
199 {
200     if (schedule == 0) {
201         errorPlan << "Schedule == 0, cannot calculate";
202         return ;
203     }
204     m_currentSchedule = schedule;
205     calculate(dt);
206 }
207 
calculate(const DateTime & dt)208 void Project::calculate(const DateTime &dt)
209 {
210     if (m_currentSchedule == 0) {
211         errorPlan << "No current schedule to calculate";
212         return ;
213     }
214     stopcalculation = false;
215     QLocale locale;
216     DateTime time = dt.isValid() ? dt : DateTime(QDateTime::currentDateTime());
217     MainSchedule *cs = static_cast<MainSchedule*>(m_currentSchedule);
218     Estimate::Use estType = (Estimate::Use) cs->type();
219     if (type() == Type_Project) {
220         cs->setPhaseName(0, i18n("Init"));
221         cs->logInfo(i18n("Schedule project from: %1", locale.toString(dt, QLocale::ShortFormat)), 0);
222         initiateCalculation(*cs);
223         initiateCalculationLists(*cs); // must be after initiateCalculation() !!
224         propagateEarliestStart(time);
225         // Calculate lateFinish from time. If a task has started, remainingEffort is used.
226         cs->setPhaseName(1, i18nc("Schedule project forward", "Forward"));
227         cs->logInfo(i18n("Calculate finish"), 1);
228         cs->lateFinish = calculateForward(estType);
229         cs->lateFinish = checkEndConstraints(cs->lateFinish);
230         propagateLatestFinish(cs->lateFinish);
231         // Calculate earlyFinish. If a task has started, remainingEffort is used.
232         cs->setPhaseName(2, i18nc("Schedule project backward","Backward"));
233         cs->logInfo(i18n("Calculate start"), 2);
234         calculateBackward(estType);
235         // Schedule. If a task has started, remainingEffort is used and appointments are copied from parent
236         cs->setPhaseName(3, i18n("Schedule"));
237         cs->logInfo(i18n("Schedule tasks forward"), 3);
238         cs->endTime = scheduleForward(cs->startTime, estType);
239         cs->logInfo(i18n("Scheduled finish: %1", locale.toString(cs->endTime, QLocale::ShortFormat)), 3);
240         if (cs->endTime > m_constraintEndTime) {
241             cs->logError(i18n("Could not finish project in time: %1", locale.toString(m_constraintEndTime, QLocale::ShortFormat)), 3);
242         } else if (cs->endTime == m_constraintEndTime) {
243             cs->logWarning(i18n("Finished project exactly on time: %1", locale.toString(m_constraintEndTime, QLocale::ShortFormat)), 3);
244         } else {
245             cs->logInfo(i18n("Finished project before time: %1", locale.toString(m_constraintEndTime, QLocale::ShortFormat)), 3);
246         }
247         calcCriticalPath(false);
248         calcResourceOverbooked();
249         cs->notScheduled = false;
250         calcFreeFloat();
251         emit scheduleChanged(cs);
252         emit projectChanged();
253     } else if (type() == Type_Subproject) {
254         warnPlan << "Subprojects not implemented";
255     } else {
256         errorPlan << "Illegal project type: " << type();
257     }
258 }
259 
calculate(ScheduleManager & sm)260 void Project::calculate(ScheduleManager &sm)
261 {
262     emit sigCalculationStarted(this, &sm);
263     sm.setScheduling(true);
264     m_progress = 0;
265     int nodes = 0;
266     foreach (Node *n, nodeIdDict) {
267         if (n->type() == Node::Type_Task || n->type() == Node::Type_Milestone) {
268             nodes++;
269         }
270     }
271     int maxprogress = nodes * 3;
272     if (sm.recalculate()) {
273         emit maxProgress(maxprogress);
274         sm.setMaxProgress(maxprogress);
275         incProgress();
276         if (sm.parentManager()) {
277             sm.expected()->startTime = sm.parentManager()->expected()->startTime;
278             sm.expected()->earlyStart = sm.parentManager()->expected()->earlyStart;
279         }
280         incProgress();
281         calculate(sm.expected(), sm.recalculateFrom());
282     } else {
283         emit maxProgress(maxprogress);
284         sm.setMaxProgress(maxprogress);
285         calculate(sm.expected());
286         emit scheduleChanged(sm.expected());
287         setCurrentSchedule(sm.expected()->id());
288     }
289     emit sigProgress(maxprogress);
290     emit sigCalculationFinished(this, &sm);
291     emit scheduleManagerChanged(&sm);
292     emit projectCalculated(&sm);
293     emit projectChanged();
294     sm.setScheduling(false);
295 }
296 
calculate(Schedule * schedule)297 void Project::calculate(Schedule *schedule)
298 {
299     if (schedule == 0) {
300         errorPlan << "Schedule == 0, cannot calculate";
301         return ;
302     }
303     m_currentSchedule = schedule;
304     calculate();
305 }
306 
calculate()307 void Project::calculate()
308 {
309     if (m_currentSchedule == 0) {
310         errorPlan << "No current schedule to calculate";
311         return ;
312     }
313     stopcalculation = false;
314     MainSchedule *cs = static_cast<MainSchedule*>(m_currentSchedule);
315     bool backwards = false;
316     if (cs->manager()) {
317         backwards = cs->manager()->schedulingDirection();
318     }
319     QLocale locale;
320     Estimate::Use estType = (Estimate::Use) cs->type();
321     if (type() == Type_Project) {
322         QTime timer; timer.start();
323         initiateCalculation(*cs);
324         initiateCalculationLists(*cs); // must be after initiateCalculation() !!
325         if (! backwards) {
326             cs->setPhaseName(0, i18n("Init"));
327             cs->logInfo(i18n("Schedule project forward from: %1", locale.toString(m_constraintStartTime, QLocale::ShortFormat)), 0);
328             cs->startTime = m_constraintStartTime;
329             cs->earlyStart = m_constraintStartTime;
330             // Calculate from start time
331             propagateEarliestStart(cs->earlyStart);
332             cs->setPhaseName(1, i18nc("Schedule project forward", "Forward"));
333             cs->logInfo(i18n("Calculate late finish"), 1);
334             cs->lateFinish = calculateForward(estType);
335 //            cs->lateFinish = checkEndConstraints(cs->lateFinish);
336             cs->logInfo(i18n("Late finish calculated: %1", locale.toString(cs->lateFinish, QLocale::ShortFormat)), 1);
337             propagateLatestFinish(cs->lateFinish);
338             cs->setPhaseName(2, i18nc("Schedule project backward", "Backward"));
339             cs->logInfo(i18n("Calculate early start"), 2);
340             calculateBackward(estType);
341             cs->setPhaseName(3, i18n("Schedule"));
342             cs->logInfo(i18n("Schedule tasks forward"), 3);
343             cs->endTime = scheduleForward(cs->startTime, estType);
344             cs->duration = cs->endTime - cs->startTime;
345             cs->logInfo(i18n("Scheduled finish: %1", locale.toString(cs->endTime, QLocale::ShortFormat)), 3);
346             if (cs->endTime > m_constraintEndTime) {
347                 cs->constraintError = true;
348                 cs->logError(i18n("Could not finish project in time: %1", locale.toString(m_constraintEndTime, QLocale::ShortFormat)), 3);
349             } else if (cs->endTime == m_constraintEndTime) {
350                 cs->logWarning(i18n("Finished project exactly on time: %1", locale.toString(m_constraintEndTime, QLocale::ShortFormat)), 3);
351             } else {
352                 cs->logInfo(i18n("Finished project before time: %1", locale.toString(m_constraintEndTime, QLocale::ShortFormat)), 3);
353             }
354             calcCriticalPath(false);
355         } else {
356             cs->setPhaseName(0, i18n("Init"));
357             cs->logInfo(i18n("Schedule project backward from: %1", locale.toString(m_constraintEndTime, QLocale::ShortFormat)), 0);
358             // Calculate from end time
359             propagateLatestFinish(m_constraintEndTime);
360             cs->setPhaseName(1, i18nc("Schedule project backward", "Backward"));
361             cs->logInfo(i18n("Calculate early start"), 1);
362             cs->earlyStart = calculateBackward(estType);
363 //            cs->earlyStart = checkStartConstraints(cs->earlyStart);
364             cs->logInfo(i18n("Early start calculated: %1", locale.toString(cs->earlyStart, QLocale::ShortFormat)), 1);
365             propagateEarliestStart(cs->earlyStart);
366             cs->setPhaseName(2, i18nc("Schedule project forward", "Forward"));
367             cs->logInfo(i18n("Calculate late finish"), 2);
368             cs->lateFinish = qMax(m_constraintEndTime, calculateForward(estType));
369             cs->logInfo(i18n("Late finish calculated: %1", locale.toString(cs->lateFinish, QLocale::ShortFormat)), 2);
370             cs->setPhaseName(3, i18n("Schedule"));
371             cs->logInfo(i18n("Schedule tasks backward"), 3);
372             cs->startTime = scheduleBackward(cs->lateFinish, estType);
373             cs->endTime = cs->startTime;
374             foreach (Node *n, allNodes()) {
375                 if (n->type() == Type_Task || n->type() == Type_Milestone) {
376                     DateTime e = n->endTime(cs->id());
377                     if (cs->endTime <  e) {
378                         cs->endTime = e;
379                     }
380                 }
381             }
382             if (cs->endTime > m_constraintEndTime) {
383                 cs->constraintError = true;
384                 cs->logError(i18n("Failed to finish project within target time"), 3);
385             }
386             cs->duration = cs->endTime - cs->startTime;
387             cs->logInfo(i18n("Scheduled start: %1, target time: %2", locale.toString(cs->startTime, QLocale::ShortFormat), locale.toString(m_constraintStartTime, QLocale::ShortFormat)), 3);
388             if (cs->startTime < m_constraintStartTime) {
389                 cs->constraintError = true;
390                 cs->logError(i18n("Must start project early in order to finish in time: %1", locale.toString(m_constraintStartTime, QLocale::ShortFormat)), 3);
391             } else if (cs->startTime == m_constraintStartTime) {
392                 cs->logWarning(i18n("Start project exactly on time: %1", locale.toString(m_constraintStartTime, QLocale::ShortFormat)), 3);
393             } else {
394                 cs->logInfo(i18n("Can start project later than time: %1", locale.toString(m_constraintStartTime, QLocale::ShortFormat)), 3);
395             }
396             calcCriticalPath(true);
397         }
398         cs->logInfo(i18n("Calculation took: %1", KFormat().formatDuration(timer.elapsed())));
399         // TODO: fix this uncertainty, manager should *always* be available
400         if (cs->manager()) {
401             finishCalculation(*(cs->manager()));
402         }
403     } else if (type() == Type_Subproject) {
404         warnPlan << "Subprojects not implemented";
405     } else {
406         errorPlan << "Illegal project type: " << type();
407     }
408 }
409 
finishCalculation(ScheduleManager & sm)410 void Project::finishCalculation(ScheduleManager &sm)
411 {
412     MainSchedule *cs = sm.expected();
413     if (nodeIdDict.count() > 1) {
414         // calculate project duration
415         cs->startTime = m_constraintEndTime;
416         cs->endTime = m_constraintStartTime;
417         for (const Node *n : qAsConst(nodeIdDict)) {
418             cs->startTime = qMin(cs->startTime, n->startTime(cs->id()));
419             cs->endTime = qMax(cs->endTime, n->endTime(cs->id()));
420         }
421         cs->duration = cs->endTime - cs->startTime;
422     }
423 
424     calcCriticalPath(false);
425     calcResourceOverbooked();
426     cs->notScheduled = false;
427     calcFreeFloat();
428     emit scheduleChanged(cs);
429     emit projectChanged();
430     debugPlan<<cs->startTime<<cs->endTime<<"-------------------------";
431 }
432 
setProgress(int progress,ScheduleManager * sm)433 void Project::setProgress(int progress, ScheduleManager *sm)
434 {
435     m_progress = progress;
436     if (sm) {
437         sm->setProgress(progress);
438     }
439     emit sigProgress(progress);
440 }
441 
setMaxProgress(int max,ScheduleManager * sm)442 void Project::setMaxProgress(int max, ScheduleManager *sm)
443 {
444     if (sm) {
445         sm->setMaxProgress(max);
446     }
447     emitMaxProgress(max);
448 }
449 
incProgress()450 void Project::incProgress()
451 {
452     m_progress += 1;
453     emit sigProgress(m_progress);
454 }
455 
emitMaxProgress(int value)456 void Project::emitMaxProgress(int value)
457 {
458     emit maxProgress(value);
459 }
460 
calcCriticalPath(bool fromEnd)461 bool Project::calcCriticalPath(bool fromEnd)
462 {
463     //debugPlan;
464     MainSchedule *cs = static_cast<MainSchedule*>(m_currentSchedule);
465     if (cs == 0) {
466         return false;
467     }
468     if (fromEnd) {
469         QListIterator<Node*> startnodes = cs->startNodes();
470         while (startnodes.hasNext()) {
471             startnodes.next() ->calcCriticalPath(fromEnd);
472         }
473     } else {
474         QListIterator<Node*> endnodes = cs->endNodes();
475         while (endnodes.hasNext()) {
476             endnodes.next() ->calcCriticalPath(fromEnd);
477         }
478     }
479     calcCriticalPathList(cs);
480     return false;
481 }
482 
calcCriticalPathList(MainSchedule * cs)483 void Project::calcCriticalPathList(MainSchedule *cs)
484 {
485     //debugPlan<<m_name<<", "<<cs->name();
486     cs->clearCriticalPathList();
487     foreach (Node *n, allNodes()) {
488         if (n->numDependParentNodes() == 0 && n->inCriticalPath(cs->id())) {
489             cs->addCriticalPath();
490             cs->addCriticalPathNode(n);
491             calcCriticalPathList(cs, n);
492         }
493     }
494     cs->criticalPathListCached = true;
495     //debugPlan<<*(criticalPathList(cs->id()));
496 }
497 
calcCriticalPathList(MainSchedule * cs,Node * node)498 void Project::calcCriticalPathList(MainSchedule *cs, Node *node)
499 {
500     //debugPlan<<node->name()<<", "<<cs->id();
501     bool newPath = false;
502     QList<Node*> lst = *(cs->currentCriticalPath());
503     foreach (Relation *r, node->dependChildNodes()) {
504         if (r->child()->inCriticalPath(cs->id())) {
505             if (newPath) {
506                 cs->addCriticalPath(&lst);
507                 //debugPlan<<node->name()<<" new path";
508             }
509             cs->addCriticalPathNode(r->child());
510             calcCriticalPathList(cs, r->child());
511             newPath = true;
512         }
513     }
514 }
515 
criticalPathList(long id)516 const QList< QList<Node*> > *Project::criticalPathList(long id)
517 {
518     Schedule *s = schedule(id);
519     if (s == 0) {
520         //debugPlan<<"No schedule with id="<<id;
521         return 0;
522     }
523     MainSchedule *ms = static_cast<MainSchedule*>(s);
524     if (! ms->criticalPathListCached) {
525         initiateCalculationLists(*ms);
526         calcCriticalPathList(ms);
527     }
528     return ms->criticalPathList();
529 }
530 
criticalPath(long id,int index)531 QList<Node*> Project::criticalPath(long id, int index)
532 {
533     Schedule *s = schedule(id);
534     if (s == 0) {
535         //debugPlan<<"No schedule with id="<<id;
536         return QList<Node*>();
537     }
538     MainSchedule *ms = static_cast<MainSchedule*>(s);
539     if (! ms->criticalPathListCached) {
540         initiateCalculationLists(*ms);
541         calcCriticalPathList(ms);
542     }
543     return ms->criticalPath(index);
544 }
545 
startTime(long id) const546 DateTime Project::startTime(long id) const
547 {
548     Schedule *s = schedule(id);
549     return s ? s->startTime : m_constraintStartTime;
550 }
551 
endTime(long id) const552 DateTime Project::endTime(long id) const
553 {
554     Schedule *s = schedule(id);
555     return s ? s->endTime : m_constraintEndTime;
556 }
557 
duration(long id) const558 Duration Project::duration(long id) const
559 {
560     Schedule *s = schedule(id);
561     return s ? s->duration : Duration::zeroDuration;
562 }
563 
getRandomDuration()564 Duration *Project::getRandomDuration()
565 {
566     return 0L;
567 }
568 
checkStartConstraints(const DateTime & dt) const569 DateTime Project::checkStartConstraints(const DateTime &dt) const
570 {
571     DateTime t = dt;
572     foreach (Node *n, nodeIdDict) {
573         if (n->type() == Node::Type_Task || n->type() == Node::Type_Milestone) {
574             switch (n->constraint()) {
575                 case Node::FixedInterval:
576                 case Node::StartNotEarlier:
577                 case Node::MustStartOn:
578                         t = qMin(t, qMax(n->constraintStartTime(), m_constraintStartTime));
579                         break;
580                 default: break;
581             }
582         }
583     }
584     return t;
585 }
586 
checkEndConstraints(const DateTime & dt) const587 DateTime Project::checkEndConstraints(const DateTime &dt) const
588 {
589     DateTime t = dt;
590     foreach (Node *n, nodeIdDict) {
591         if (n->type() == Node::Type_Task || n->type() == Node::Type_Milestone) {
592             switch (n->constraint()) {
593                 case Node::FixedInterval:
594                 case Node::FinishNotLater:
595                 case Node::MustFinishOn:
596                         t = qMax(t, qMin(n->constraintEndTime(), m_constraintEndTime));
597                         break;
598                 default: break;
599             }
600         }
601     }
602     return t;
603 }
604 
605 #ifndef PLAN_NLOGDEBUG
checkParent(Node * n,const QList<Node * > & list,QList<Relation * > & checked)606 bool Project::checkParent(Node *n, const QList<Node*> &list, QList<Relation*> &checked)
607 {
608     if (n->isStartNode()) {
609         debugPlan<<n<<"start node"<<list;
610         return true;
611     }
612     debugPlan<<"Check:"<<n<<":"<<checked.count()<<":"<<list;
613     if (list.contains(n)) {
614         debugPlan<<"Failed:"<<n<<":"<<list;
615         return false;
616     }
617     QList<Node*> lst = list;
618     lst << n;
619     foreach (Relation *r, n->dependParentNodes()) {
620         if (checked.contains(r)) {
621             debugPlan<<"Depend:"<<n<<":"<<r->parent()<<": checked";
622             continue;
623         }
624         checked << r;
625         if (! checkParent(r->parent(), lst, checked)) {
626             return false;
627         }
628     }
629     Task *t = static_cast<Task*>(n);
630     foreach (Relation *r, t->parentProxyRelations()) {
631         if (checked.contains(r)) {
632             debugPlan<<"Depend:"<<n<<":"<<r->parent()<<": checked";
633             continue;
634         }
635         checked << r;
636         debugPlan<<"Proxy:"<<n<<":"<<r->parent()<<":"<<lst;
637         if (! checkParent(r->parent(), lst, checked)) {
638             return false;
639         }
640     }
641     return true;
642 }
643 
checkChildren(Node * n,const QList<Node * > & list,QList<Relation * > & checked)644 bool Project::checkChildren(Node *n, const QList<Node*> &list, QList<Relation*> &checked)
645 {
646     if (n->isEndNode()) {
647         debugPlan<<n<<"end node"<<list;
648         return true;
649     }
650     debugPlan<<"Check:"<<n<<":"<<checked.count()<<":"<<list;
651     if (list.contains(n)) {
652         debugPlan<<"Failed:"<<n<<":"<<list;
653         return false;
654     }
655     QList<Node*> lst = list;
656     lst << n;
657     foreach (Relation *r, n->dependChildNodes()) {
658         if (checked.contains(r)) {
659             debugPlan<<"Depend:"<<n<<":"<<r->parent()<<": checked";
660             continue;
661         }
662         checked << r;
663         if (! checkChildren(r->child(), lst, checked)) {
664             return false;
665         }
666     }
667     Task *t = static_cast<Task*>(n);
668     foreach (Relation *r, t->childProxyRelations()) {
669         if (checked.contains(r)) {
670             debugPlan<<"Depend:"<<n<<":"<<r->parent()<<": checked";
671             continue;
672         }
673         debugPlan<<"Proxy:"<<n<<":"<<r->parent()<<":"<<lst;
674         checked << r;
675         if (! checkChildren(r->child(), lst, checked)) {
676             return false;
677         }
678     }
679     return true;
680 }
681 #endif
tasksForward()682 void Project::tasksForward()
683 {
684     m_hardConstraints.clear();
685     m_softConstraints.clear();
686     m_terminalNodes.clear();
687     // Do these in reverse order to get tasks with same prio in wbs order
688     const QList<Task*> tasks = allTasks();
689     for (int i = tasks.count() -1; i >= 0; --i) {
690         Task *t = tasks.at(i);
691         switch (t->constraint()) {
692             case Node::MustStartOn:
693             case Node::MustFinishOn:
694             case Node::FixedInterval:
695                 m_hardConstraints.prepend(t);
696                 break;
697             case Node::StartNotEarlier:
698             case Node::FinishNotLater:
699                 m_softConstraints.prepend(t);
700                 break;
701             default:
702                 if (t->isEndNode()) {
703                     m_terminalNodes.insert(-t->priority(), t);
704                 }
705                 break;
706         }
707     }
708 #ifndef PLAN_NLOGDEBUG
709     debugPlan<<"End nodes:"<<m_terminalNodes;
710     foreach (Node* n, m_terminalNodes) {
711         QList<Node*> lst;
712         QList<Relation*> rel;
713         Q_ASSERT(checkParent(n, lst, rel)); Q_UNUSED(n);
714     }
715 #endif
716 }
717 
tasksBackward()718 void Project::tasksBackward()
719 {
720     m_hardConstraints.clear();
721     m_softConstraints.clear();
722     m_terminalNodes.clear();
723     // Do these in reverse order to get tasks with same prio in wbs order
724     const QList<Task*> tasks = allTasks();
725     for (int i = tasks.count() -1; i >= 0; --i) {
726         Task *t = tasks.at(i);
727         switch (t->constraint()) {
728             case Node::MustStartOn:
729             case Node::MustFinishOn:
730             case Node::FixedInterval:
731                 m_hardConstraints.prepend(t);
732                 break;
733             case Node::StartNotEarlier:
734             case Node::FinishNotLater:
735                 m_softConstraints.prepend(t);
736                 break;
737             default:
738                 if (t->isStartNode()) {
739                     m_terminalNodes.insert(-t->priority(), t);
740                 }
741                 break;
742         }
743     }
744 #ifndef PLAN_NLOGDEBUG
745     debugPlan<<"Start nodes:"<<m_terminalNodes;
746     foreach (Node* n, m_terminalNodes) {
747         QList<Node*> lst;
748         QList<Relation*> rel;
749         Q_ASSERT(checkChildren(n, lst, rel)); Q_UNUSED(n);
750     }
751 #endif
752 }
753 
calculateForward(int use)754 DateTime Project::calculateForward(int use)
755 {
756     //debugPlan<<m_name;
757     DateTime finish;
758     MainSchedule *cs = static_cast<MainSchedule*>(m_currentSchedule);
759     if (cs == 0) {
760         return finish;
761     }
762     if (type() == Node::Type_Project) {
763         QTime timer;
764         timer.start();
765         cs->logInfo(i18n("Start calculating forward"));
766         m_visitedForward = true;
767         if (! m_visitedBackward) {
768             // setup tasks
769             tasksForward();
770             // Do all hard constrained first
771             foreach (Node *n, m_hardConstraints) {
772                 cs->logDebug("Calculate task with hard constraint:" + n->name() + " : " + n->constraintToString());
773                 DateTime time = n->calculateEarlyFinish(use); // do not do predeccessors
774                 if (time > finish) {
775                     finish = time;
776                 }
777             }
778             // do the predeccessors
779             foreach (Node *n, m_hardConstraints) {
780                 cs->logDebug("Calculate predeccessors to hard constrained task:" + n->name() + " : " + n->constraintToString());
781                 DateTime time = n->calculateForward(use);
782                 if (time > finish) {
783                     finish = time;
784                 }
785             }
786             // now try to schedule soft constrained *with* predeccessors
787             foreach (Node *n, m_softConstraints) {
788                 cs->logDebug("Calculate task with soft constraint:" + n->name() + " : " + n->constraintToString());
789                 DateTime time = n->calculateForward(use);
790                 if (time > finish) {
791                     finish = time;
792                 }
793             }
794             // and then the rest using the end nodes to calculate everything (remaining)
795             foreach (Task *n, m_terminalNodes) {
796                 cs->logDebug("Calculate using end task:" + n->name() + " : " + n->constraintToString());
797                 DateTime time = n->calculateForward(use);
798                 if (time > finish) {
799                     finish = time;
800                 }
801             }
802         } else {
803             // tasks have been calculated backwards in this order
804             foreach (Node *n, cs->backwardNodes()) {
805                 DateTime time = n->calculateForward(use);
806                 if (time > finish) {
807                     finish = time;
808                 }
809             }
810         }
811         cs->logInfo(i18n("Finished calculating forward: %1 ms", timer.elapsed()));
812     } else {
813         //TODO: subproject
814     }
815     return finish;
816 }
817 
calculateBackward(int use)818 DateTime Project::calculateBackward(int use)
819 {
820     //debugPlan<<m_name;
821     DateTime start;
822     MainSchedule *cs = static_cast<MainSchedule*>(m_currentSchedule);
823     if (cs == 0) {
824         return start;
825     }
826     if (type() == Node::Type_Project) {
827         QTime timer;
828         timer.start();
829         cs->logInfo(i18n("Start calculating backward"));
830         m_visitedBackward = true;
831         if (! m_visitedForward) {
832             // setup tasks
833             tasksBackward();
834             // Do all hard constrained first
835             foreach (Task *n, m_hardConstraints) {
836                 cs->logDebug("Calculate task with hard constraint:" + n->name() + " : " + n->constraintToString());
837                 DateTime time = n->calculateLateStart(use); // do not do predeccessors
838                 if (! start.isValid() || time < start) {
839                     start = time;
840                 }
841             }
842             // then do the predeccessors
843             foreach (Task *n, m_hardConstraints) {
844                 cs->logDebug("Calculate predeccessors to hard constrained task:" + n->name() + " : " + n->constraintToString());
845                 DateTime time = n->calculateBackward(use);
846                 if (! start.isValid() || time < start) {
847                     start = time;
848                 }
849             }
850             // now try to schedule soft constrained *with* predeccessors
851             foreach (Task *n, m_softConstraints) {
852                 cs->logDebug("Calculate task with soft constraint:" + n->name() + " : " + n->constraintToString());
853                 DateTime time = n->calculateBackward(use);
854                 if (! start.isValid() || time < start) {
855                     start = time;
856                 }
857             }
858             // and then the rest using the start nodes to calculate everything (remaining)
859             foreach (Task *n, m_terminalNodes) {
860                 cs->logDebug("Calculate using start task:" + n->name() + " : " + n->constraintToString());
861                 DateTime time = n->calculateBackward(use);
862                 if (! start.isValid() || time < start) {
863                     start = time;
864                 }
865             }
866         } else {
867             // tasks have been calculated forwards in this order
868             foreach (Node *n, cs->forwardNodes()) {
869                 DateTime time = n->calculateBackward(use);
870                 if (! start.isValid() || time < start) {
871                     start = time;
872                 }
873             }
874         }
875         cs->logInfo(i18n("Finished calculating backward: %1 ms", timer.elapsed()));
876     } else {
877         //TODO: subproject
878     }
879     return start;
880 }
881 
scheduleForward(const DateTime & earliest,int use)882 DateTime Project::scheduleForward(const DateTime &earliest, int use)
883 {
884     DateTime end;
885     MainSchedule *cs = static_cast<MainSchedule*>(m_currentSchedule);
886     if (cs == 0 || stopcalculation) {
887         return DateTime();
888     }
889     QTime timer;
890     timer.start();
891     cs->logInfo(i18n("Start scheduling forward"));
892     resetVisited();
893     // Schedule in the same order as calculated forward
894     // Do all hard constrained first
895     foreach (Node *n, m_hardConstraints) {
896         cs->logDebug("Schedule task with hard constraint:" + n->name() + " : " + n->constraintToString());
897         DateTime time = n->scheduleFromStartTime(use); // do not do predeccessors
898         if (time > end) {
899             end = time;
900         }
901     }
902     foreach (Node *n, cs->forwardNodes()) {
903         cs->logDebug("Schedule task:" + n->name() + " : " + n->constraintToString());
904         DateTime time = n->scheduleForward(earliest, use);
905         if (time > end) {
906             end = time;
907         }
908     }
909     // Fix summarytasks
910     adjustSummarytask();
911     cs->logInfo(i18n("Finished scheduling forward: %1 ms", timer.elapsed()));
912     foreach (Node *n, allNodes()) {
913         if (n->type() == Node::Type_Task || n->type() == Node::Type_Milestone) {
914             Q_ASSERT(n->isScheduled());
915         }
916     }
917 
918     return end;
919 }
920 
scheduleBackward(const DateTime & latest,int use)921 DateTime Project::scheduleBackward(const DateTime &latest, int use)
922 {
923     DateTime start;
924     MainSchedule *cs = static_cast<MainSchedule*>(m_currentSchedule);
925     if (cs == 0 || stopcalculation) {
926         return start;
927     }
928     QTime timer;
929     timer.start();
930     cs->logInfo(i18n("Start scheduling backward"));
931     resetVisited();
932     // Schedule in the same order as calculated backward
933     // Do all hard constrained first
934     foreach (Node *n, m_hardConstraints) {
935         cs->logDebug("Schedule task with hard constraint:" + n->name() + " : " + n->constraintToString());
936         DateTime time = n->scheduleFromEndTime(use); // do not do predeccessors
937         if (! start.isValid() || time < start) {
938             start = time;
939         }
940     }
941     foreach (Node *n, cs->backwardNodes()) {
942         cs->logDebug("Schedule task:" + n->name() + " : " + n->constraintToString());
943         DateTime time = n->scheduleBackward(latest, use);
944         if (! start.isValid() || time < start) {
945             start = time;
946         }
947     }
948     // Fix summarytasks
949     adjustSummarytask();
950     cs->logInfo(i18n("Finished scheduling backward: %1 ms", timer.elapsed()));
951     foreach (Node *n, allNodes()) {
952         if (n->type() == Node::Type_Task || n->type() == Node::Type_Milestone) {
953             Q_ASSERT(n->isScheduled());
954         }
955     }
956     return start;
957 }
958 
adjustSummarytask()959 void Project::adjustSummarytask()
960 {
961     MainSchedule *cs = static_cast<MainSchedule*>(m_currentSchedule);
962     if (cs == 0 || stopcalculation) {
963         return;
964     }
965     QListIterator<Node*> it(cs->summaryTasks());
966     while (it.hasNext()) {
967         it.next() ->adjustSummarytask();
968     }
969 }
970 
initiateCalculation(MainSchedule & sch)971 void Project::initiateCalculation(MainSchedule &sch)
972 {
973     //debugPlan<<m_name;
974     // clear all resource appointments
975     m_visitedForward = false;
976     m_visitedBackward = false;
977     QListIterator<ResourceGroup*> git(m_resourceGroups);
978     while (git.hasNext()) {
979         git.next() ->initiateCalculation(sch);
980     }
981     Node::initiateCalculation(sch);
982 }
983 
initiateCalculationLists(MainSchedule & sch)984 void Project::initiateCalculationLists(MainSchedule &sch)
985 {
986     //debugPlan<<m_name;
987     sch.clearNodes();
988     if (type() == Node::Type_Project) {
989         QListIterator<Node*> it = childNodeIterator();
990         while (it.hasNext()) {
991             it.next() ->initiateCalculationLists(sch);
992         }
993     } else {
994         //TODO: subproject
995     }
996 }
997 
load(KoXmlElement & element,XMLLoaderObject & status)998 bool Project::load(KoXmlElement &element, XMLLoaderObject &status)
999 {
1000     //debugPlan<<"--->";
1001     m_useSharedResources = false; // default should off in case old project
1002     // load locale first
1003     KoXmlNode n = element.firstChild();
1004     for (; ! n.isNull(); n = n.nextSibling()) {
1005         if (! n.isElement()) {
1006             continue;
1007         }
1008         KoXmlElement e = n.toElement();
1009         if (e.tagName() == "locale") {
1010             Locale *l = locale();
1011             l->setCurrencySymbol(e.attribute("currency-symbol", ""));
1012 
1013             if (e.hasAttribute("currency-digits")) {
1014                 l->setMonetaryDecimalPlaces(e.attribute("currency-digits").toInt());
1015             }
1016             QLocale::Language language = QLocale::AnyLanguage;
1017             QLocale::Country country = QLocale::AnyCountry;
1018             if (e.hasAttribute("language")) {
1019                 language = static_cast<QLocale::Language>(e.attribute("language").toInt());
1020             }
1021             if (e.hasAttribute("country")) {
1022                 country = static_cast<QLocale::Country>(e.attribute("country").toInt());
1023             }
1024             l->setCurrencyLocale(language, country);
1025         } else if (e.tagName() == "shared-resources") {
1026             m_useSharedResources = e.attribute("use", "0").toInt();
1027             m_sharedResourcesFile = e.attribute("file");
1028             m_sharedProjectsUrl = QUrl(e.attribute("projects-url"));
1029             m_loadProjectsAtStartup = (bool)e.attribute("projects-loadatstartup", "0").toInt();
1030         } else if (e.tagName() == QLatin1String("documents")) {
1031             m_documents.load(e, status);
1032         } else if (e.tagName() == QLatin1String("workpackageinfo")) {
1033             if (e.hasAttribute("check-for-workpackages")) {
1034                 m_workPackageInfo.checkForWorkPackages = e.attribute("check-for-workpackages").toInt();
1035             }
1036             if (e.hasAttribute("retrieve-url")) {
1037                 m_workPackageInfo.retrieveUrl = QUrl(e.attribute("retrieve-url"));
1038             }
1039             if (e.hasAttribute("delete-after-retrieval")) {
1040                 m_workPackageInfo.deleteAfterRetrieval = e.attribute("delete-after-retrieval").toInt();
1041             }
1042             if (e.hasAttribute("archive-after-retrieval")) {
1043                 m_workPackageInfo.archiveAfterRetrieval = e.attribute("archive-after-retrieval").toInt();
1044             }
1045             if (e.hasAttribute("archive-url")) {
1046                 m_workPackageInfo.archiveUrl = QUrl(e.attribute("archive-url"));
1047             }
1048             if (e.hasAttribute("publish-url")) {
1049                 m_workPackageInfo.publishUrl = QUrl(e.attribute("publish-url"));
1050             }
1051         } else if (e.tagName() == QLatin1String("task-modules")) {
1052             m_useLocalTaskModules = false;
1053             QList<QUrl> urls;
1054             for (KoXmlNode child = e.firstChild(); !child.isNull(); child = child.nextSibling()) {
1055                 KoXmlElement path = child.toElement();
1056                 if (path.isNull()) {
1057                     continue;
1058                 }
1059                 QString s = path.attribute("url");
1060                 if (!s.isEmpty()) {
1061                     QUrl url = QUrl::fromUserInput(s);
1062                     if (!urls.contains(url)) {
1063                         urls << url;
1064                     }
1065                 }
1066             }
1067             m_taskModules = urls;
1068             // If set adds local path to taskModules()
1069             setUseLocalTaskModules((bool)e.attribute("use-local-task-modules").toInt());
1070         }
1071     }
1072     QList<Calendar*> cals;
1073     QString s;
1074     bool ok = false;
1075     setName(element.attribute("name"));
1076     removeId(m_id);
1077     m_id = element.attribute("id");
1078     registerNodeId(this);
1079     m_priority = element.attribute(QStringLiteral("priority"), "0").toInt();
1080     m_leader = element.attribute("leader");
1081     m_description = element.attribute("description");
1082     QTimeZone tz(element.attribute("timezone").toLatin1());
1083     if (tz.isValid()) {
1084         m_timeZone = tz;
1085     } else warnPlan<<"No timezone specified, using default (local)";
1086     status.setProjectTimeZone(m_timeZone);
1087 
1088     // Allow for both numeric and text
1089     s = element.attribute("scheduling", "0");
1090     m_constraint = (Node::ConstraintType) s.toInt(&ok);
1091     if (!ok)
1092         setConstraint(s);
1093     if (m_constraint != Node::MustStartOn &&
1094             m_constraint != Node::MustFinishOn) {
1095         errorPlan << "Illegal constraint: " << constraintToString();
1096         setConstraint(Node::MustStartOn);
1097     }
1098     s = element.attribute("start-time");
1099     if (!s.isEmpty())
1100         m_constraintStartTime = DateTime::fromString(s, m_timeZone);
1101     s = element.attribute("end-time");
1102     if (!s.isEmpty())
1103         m_constraintEndTime = DateTime::fromString(s, m_timeZone);
1104 
1105     status.setProgress(10);
1106 
1107     // Load the project children
1108     // Do calendars first, they only reference other calendars
1109     //debugPlan<<"Calendars--->";
1110     n = element.firstChild();
1111     for (; ! n.isNull(); n = n.nextSibling()) {
1112         if (! n.isElement()) {
1113             continue;
1114         }
1115         KoXmlElement e = n.toElement();
1116         if (e.tagName() == "calendar") {
1117             // Load the calendar.
1118             // Referenced by resources
1119             Calendar * child = new Calendar();
1120             child->setProject(this);
1121             if (child->load(e, status)) {
1122                 cals.append(child); // temporary, reorder later
1123             } else {
1124                 // TODO: Complain about this
1125                 errorPlan << "Failed to load calendar";
1126                 delete child;
1127             }
1128         } else if (e.tagName() == "standard-worktime") {
1129             // Load standard worktime
1130             StandardWorktime * child = new StandardWorktime();
1131             if (child->load(e, status)) {
1132                 setStandardWorktime(child);
1133             } else {
1134                 errorPlan << "Failed to load standard worktime";
1135                 delete child;
1136             }
1137         }
1138     }
1139     // calendars references calendars in arbitrary saved order
1140     bool added = false;
1141     do {
1142         added = false;
1143         QList<Calendar*> lst;
1144         while (!cals.isEmpty()) {
1145             Calendar *c = cals.takeFirst();
1146             c->m_blockversion = true;
1147             if (c->parentId().isEmpty()) {
1148                 addCalendar(c, status.baseCalendar()); // handle pre 0.6 version
1149                 added = true;
1150                 //debugPlan<<"added to project:"<<c->name();
1151             } else {
1152                 Calendar *par = calendar(c->parentId());
1153                 if (par) {
1154                     par->m_blockversion = true;
1155                     addCalendar(c, par);
1156                     added = true;
1157                     //debugPlan<<"added:"<<c->name()<<" to parent:"<<par->name();
1158                     par->m_blockversion = false;
1159                 } else {
1160                     lst.append(c); // treat later
1161                     //debugPlan<<"treat later:"<<c->name();
1162                 }
1163             }
1164             c->m_blockversion = false;
1165         }
1166         cals = lst;
1167     } while (added);
1168     if (! cals.isEmpty()) {
1169         errorPlan<<"All calendars not saved!";
1170     }
1171     //debugPlan<<"Calendars<---";
1172 
1173     status.setProgress(15);
1174 
1175     // Resource groups and resources, can reference calendars
1176     n = element.firstChild();
1177     for (; ! n.isNull(); n = n.nextSibling()) {
1178         if (! n.isElement()) {
1179             continue;
1180         }
1181         KoXmlElement e = n.toElement();
1182         if (e.tagName() == "resource-group") {
1183             // Load the resources
1184             // References calendars
1185             ResourceGroup * child = new ResourceGroup();
1186             if (child->load(e, status)) {
1187                 addResourceGroup(child);
1188             } else {
1189                 // TODO: Complain about this
1190                 delete child;
1191             }
1192         }
1193     }
1194 
1195     status.setProgress(20);
1196 
1197     // The main stuff
1198     n = element.firstChild();
1199     for (; ! n.isNull(); n = n.nextSibling()) {
1200         if (! n.isElement()) {
1201             continue;
1202         }
1203         KoXmlElement e = n.toElement();
1204         if (e.tagName() == "project") {
1205             //debugPlan<<"Sub project--->";
1206 /*                // Load the subproject
1207             Project * child = new Project(this);
1208             if (child->load(e)) {
1209                 if (!addTask(child, this)) {
1210                     delete child; // TODO: Complain about this
1211                 }
1212             } else {
1213                 // TODO: Complain about this
1214                 delete child;
1215             }*/
1216         } else if (e.tagName() == "task") {
1217             //debugPlan<<"Task--->";
1218             // Load the task (and resourcerequests).
1219             // Depends on resources already loaded
1220             Task * child = new Task(this);
1221             if (child->load(e, status)) {
1222                 if (!addTask(child, this)) {
1223                     delete child; // TODO: Complain about this
1224                 }
1225             } else {
1226                 // TODO: Complain about this
1227                 delete child;
1228             }
1229         }
1230     }
1231 
1232     status.setProgress(70);
1233 
1234     // These go last
1235     n = element.firstChild();
1236     for (; ! n.isNull(); n = n.nextSibling()) {
1237         //debugPlan<<n.isElement();
1238         if (! n.isElement()) {
1239             continue;
1240         }
1241         KoXmlElement e = n.toElement();
1242         if (e.tagName() == "accounts") {
1243             //debugPlan<<"Accounts--->";
1244             // Load accounts
1245             // References tasks
1246             if (!m_accounts.load(e, *this)) {
1247                 errorPlan << "Failed to load accounts";
1248             }
1249         } else if (e.tagName() == "relation") {
1250             //debugPlan<<"Relation--->";
1251             // Load the relation
1252             // References tasks
1253             Relation * child = new Relation();
1254             if (!child->load(e, *this)) {
1255                 // TODO: Complain about this
1256                 errorPlan << "Failed to load relation";
1257                 delete child;
1258             }
1259             //debugPlan<<"Relation<---";
1260         } else if (e.tagName() == "schedules") {
1261             //debugPlan<<"Project schedules & task appointments--->";
1262             // References tasks and resources
1263             KoXmlNode sn = e.firstChild();
1264             for (; ! sn.isNull(); sn = sn.nextSibling()) {
1265                 if (! sn.isElement()) {
1266                     continue;
1267                 }
1268                 KoXmlElement el = sn.toElement();
1269                 //debugPlan<<el.tagName()<<" Version="<<status.version();
1270                 ScheduleManager *sm = 0;
1271                 bool add = false;
1272                 if (status.version() <= "0.5") {
1273                     if (el.tagName() == "schedule") {
1274                         sm = findScheduleManagerByName(el.attribute("name"));
1275                         if (sm == 0) {
1276                             sm = new ScheduleManager(*this, el.attribute("name"));
1277                             add = true;
1278                         }
1279                     }
1280                 } else if (el.tagName() == "plan") {
1281                     sm = new ScheduleManager(*this);
1282                     add = true;
1283                 }
1284                 if (sm) {
1285                     debugPlan<<"load schedule manager";
1286                     if (sm->loadXML(el, status)) {
1287                         if (add)
1288                             addScheduleManager(sm);
1289                     } else {
1290                         errorPlan << "Failed to load schedule manager";
1291                         delete sm;
1292                     }
1293                 } else {
1294                     debugPlan<<"No schedule manager ?!";
1295                 }
1296             }
1297             //debugPlan<<"Node schedules<---";
1298         } else if (e.tagName() == "resource-teams") {
1299             //debugPlan<<"Resource teams--->";
1300             // References other resources
1301             KoXmlNode tn = e.firstChild();
1302             for (; ! tn.isNull(); tn = tn.nextSibling()) {
1303                 if (! tn.isElement()) {
1304                     continue;
1305                 }
1306                 KoXmlElement el = tn.toElement();
1307                 if (el.tagName() == "team") {
1308                     Resource *r = findResource(el.attribute("team-id"));
1309                     Resource *tm = findResource(el.attribute("member-id"));
1310                     if (r == 0 || tm == 0) {
1311                         errorPlan<<"resource-teams: cannot find resources";
1312                         continue;
1313                     }
1314                     if (r == tm) {
1315                         errorPlan<<"resource-teams: a team cannot be a member of itself";
1316                         continue;
1317                     }
1318                     r->addTeamMemberId(tm->id());
1319                 } else {
1320                     errorPlan<<"resource-teams: unhandled tag"<<el.tagName();
1321                 }
1322             }
1323             //debugPlan<<"Resource teams<---";
1324         } else if (e.tagName() == "wbs-definition") {
1325             m_wbsDefinition.loadXML(e, status);
1326         } else if (e.tagName() == "locale") {
1327             // handled earlier
1328         } else if (e.tagName() == "resource-group") {
1329             // handled earlier
1330         } else if (e.tagName() == "calendar") {
1331             // handled earlier
1332         } else if (e.tagName() == "standard-worktime") {
1333             // handled earlier
1334         } else if (e.tagName() == "project") {
1335             // handled earlier
1336         } else if (e.tagName() == "task") {
1337             // handled earlier
1338         } else if (e.tagName() == "shared-resources") {
1339             // handled earlier
1340         } else if (e.tagName() == "documents") {
1341             // handled earlier
1342         } else if (e.tagName() == "workpackageinfo") {
1343             // handled earlier
1344         } else if (e.tagName() == "task-modules") {
1345             // handled earlier
1346         } else {
1347             warnPlan<<"Unhandled tag:"<<e.tagName();
1348         }
1349     }
1350     //debugPlan<<"<---";
1351 
1352     status.setProgress(90);
1353 
1354     return true;
1355 }
1356 
save(QDomElement & element,const XmlSaveContext & context) const1357 void Project::save(QDomElement &element, const XmlSaveContext &context) const
1358 {
1359     QDomElement me = element.ownerDocument().createElement("project");
1360     element.appendChild(me);
1361 
1362     me.setAttribute("name", m_name);
1363     me.setAttribute("leader", m_leader);
1364     me.setAttribute("id", m_id);
1365     me.setAttribute("priority", QString::number(m_priority));
1366     me.setAttribute("description", m_description);
1367     me.setAttribute("timezone", m_timeZone.isValid() ? QString::fromLatin1(m_timeZone.id()) : QString());
1368 
1369     me.setAttribute("scheduling", constraintToString());
1370     me.setAttribute("start-time", m_constraintStartTime.toString(Qt::ISODate));
1371     me.setAttribute("end-time", m_constraintEndTime.toString(Qt::ISODate));
1372 
1373     m_wbsDefinition.saveXML(me);
1374 
1375     QDomElement loc = me.ownerDocument().createElement("locale");
1376     me.appendChild(loc);
1377     const Locale *l = locale();
1378     loc.setAttribute("currency-symbol", l->currencySymbol());
1379     loc.setAttribute("currency-digits", l->monetaryDecimalPlaces());
1380     loc.setAttribute("language", l->currencyLanguage());
1381     loc.setAttribute("country", l->currencyCountry());
1382 
1383     QDomElement share = me.ownerDocument().createElement("shared-resources");
1384     me.appendChild(share);
1385     share.setAttribute("use", m_useSharedResources);
1386     share.setAttribute("file", m_sharedResourcesFile);
1387     share.setAttribute("projects-url", QString(m_sharedProjectsUrl.toEncoded()));
1388     share.setAttribute("projects-loadatstartup", m_loadProjectsAtStartup);
1389 
1390     QDomElement wpi = me.ownerDocument().createElement("workpackageinfo");
1391     me.appendChild(wpi);
1392     wpi.setAttribute("check-for-workpackages", m_workPackageInfo.checkForWorkPackages);
1393     wpi.setAttribute("retrieve-url", m_workPackageInfo.retrieveUrl.toString(QUrl::None));
1394     wpi.setAttribute("delete-after-retrieval", m_workPackageInfo.deleteAfterRetrieval);
1395     wpi.setAttribute("archive-after-retrieval", m_workPackageInfo.archiveAfterRetrieval);
1396     wpi.setAttribute("archive-url", m_workPackageInfo.archiveUrl.toString(QUrl::None));
1397     wpi.setAttribute("publish-url", m_workPackageInfo.publishUrl.toString(QUrl::None));
1398 
1399     QDomElement tm = me.ownerDocument().createElement("task-modules");
1400     me.appendChild(tm);
1401     tm.setAttribute("use-local-task-modules", m_useLocalTaskModules);
1402     for (const QUrl &url : taskModules(false/*no local*/)) {
1403         QDomElement e = tm.ownerDocument().createElement("task-module");
1404         tm.appendChild(e);
1405         e.setAttribute("url", url.toString());
1406     }
1407 
1408     m_documents.save(me);
1409 
1410     if (context.saveAll(this)) {
1411         m_accounts.save(me);
1412 
1413         // save calendars
1414         foreach (Calendar *c, calendarIdDict) {
1415             c->save(me);
1416         }
1417         // save standard worktime
1418         if (m_standardWorktime)
1419             m_standardWorktime->save(me);
1420 
1421         // save project resources, must be after calendars
1422         QListIterator<ResourceGroup*> git(m_resourceGroups);
1423         while (git.hasNext()) {
1424             git.next() ->save(me);
1425         }
1426     }
1427     // Only save parent relations
1428     QListIterator<Relation*> it(m_dependParentNodes);
1429     while (it.hasNext()) {
1430         Relation *r = it.next();
1431         if (context.saveNode(r->parent()) && context.saveNode(r->child())) {
1432             r->save(me, context);
1433         }
1434     }
1435     if (context.saveAll(this)) {
1436         for (int i = 0; i < numChildren(); i++)
1437         // Save all children
1438         childNode(i)->save(me, context);
1439     }
1440     // Now we can save relations assuming no tasks have relations outside the project
1441     QListIterator<Node*> nodes(m_nodes);
1442     while (nodes.hasNext()) {
1443         nodes.next()->saveRelations(me, context);
1444     }
1445     if (context.saveAll(this)) {
1446         if (!m_managers.isEmpty()) {
1447             QDomElement el = me.ownerDocument().createElement("schedules");
1448             me.appendChild(el);
1449             foreach (ScheduleManager *sm, m_managers) {
1450                 sm->saveXML(el);
1451             }
1452         }
1453         // save resource teams
1454         QDomElement el = me.ownerDocument().createElement("resource-teams");
1455         me.appendChild(el);
1456         foreach (Resource *r, resourceIdDict) {
1457             if (r->type() != Resource::Type_Team) {
1458                 continue;
1459             }
1460             foreach (const QString &id, r->teamMemberIds()) {
1461                 QDomElement e = el.ownerDocument().createElement("team");
1462                 el.appendChild(e);
1463                 e.setAttribute("team-id", r->id());
1464                 e.setAttribute("member-id", id);
1465             }
1466         }
1467     }
1468 }
1469 
saveWorkPackageXML(QDomElement & element,const Node * node,long id) const1470 void Project::saveWorkPackageXML(QDomElement &element, const Node *node, long id) const
1471 {
1472     QDomElement me = element.ownerDocument().createElement("project");
1473     element.appendChild(me);
1474 
1475     me.setAttribute("name", m_name);
1476     me.setAttribute("leader", m_leader);
1477     me.setAttribute("id", m_id);
1478     me.setAttribute("description", m_description);
1479     me.setAttribute("timezone", m_timeZone.isValid() ? QString::fromLatin1(m_timeZone.id()) : QString());
1480 
1481     me.setAttribute("scheduling", constraintToString());
1482     me.setAttribute("start-time", m_constraintStartTime.toString(Qt::ISODate));
1483     me.setAttribute("end-time", m_constraintEndTime.toString(Qt::ISODate));
1484 
1485     QListIterator<ResourceGroup*> git(m_resourceGroups);
1486     while (git.hasNext()) {
1487         git.next() ->saveWorkPackageXML(me, node->assignedResources(id));
1488     }
1489 
1490     if (node == 0) {
1491         return;
1492     }
1493     node->saveWorkPackageXML(me, id);
1494 
1495     foreach (ScheduleManager *sm, m_managerIdMap) {
1496         if (sm->scheduleId() == id) {
1497             QDomElement el = me.ownerDocument().createElement("schedules");
1498             me.appendChild(el);
1499             sm->saveWorkPackageXML(el, *node);
1500             break;
1501         }
1502     }
1503 }
1504 
setParentSchedule(Schedule * sch)1505 void Project::setParentSchedule(Schedule *sch)
1506 {
1507     QListIterator<Node*> it = m_nodes;
1508     while (it.hasNext()) {
1509         it.next() ->setParentSchedule(sch);
1510     }
1511 }
1512 
addResourceGroup(ResourceGroup * group,int index)1513 void Project::addResourceGroup(ResourceGroup *group, int index)
1514 {
1515     int i = index == -1 ? m_resourceGroups.count() : index;
1516     emit resourceGroupToBeAdded(group, i);
1517     m_resourceGroups.insert(i, group);
1518     setResourceGroupId(group);
1519     group->setProject(this);
1520     foreach (Resource *r, group->resources()) {
1521         setResourceId(r);
1522         r->setProject(this);
1523     }
1524     emit resourceGroupAdded(group);
1525     emit projectChanged();
1526 }
1527 
takeResourceGroup(ResourceGroup * group)1528 ResourceGroup *Project::takeResourceGroup(ResourceGroup *group)
1529 {
1530     int i = m_resourceGroups.indexOf(group);
1531     Q_ASSERT(i != -1);
1532     if (i == -1) {
1533         return 0;
1534     }
1535     emit resourceGroupToBeRemoved(group);
1536     ResourceGroup *g = m_resourceGroups.takeAt(i);
1537     Q_ASSERT(group == g);
1538     g->setProject(0);
1539     removeResourceGroupId(g->id());
1540     foreach (Resource *r, g->resources()) {
1541         r->setProject(0);
1542         removeResourceId(r->id());
1543     }
1544     emit resourceGroupRemoved(g);
1545     emit projectChanged();
1546     return g;
1547 }
1548 
resourceGroups()1549 QList<ResourceGroup*> &Project::resourceGroups()
1550 {
1551     return m_resourceGroups;
1552 }
1553 
addResource(ResourceGroup * group,Resource * resource,int index)1554 void Project::addResource(ResourceGroup *group, Resource *resource, int index)
1555 {
1556     int i = index == -1 ? group->numResources() : index;
1557     emit resourceToBeAdded(group, i);
1558     group->addResource(i, resource, 0);
1559     setResourceId(resource);
1560     emit resourceAdded(resource);
1561     emit projectChanged();
1562 }
1563 
takeResource(ResourceGroup * group,Resource * resource)1564 Resource *Project::takeResource(ResourceGroup *group, Resource *resource)
1565 {
1566     emit resourceToBeRemoved(resource);
1567     bool result = removeResourceId(resource->id());
1568     Q_ASSERT(result == true);
1569     if (!result) {
1570         warnPlan << "Could not remove resource with id" << resource->id();
1571     }
1572     resource->removeRequests(); // not valid anymore
1573     Resource *r = group->takeResource(resource);
1574     Q_ASSERT(resource == r);
1575     if (resource != r) {
1576         warnPlan << "Could not take resource from group";
1577     }
1578     emit resourceRemoved(resource);
1579     emit projectChanged();
1580     return r;
1581 }
1582 
moveResource(ResourceGroup * group,Resource * resource)1583 void Project::moveResource(ResourceGroup *group, Resource *resource)
1584 {
1585     if (group == resource->parentGroup()) {
1586         return;
1587     }
1588     takeResource(resource->parentGroup(), resource);
1589     addResource(group, resource);
1590     return;
1591 }
1592 
externalProjects() const1593 QMap< QString, QString > Project::externalProjects() const
1594 {
1595     QMap< QString, QString > map;
1596     foreach (Resource *r, resourceList()) {
1597         for(QMapIterator<QString, QString> it(r->externalProjects()); it.hasNext();) {
1598             it.next();
1599             if (! map.contains(it.key())) {
1600                 map[ it.key() ] = it.value();
1601             }
1602         }
1603     }
1604     return map;
1605 }
1606 
addTask(Node * task,Node * position)1607 bool Project::addTask(Node* task, Node* position)
1608 {
1609     // we want to add a task at the given position. => the new node will
1610     // become next sibling right after position.
1611     if (0 == position) {
1612         return addSubTask(task, this);
1613     }
1614     //debugPlan<<"Add"<<task->name()<<" after"<<position->name();
1615     // in case we want to add to the main project, we make it child element
1616     // of the root element.
1617     if (Node::Type_Project == position->type()) {
1618         return addSubTask(task, position);
1619     }
1620     // find the position
1621     // we have to tell the parent that we want to delete one of its children
1622     Node* parentNode = position->parentNode();
1623     if (!parentNode) {
1624         debugPlan <<"parent node not found???";
1625         return false;
1626     }
1627     int index = parentNode->findChildNode(position);
1628     if (-1 == index) {
1629         // ok, it does not exist
1630         debugPlan <<"Task not found???";
1631         return false;
1632     }
1633     return addSubTask(task, index + 1, parentNode);
1634 }
1635 
addSubTask(Node * task,Node * parent)1636 bool Project::addSubTask(Node* task, Node* parent)
1637 {
1638     // append task to parent
1639     return addSubTask(task, -1, parent);
1640 }
1641 
addSubTask(Node * task,int index,Node * parent,bool emitSignal)1642 bool Project::addSubTask(Node* task, int index, Node* parent, bool emitSignal)
1643 {
1644     // we want to add a subtask to the node "parent" at the given index.
1645     // If parent is 0, add to this
1646     Node *p = parent;
1647     if (0 == p) {
1648         p = this;
1649     }
1650     if (!registerNodeId(task)) {
1651         errorPlan << "Failed to register node id, can not add subtask: " << task->name();
1652         return false;
1653     }
1654     int i = index == -1 ? p->numChildren() : index;
1655     if (emitSignal) emit nodeToBeAdded(p, i);
1656     p->insertChildNode(i, task);
1657     connect(this, &Project::standardWorktimeChanged, task, &Node::slotStandardWorktimeChanged);
1658     if (emitSignal) {
1659         emit nodeAdded(task);
1660         emit projectChanged();
1661         if (p != this && p->numChildren() == 1) {
1662             emit nodeChanged(p, TypeProperty);
1663         }
1664     }
1665     return true;
1666 }
1667 
takeTask(Node * node,bool emitSignal)1668 void Project::takeTask(Node *node, bool emitSignal)
1669 {
1670     //debugPlan<<node->name();
1671     Node * parent = node->parentNode();
1672     if (parent == 0) {
1673         debugPlan <<"Node must have a parent!";
1674         return;
1675     }
1676     removeId(node->id());
1677     if (emitSignal) emit nodeToBeRemoved(node);
1678     disconnect(this, &Project::standardWorktimeChanged, node, &Node::slotStandardWorktimeChanged);
1679     parent->takeChildNode(node);
1680     if (emitSignal) {
1681         emit nodeRemoved(node);
1682         emit projectChanged();
1683         if (parent != this && parent->type() != Node::Type_Summarytask) {
1684             emit nodeChanged(parent, TypeProperty);
1685         }
1686     }
1687 }
1688 
canMoveTask(Node * node,Node * newParent,bool checkBaselined)1689 bool Project::canMoveTask(Node* node, Node *newParent, bool checkBaselined)
1690 {
1691     //debugPlan<<node->name()<<" to"<<newParent->name();
1692     if (node == this) {
1693         return false;
1694     }
1695     if (checkBaselined) {
1696         if (node->isBaselined() || (newParent->type() != Node::Type_Summarytask && newParent->isBaselined())) {
1697             return false;
1698         }
1699     }
1700     Node *p = newParent;
1701     while (p && p != this) {
1702         if (! node->canMoveTo(p)) {
1703             return false;
1704         }
1705         p = p->parentNode();
1706     }
1707     return true;
1708 }
1709 
moveTask(Node * node,Node * newParent,int newPos)1710 bool Project::moveTask(Node* node, Node *newParent, int newPos)
1711 {
1712     //debugPlan<<node->name()<<" to"<<newParent->name()<<","<<newPos;
1713     if (! canMoveTask(node, newParent)) {
1714         return false;
1715     }
1716     Node *oldParent = node->parentNode();
1717     int oldPos = oldParent->indexOf(node);
1718     int i = newPos < 0 ? newParent->numChildren() : newPos;
1719     if (oldParent == newParent && i == oldPos) {
1720         // no need to move to where it already is
1721         return false;
1722     }
1723     int newRow = i;
1724     if (oldParent == newParent && newPos > oldPos) {
1725         ++newRow; // itemmodels wants new row *before* node is removed from old position
1726     }
1727     debugPlan<<node->name()<<"at"<<oldParent->indexOf(node)<<"to"<<newParent->name()<<i<<newRow<<"("<<newPos<<")";
1728     emit nodeToBeMoved(node, oldPos, newParent, newRow);
1729     takeTask(node, false);
1730     addSubTask(node, i, newParent, false);
1731     emit nodeMoved(node);
1732     if (oldParent != this && oldParent->numChildren() == 0) {
1733         emit nodeChanged(oldParent, TypeProperty);
1734     }
1735     if (newParent != this && newParent->numChildren() == 1) {
1736         emit nodeChanged(newParent, TypeProperty);
1737     }
1738     return true;
1739 }
1740 
canIndentTask(Node * node)1741 bool Project::canIndentTask(Node* node)
1742 {
1743     if (0 == node) {
1744         // should always be != 0. At least we would get the Project,
1745         // but you never know who might change that, so better be careful
1746         return false;
1747     }
1748     if (node->type() == Node::Type_Project) {
1749         //debugPlan<<"The root node cannot be indented";
1750         return false;
1751     }
1752     // we have to find the parent of task to manipulate its list of children
1753     Node* parentNode = node->parentNode();
1754     if (!parentNode) {
1755         return false;
1756     }
1757     if (parentNode->findChildNode(node) == -1) {
1758         errorPlan << "Tasknot found???";
1759         return false;
1760     }
1761     Node *sib = node->siblingBefore();
1762     if (!sib) {
1763         //debugPlan<<"new parent node not found";
1764         return false;
1765     }
1766     if (node->findParentRelation(sib) || node->findChildRelation(sib)) {
1767         //debugPlan<<"Cannot have relations to parent";
1768         return false;
1769     }
1770     return true;
1771 }
1772 
indentTask(Node * node,int index)1773 bool Project::indentTask(Node* node, int index)
1774 {
1775     if (canIndentTask(node)) {
1776         Node * newParent = node->siblingBefore();
1777         int i = index == -1 ? newParent->numChildren() : index;
1778         moveTask(node, newParent, i);
1779         //debugPlan;
1780         return true;
1781     }
1782     return false;
1783 }
1784 
canUnindentTask(Node * node)1785 bool Project::canUnindentTask(Node* node)
1786 {
1787     if (0 == node) {
1788         // is always != 0. At least we would get the Project, but you
1789         // never know who might change that, so better be careful
1790         return false;
1791     }
1792     if (Node::Type_Project == node->type()) {
1793         //debugPlan<<"The root node cannot be unindented";
1794         return false;
1795     }
1796     // we have to find the parent of task to manipulate its list of children
1797     // and we need the parent's parent too
1798     Node* parentNode = node->parentNode();
1799     if (!parentNode) {
1800         return false;
1801     }
1802     Node* grandParentNode = parentNode->parentNode();
1803     if (!grandParentNode) {
1804         //debugPlan<<"This node already is at the top level";
1805         return false;
1806     }
1807     int index = parentNode->findChildNode(node);
1808     if (-1 == index) {
1809         errorPlan << "Tasknot found???";
1810         return false;
1811     }
1812     return true;
1813 }
1814 
unindentTask(Node * node)1815 bool Project::unindentTask(Node* node)
1816 {
1817     if (canUnindentTask(node)) {
1818         Node * parentNode = node->parentNode();
1819         Node *grandParentNode = parentNode->parentNode();
1820         int i = grandParentNode->indexOf(parentNode) + 1;
1821         if (i == 0)  {
1822             i = grandParentNode->numChildren();
1823         }
1824         moveTask(node, grandParentNode, i);
1825         //debugPlan;
1826         return true;
1827     }
1828     return false;
1829 }
1830 
canMoveTaskUp(Node * node)1831 bool Project::canMoveTaskUp(Node* node)
1832 {
1833     if (node == 0)
1834         return false; // safety
1835     // we have to find the parent of task to manipulate its list of children
1836     Node* parentNode = node->parentNode();
1837     if (!parentNode) {
1838         //debugPlan<<"No parent found";
1839         return false;
1840     }
1841     if (parentNode->findChildNode(node) == -1) {
1842         errorPlan << "Tasknot found???";
1843         return false;
1844     }
1845     if (node->siblingBefore()) {
1846         return true;
1847     }
1848     return false;
1849 }
1850 
moveTaskUp(Node * node)1851 bool Project::moveTaskUp(Node* node)
1852 {
1853     if (canMoveTaskUp(node)) {
1854         moveTask(node, node->parentNode(), node->parentNode()->indexOf(node) - 1);
1855         return true;
1856     }
1857     return false;
1858 }
1859 
canMoveTaskDown(Node * node)1860 bool Project::canMoveTaskDown(Node* node)
1861 {
1862     if (node == 0)
1863         return false; // safety
1864     // we have to find the parent of task to manipulate its list of children
1865     Node* parentNode = node->parentNode();
1866     if (!parentNode) {
1867         return false;
1868     }
1869     if (parentNode->findChildNode(node) == -1) {
1870         errorPlan << "Tasknot found???";
1871         return false;
1872     }
1873     if (node->siblingAfter()) {
1874         return true;
1875     }
1876     return false;
1877 }
1878 
moveTaskDown(Node * node)1879 bool Project::moveTaskDown(Node* node)
1880 {
1881     if (canMoveTaskDown(node)) {
1882         moveTask(node, node->parentNode(), node->parentNode()->indexOf(node) + 1);
1883         return true;
1884     }
1885     return false;
1886 }
1887 
createTask()1888 Task *Project::createTask()
1889 {
1890     Task * node = new Task();
1891     node->setId(uniqueNodeId());
1892     reserveId(node->id(), node);
1893     return node;
1894 }
1895 
createTask(const Task & def)1896 Task *Project::createTask(const Task &def)
1897 {
1898     Task * node = new Task(def);
1899     node->setId(uniqueNodeId());
1900     reserveId(node->id(), node);
1901     return node;
1902 }
1903 
findNode(const QString & id) const1904 Node *Project::findNode(const QString &id) const
1905 {
1906     if (m_parent == 0) {
1907         if (nodeIdDict.contains(id)) {
1908             return nodeIdDict[ id ];
1909         }
1910         return 0;
1911     }
1912     return m_parent->findNode(id);
1913 }
1914 
nodeIdentExists(const QString & id) const1915 bool Project::nodeIdentExists(const QString &id) const
1916 {
1917     return nodeIdDict.contains(id) || nodeIdReserved.contains(id);
1918 }
1919 
uniqueNodeId(int seed) const1920 QString Project::uniqueNodeId(int seed) const
1921 {
1922     Q_UNUSED(seed);
1923     QString ident = generateId();
1924     while (nodeIdentExists(ident)) {
1925         ident = generateId();
1926     }
1927     return ident;
1928 }
1929 
uniqueNodeId(const QList<QString> & existingIds,int seed)1930 QString Project::uniqueNodeId(const QList<QString> &existingIds, int seed)
1931 {
1932     QString id = uniqueNodeId(seed);
1933     while (existingIds.contains(id)) {
1934         id = uniqueNodeId(seed);
1935     }
1936     return id;
1937 }
1938 
removeId(const QString & id)1939 bool Project::removeId(const QString &id)
1940 {
1941     //debugPlan <<"id=" << id;
1942     if (m_parent) {
1943         return m_parent->removeId(id);
1944     }
1945     //debugPlan << "id=" << id<< nodeIdDict.contains(id);
1946     return nodeIdDict.remove(id);
1947 }
1948 
reserveId(const QString & id,Node * node)1949 void Project::reserveId(const QString &id, Node *node)
1950 {
1951     //debugPlan <<"id=" << id << node->name();
1952     nodeIdReserved.insert(id, node);
1953 }
1954 
registerNodeId(Node * node)1955 bool Project::registerNodeId(Node *node)
1956 {
1957     nodeIdReserved.remove(node->id());
1958     if (node->id().isEmpty()) {
1959         warnPlan << "Node id is empty, cannot register it";
1960         return false;
1961     }
1962     Node *rn = findNode(node->id());
1963     if (rn == 0) {
1964         //debugPlan <<"id=" << node->id() << node->name();
1965         nodeIdDict.insert(node->id(), node);
1966         return true;
1967     }
1968     if (rn != node) {
1969         errorPlan << "Id already exists for different task: " << node->id();
1970         return false;
1971     }
1972     //debugPlan<<"Already exists" <<"id=" << node->id() << node->name();
1973     return true;
1974 }
1975 
allNodes(bool ordered,Node * parent) const1976 QList<Node*> Project::allNodes(bool ordered, Node* parent) const
1977 {
1978     QList<Node*> lst;
1979     if (ordered) {
1980         const Node *p = parent ? parent : this;
1981         foreach (Node *n, p->childNodeIterator()) {
1982             if (n->type() == Node::Type_Task || n->type() == Type_Milestone || n->type() == Node::Type_Summarytask) {
1983                 lst << static_cast<Task*>(n);
1984                 lst += allNodes(ordered, n);
1985             }
1986         }
1987     } else {
1988         lst = nodeIdDict.values();
1989         int me = lst.indexOf(const_cast<Project*>(this));
1990         if (me != -1) {
1991             lst.removeAt(me);
1992         }
1993     }
1994     return lst;
1995 }
1996 
allTasks(const Node * parent) const1997 QList<Task*> Project::allTasks(const Node *parent) const
1998 {
1999     QList<Task*> lst;
2000     const Node *p = parent ? parent : this;
2001     foreach (Node *n, p->childNodeIterator()) {
2002         if (n->type() == Node::Type_Task || n->type() == Type_Milestone) {
2003             lst << static_cast<Task*>(n);
2004         }
2005         lst += allTasks(n);
2006     }
2007     return lst;
2008 }
2009 
isStarted() const2010 bool Project::isStarted() const
2011 {
2012     const QList<Task*> tasks = allTasks();
2013     for (const Task *t : tasks) {
2014         if (t->isStarted()) {
2015             return true;
2016         }
2017     }
2018     return false;
2019 }
2020 
setResourceGroupId(ResourceGroup * group)2021 bool Project::setResourceGroupId(ResourceGroup *group)
2022 {
2023     if (group == 0) {
2024         return false;
2025     }
2026     if (! group->id().isEmpty()) {
2027         ResourceGroup *g = findResourceGroup(group->id());
2028         if (group == g) {
2029             return true;
2030         } else if (g == 0) {
2031             insertResourceGroupId(group->id(), group);
2032             return true;
2033         }
2034     }
2035     QString id = uniqueResourceGroupId();
2036     group->setId(id);
2037     if (id.isEmpty()) {
2038         return false;
2039     }
2040     insertResourceGroupId(id, group);
2041     return true;
2042 }
2043 
uniqueResourceGroupId() const2044 QString Project::uniqueResourceGroupId() const {
2045     QString id = generateId();
2046     while (resourceGroupIdDict.contains(id)) {
2047         id = generateId();
2048     }
2049     return id;
2050 }
2051 
group(const QString & id)2052 ResourceGroup *Project::group(const QString& id)
2053 {
2054     return findResourceGroup(id);
2055 }
2056 
groupByName(const QString & name) const2057 ResourceGroup *Project::groupByName(const QString& name) const
2058 {
2059     foreach (ResourceGroup *g, resourceGroupIdDict) {
2060         if (g->name() == name) {
2061             return g;
2062         }
2063     }
2064     return 0;
2065 }
2066 
autoAllocateResources() const2067 QList<Resource*> Project::autoAllocateResources() const
2068 {
2069     QList<Resource*> lst;
2070     foreach (Resource *r, resourceIdDict) {
2071         if (r->autoAllocate()) {
2072             lst << r;
2073         }
2074     }
2075     return lst;
2076 }
2077 
insertResourceId(const QString & id,Resource * resource)2078 void Project::insertResourceId(const QString &id, Resource *resource)
2079 {
2080     resourceIdDict.insert(id, resource);
2081 }
2082 
removeResourceId(const QString & id)2083 bool Project::removeResourceId(const QString &id)
2084 {
2085     return resourceIdDict.remove(id);
2086 }
2087 
setResourceId(Resource * resource)2088 bool Project::setResourceId(Resource *resource)
2089 {
2090     if (resource == 0) {
2091         return false;
2092     }
2093     if (! resource->id().isEmpty()) {
2094         Resource *r = findResource(resource->id());
2095         if (resource == r) {
2096             return true;
2097         } else if (r == 0) {
2098             insertResourceId(resource->id(), resource);
2099             return true;
2100         }
2101     }
2102     QString id = uniqueResourceId();
2103     resource->setId(id);
2104     if (id.isEmpty()) {
2105         return false;
2106     }
2107     insertResourceId(id, resource);
2108     return true;
2109 }
2110 
uniqueResourceId() const2111 QString Project::uniqueResourceId() const {
2112     QString id = generateId();
2113     while (resourceIdDict.contains(id)) {
2114         id = generateId();
2115     }
2116     return id;
2117 }
2118 
resource(const QString & id)2119 Resource *Project::resource(const QString& id)
2120 {
2121     return findResource(id);
2122 }
2123 
resourceByName(const QString & name) const2124 Resource *Project::resourceByName(const QString& name) const
2125 {
2126     QHash<QString, Resource*>::const_iterator it;
2127     for (it = resourceIdDict.constBegin(); it != resourceIdDict.constEnd(); ++it) {
2128         Resource *r = it.value();
2129         if (r->name() == name) {
2130             Q_ASSERT(it.key() == r->id());
2131             return r;
2132         }
2133     }
2134     return 0;
2135 }
2136 
resourceNameList() const2137 QStringList Project::resourceNameList() const
2138 {
2139     QStringList lst;
2140     foreach (Resource *r, resourceIdDict) {
2141         lst << r->name();
2142     }
2143     return lst;
2144 }
2145 
plannedEffortCostPrDay(QDate start,QDate end,long id,EffortCostCalculationType typ) const2146 EffortCostMap Project::plannedEffortCostPrDay(QDate  start, QDate end, long id, EffortCostCalculationType typ) const
2147 {
2148     //debugPlan<<start<<end<<id;
2149     Schedule *s = schedule(id);
2150     if (s == 0) {
2151         return EffortCostMap();
2152     }
2153     EffortCostMap ec;
2154     QListIterator<Node*> it(childNodeIterator());
2155     while (it.hasNext()) {
2156         ec += it.next() ->plannedEffortCostPrDay(start, end, id, typ);
2157     }
2158     return ec;
2159 }
2160 
plannedEffortCostPrDay(const Resource * resource,QDate start,QDate end,long id,EffortCostCalculationType typ) const2161 EffortCostMap Project::plannedEffortCostPrDay(const Resource *resource, QDate  start, QDate end, long id, EffortCostCalculationType typ) const
2162 {
2163     //debugPlan<<start<<end<<id;
2164     EffortCostMap ec;
2165     QListIterator<Node*> it(childNodeIterator());
2166     while (it.hasNext()) {
2167         ec += it.next() ->plannedEffortCostPrDay(resource, start, end, id, typ);
2168     }
2169     return ec;
2170 }
2171 
actualEffortCostPrDay(QDate start,QDate end,long id,EffortCostCalculationType typ) const2172 EffortCostMap Project::actualEffortCostPrDay(QDate  start, QDate end, long id, EffortCostCalculationType typ) const
2173 {
2174     //debugPlan<<start<<end<<id;
2175     EffortCostMap ec;
2176     QListIterator<Node*> it(childNodeIterator());
2177     while (it.hasNext()) {
2178         ec += it.next() ->actualEffortCostPrDay(start, end, id, typ);
2179     }
2180     return ec;
2181 }
2182 
actualEffortCostPrDay(const Resource * resource,QDate start,QDate end,long id,EffortCostCalculationType typ) const2183 EffortCostMap Project::actualEffortCostPrDay(const Resource *resource, QDate  start, QDate end, long id,  EffortCostCalculationType typ) const
2184 {
2185     //debugPlan<<start<<end<<id;
2186     EffortCostMap ec;
2187     QListIterator<Node*> it(childNodeIterator());
2188     while (it.hasNext()) {
2189         ec += it.next() ->actualEffortCostPrDay(resource, start, end, id, typ);
2190     }
2191     return ec;
2192 }
2193 
2194 // Returns the total planned effort for this project (or subproject)
plannedEffort(long id,EffortCostCalculationType typ) const2195 Duration Project::plannedEffort(long id, EffortCostCalculationType typ) const
2196 {
2197     //debugPlan;
2198     Duration eff;
2199     QListIterator<Node*> it(childNodeIterator());
2200     while (it.hasNext()) {
2201         eff += it.next() ->plannedEffort(id, typ);
2202     }
2203     return eff;
2204 }
2205 
2206 // Returns the total planned effort for this project (or subproject) on date
plannedEffort(QDate date,long id,EffortCostCalculationType typ) const2207 Duration Project::plannedEffort(QDate date, long id, EffortCostCalculationType typ) const
2208 {
2209     //debugPlan;
2210     Duration eff;
2211     QListIterator<Node*> it(childNodeIterator());
2212     while (it.hasNext()) {
2213         eff += it.next() ->plannedEffort(date, id, typ);
2214     }
2215     return eff;
2216 }
2217 
2218 // Returns the total planned effort for this project (or subproject) upto and including date
plannedEffortTo(QDate date,long id,EffortCostCalculationType typ) const2219 Duration Project::plannedEffortTo(QDate date, long id, EffortCostCalculationType typ) const
2220 {
2221     //debugPlan;
2222     Duration eff;
2223     QListIterator<Node*> it(childNodeIterator());
2224     while (it.hasNext()) {
2225         eff += it.next() ->plannedEffortTo(date, id, typ);
2226     }
2227     return eff;
2228 }
2229 
2230 // Returns the total actual effort for this project (or subproject) upto and including date
actualEffortTo(QDate date) const2231 Duration Project::actualEffortTo(QDate date) const
2232 {
2233     //debugPlan;
2234     Duration eff;
2235     QListIterator
2236     <Node*> it(childNodeIterator());
2237     while (it.hasNext()) {
2238         eff += it.next() ->actualEffortTo(date);
2239     }
2240     return eff;
2241 }
2242 
2243 // Returns the total planned effort for this project (or subproject) upto and including date
plannedCostTo(QDate date,long id,EffortCostCalculationType typ) const2244 double Project::plannedCostTo(QDate date, long id, EffortCostCalculationType typ) const
2245 {
2246     //debugPlan;
2247     double c = 0;
2248     QListIterator
2249     <Node*> it(childNodeIterator());
2250     while (it.hasNext()) {
2251         c += it.next() ->plannedCostTo(date, id, typ);
2252     }
2253     return c;
2254 }
2255 
2256 // Returns the total actual cost for this project (or subproject) upto and including date
actualCostTo(long int id,QDate date) const2257 EffortCost Project::actualCostTo(long int id, QDate date) const
2258 {
2259     //debugPlan;
2260     EffortCost c;
2261     QListIterator<Node*> it(childNodeIterator());
2262     while (it.hasNext()) {
2263         c += it.next() ->actualCostTo(id, date);
2264     }
2265     return c;
2266 }
2267 
budgetedWorkPerformed(QDate date,long id) const2268 Duration Project::budgetedWorkPerformed(QDate date, long id) const
2269 {
2270     //debugPlan;
2271     Duration e;
2272     foreach (Node *n, childNodeIterator()) {
2273         e += n->budgetedWorkPerformed(date, id);
2274     }
2275     return e;
2276 }
2277 
budgetedCostPerformed(QDate date,long id) const2278 double Project::budgetedCostPerformed(QDate date, long id) const
2279 {
2280     //debugPlan;
2281     double c = 0.0;
2282     foreach (Node *n, childNodeIterator()) {
2283         c += n->budgetedCostPerformed(date, id);
2284     }
2285     return c;
2286 }
2287 
effortPerformanceIndex(QDate date,long id) const2288 double Project::effortPerformanceIndex(QDate date, long id) const
2289 {
2290     //debugPlan;
2291     debugPlan<<date<<id;
2292     Duration b = budgetedWorkPerformed(date, id);
2293     if (b == Duration::zeroDuration) {
2294         return 1.0;
2295     }
2296     Duration a = actualEffortTo(date);
2297     if (b == Duration::zeroDuration) {
2298         return 1.0;
2299     }
2300     return b.toDouble() / a.toDouble();
2301 }
2302 
schedulePerformanceIndex(QDate date,long id) const2303 double Project::schedulePerformanceIndex(QDate date, long id) const
2304 {
2305     //debugPlan;
2306     double r = 1.0;
2307     double s = bcws(date, id);
2308     double p = bcwp(date, id);
2309     if (s > 0.0) {
2310         r = p / s;
2311     }
2312     debugPlan<<s<<p<<r;
2313     return r;
2314 }
2315 
bcws(QDate date,long id) const2316 double Project::bcws(QDate date, long id) const
2317 {
2318     //debugPlan;
2319     double c = plannedCostTo(date, id, ECCT_EffortWork);
2320     debugPlan<<c;
2321     return c;
2322 }
2323 
bcwp(long id) const2324 double Project::bcwp(long id) const
2325 {
2326     QDate date = QDate::currentDate();
2327     return bcwp(date, id);
2328 }
2329 
bcwp(QDate date,long id) const2330 double Project::bcwp(QDate date, long id) const
2331 {
2332     debugPlan<<date<<id;
2333     QDate start = startTime(id).date();
2334     QDate end = endTime(id).date();
2335     EffortCostMap plan = plannedEffortCostPrDay(start, end, id, ECCT_EffortWork);
2336     EffortCostMap actual = actualEffortCostPrDay(start, (end > date ? end : date), id);
2337 
2338     double budgetAtCompletion;
2339     double plannedCompleted;
2340     double budgetedCompleted;
2341     bool useEffort = false; //FIXME
2342     if (useEffort) {
2343         budgetAtCompletion = plan.totalEffort().toDouble(Duration::Unit_h);
2344         plannedCompleted = plan.effortTo(date).toDouble(Duration::Unit_h);
2345         //actualCompleted = actual.effortTo(date).toDouble(Duration::Unit_h);
2346         budgetedCompleted = budgetedWorkPerformed(date, id).toDouble(Duration::Unit_h);
2347     } else {
2348         budgetAtCompletion = plan.totalCost();
2349         plannedCompleted = plan.costTo(date);
2350         budgetedCompleted = budgetedCostPerformed(date, id);
2351     }
2352     double c = 0.0;
2353     if (budgetAtCompletion > 0.0) {
2354         double percentageCompletion = budgetedCompleted / budgetAtCompletion;
2355         c = budgetAtCompletion * percentageCompletion; //??
2356         debugPlan<<percentageCompletion<<budgetAtCompletion<<budgetedCompleted<<plannedCompleted;
2357     }
2358     return c;
2359 }
2360 
addCalendar(Calendar * calendar,Calendar * parent,int index)2361 void Project::addCalendar(Calendar *calendar, Calendar *parent, int index)
2362 {
2363     Q_ASSERT(calendar != 0);
2364     //debugPlan<<calendar->name()<<","<<(parent?parent->name():"No parent");
2365     int row = parent == 0 ? m_calendars.count() : parent->calendars().count();
2366     if (index >= 0 && index < row) {
2367         row = index;
2368     }
2369     emit calendarToBeAdded(parent, row);
2370     calendar->setProject(this);
2371     if (parent == 0) {
2372         calendar->setParentCal(0); // in case
2373         m_calendars.insert(row, calendar);
2374     } else {
2375         calendar->setParentCal(parent, row);
2376     }
2377     if (calendar->isDefault()) {
2378         setDefaultCalendar(calendar);
2379     }
2380     setCalendarId(calendar);
2381     emit calendarAdded(calendar);
2382     emit projectChanged();
2383 }
2384 
takeCalendar(Calendar * calendar)2385 void Project::takeCalendar(Calendar *calendar)
2386 {
2387     emit calendarToBeRemoved(calendar);
2388     removeCalendarId(calendar->id());
2389     if (calendar == m_defaultCalendar) {
2390         m_defaultCalendar = 0;
2391     }
2392     if (calendar->parentCal() == 0) {
2393         int i = indexOf(calendar);
2394         if (i != -1) {
2395             m_calendars.removeAt(i);
2396         }
2397     } else {
2398         calendar->setParentCal(0);
2399     }
2400     emit calendarRemoved(calendar);
2401     calendar->setProject(0);
2402     emit projectChanged();
2403 }
2404 
indexOf(const Calendar * calendar) const2405 int Project::indexOf(const Calendar *calendar) const
2406 {
2407     return m_calendars.indexOf(const_cast<Calendar*>(calendar));
2408 }
2409 
calendar(const QString & id) const2410 Calendar *Project::calendar(const QString& id) const
2411 {
2412     return findCalendar(id);
2413 }
2414 
calendarByName(const QString & name) const2415 Calendar *Project::calendarByName(const QString& name) const
2416 {
2417     foreach(Calendar *c, calendarIdDict) {
2418         if (c->name() == name) {
2419             return c;
2420         }
2421     }
2422     return 0;
2423 }
2424 
calendars() const2425 const QList<Calendar*> &Project::calendars() const
2426 {
2427     return m_calendars;
2428 }
2429 
allCalendars() const2430 QList<Calendar*> Project::allCalendars() const
2431 {
2432     return calendarIdDict.values();
2433 }
2434 
calendarNames() const2435 QStringList Project::calendarNames() const
2436 {
2437     QStringList lst;
2438     foreach(Calendar *c, calendarIdDict) {
2439         lst << c->name();
2440     }
2441     return lst;
2442 }
2443 
setCalendarId(Calendar * calendar)2444 bool Project::setCalendarId(Calendar *calendar)
2445 {
2446     if (calendar == 0) {
2447         return false;
2448     }
2449     if (! calendar->id().isEmpty()) {
2450         Calendar *c = findCalendar(calendar->id());
2451         if (calendar == c) {
2452             return true;
2453         } else if (c == 0) {
2454             insertCalendarId(calendar->id(), calendar);
2455             return true;
2456         }
2457     }
2458     QString id = uniqueCalendarId();
2459     calendar->setId(id);
2460     if (id.isEmpty()) {
2461         return false;
2462     }
2463     insertCalendarId(id, calendar);
2464     return true;
2465 }
2466 
uniqueCalendarId() const2467 QString Project::uniqueCalendarId() const {
2468     QString id = generateId();
2469     while (calendarIdDict.contains(id)) {
2470         id = generateId();
2471     }
2472     return id;
2473 }
2474 
setDefaultCalendar(Calendar * cal)2475 void Project::setDefaultCalendar(Calendar *cal)
2476 {
2477     if (m_defaultCalendar) {
2478         m_defaultCalendar->setDefault(false);
2479     }
2480     m_defaultCalendar = cal;
2481     if (cal) {
2482         cal->setDefault(true);
2483     }
2484     emit defaultCalendarChanged(cal);
2485     emit projectChanged();
2486 }
2487 
setStandardWorktime(StandardWorktime * worktime)2488 void Project::setStandardWorktime(StandardWorktime * worktime)
2489 {
2490     if (m_standardWorktime != worktime) {
2491         delete m_standardWorktime;
2492         m_standardWorktime = worktime;
2493         m_standardWorktime->setProject(this);
2494         emit standardWorktimeChanged(worktime);
2495     }
2496 }
2497 
emitDocumentAdded(Node * node,Document * doc,int index)2498 void Project::emitDocumentAdded(Node *node , Document *doc , int index)
2499 {
2500     emit documentAdded(node, doc, index);
2501 }
2502 
emitDocumentRemoved(Node * node,Document * doc,int index)2503 void Project::emitDocumentRemoved(Node *node , Document *doc , int index)
2504 {
2505     emit documentRemoved(node, doc, index);
2506 }
2507 
emitDocumentChanged(Node * node,Document * doc,int index)2508 void Project::emitDocumentChanged(Node *node , Document *doc , int index)
2509 {
2510     emit documentChanged(node, doc, index);
2511 }
2512 
linkExists(const Node * par,const Node * child) const2513 bool Project::linkExists(const Node *par, const Node *child) const
2514 {
2515     if (par == 0 || child == 0 || par == child || par->isDependChildOf(child)) {
2516         return false;
2517     }
2518     foreach (Relation *r, par->dependChildNodes()) {
2519         if (r->child() == child) {
2520             return true;
2521         }
2522     }
2523     return false;
2524 }
2525 
legalToLink(const Node * par,const Node * child) const2526 bool Project::legalToLink(const Node *par, const Node *child) const
2527 {
2528     //debugPlan<<par.name()<<" ("<<par.numDependParentNodes()<<" parents)"<<child.name()<<" ("<<child.numDependChildNodes()<<" children)";
2529 
2530     if (par == 0 || child == 0 || par == child || par->isDependChildOf(child)) {
2531         return false;
2532     }
2533     if (linkExists(par, child)) {
2534         return false;
2535     }
2536     bool legal = true;
2537     // see if par/child is related
2538     if (legal && (par->isParentOf(child) || child->isParentOf(par))) {
2539         legal = false;
2540     }
2541     if (legal)
2542         legal = legalChildren(par, child);
2543     if (legal)
2544         legal = legalParents(par, child);
2545 
2546     if (legal) {
2547         foreach (Node *p, par->childNodeIterator()) {
2548             if (! legalToLink(p, child)) {
2549                 return false;
2550             }
2551         }
2552     }
2553     return legal;
2554 }
2555 
legalParents(const Node * par,const Node * child) const2556 bool Project::legalParents(const Node *par, const Node *child) const
2557 {
2558     bool legal = true;
2559     //debugPlan<<par->name()<<" ("<<par->numDependParentNodes()<<" parents)"<<child->name()<<" ("<<child->numDependChildNodes()<<" children)";
2560     for (int i = 0; i < par->numDependParentNodes() && legal; ++i) {
2561         Node *pNode = par->getDependParentNode(i) ->parent();
2562         if (child->isParentOf(pNode) || pNode->isParentOf(child)) {
2563             //debugPlan<<"Found:"<<pNode->name()<<" is related to"<<child->name();
2564             legal = false;
2565         } else {
2566             legal = legalChildren(pNode, child);
2567         }
2568         if (legal)
2569             legal = legalParents(pNode, child);
2570     }
2571     return legal;
2572 }
2573 
legalChildren(const Node * par,const Node * child) const2574 bool Project::legalChildren(const Node *par, const Node *child) const
2575 {
2576     bool legal = true;
2577     //debugPlan<<par->name()<<" ("<<par->numDependParentNodes()<<" parents)"<<child->name()<<" ("<<child->numDependChildNodes()<<" children)";
2578     for (int j = 0; j < child->numDependChildNodes() && legal; ++j) {
2579         Node *cNode = child->getDependChildNode(j) ->child();
2580         if (par->isParentOf(cNode) || cNode->isParentOf(par)) {
2581             //debugPlan<<"Found:"<<par->name()<<" is related to"<<cNode->name();
2582             legal = false;
2583         } else {
2584             legal = legalChildren(par, cNode);
2585         }
2586     }
2587     return legal;
2588 }
2589 
wbsDefinition()2590 WBSDefinition &Project::wbsDefinition()
2591 {
2592     return m_wbsDefinition;
2593 }
2594 
setWbsDefinition(const WBSDefinition & def)2595 void Project::setWbsDefinition(const WBSDefinition &def)
2596 {
2597     //debugPlan;
2598     m_wbsDefinition = def;
2599     emit wbsDefinitionChanged();
2600     emit projectChanged();
2601 }
2602 
generateWBSCode(QList<int> & indexes,bool sortable) const2603 QString Project::generateWBSCode(QList<int> &indexes, bool sortable) const
2604 {
2605     QString code = m_wbsDefinition.projectCode();
2606     if (sortable) {
2607         int fw = (nodeIdDict.count() / 10) + 1;
2608         QLatin1Char fc('0');
2609         foreach (int index, indexes) {
2610             code += ".%1";
2611             code = code.arg(QString::number(index), fw, fc);
2612         }
2613         //debugPlan<<code<<"------------------";
2614     } else {
2615         if (! code.isEmpty() && ! indexes.isEmpty()) {
2616             code += m_wbsDefinition.projectSeparator();
2617         }
2618         int level = 1;
2619         foreach (int index, indexes) {
2620             code += m_wbsDefinition.code(index + 1, level);
2621             if (level < indexes.count()) {
2622                 // not last level, add separator also
2623                 code += m_wbsDefinition.separator(level);
2624             }
2625             ++level;
2626         }
2627     }
2628     //debugPlan<<code;
2629     return code;
2630 }
2631 
setCurrentSchedule(long id)2632 void Project::setCurrentSchedule(long id)
2633 {
2634     //debugPlan;
2635     setCurrentSchedulePtr(findSchedule(id));
2636     Node::setCurrentSchedule(id);
2637     QHash<QString, Resource*> hash = resourceIdDict;
2638     foreach (Resource * r, hash) {
2639         r->setCurrentSchedule(id);
2640     }
2641     emit currentScheduleChanged();
2642     emit projectChanged();
2643 }
2644 
scheduleManager(long id) const2645 ScheduleManager *Project::scheduleManager(long id) const
2646 {
2647     foreach (ScheduleManager *sm, m_managers) {
2648         if (sm->scheduleId() == id) {
2649             return sm;
2650         }
2651     }
2652     return 0;
2653 }
2654 
scheduleManager(const QString & id) const2655 ScheduleManager *Project::scheduleManager(const QString &id) const
2656 {
2657     return m_managerIdMap.value(id);
2658 }
2659 
findScheduleManagerByName(const QString & name) const2660 ScheduleManager *Project::findScheduleManagerByName(const QString &name) const
2661 {
2662     //debugPlan;
2663     ScheduleManager *m = 0;
2664     foreach(ScheduleManager *sm, m_managers) {
2665         m = sm->findManager(name);
2666         if (m) {
2667             break;
2668         }
2669     }
2670     return m;
2671 }
2672 
allScheduleManagers() const2673 QList<ScheduleManager*> Project::allScheduleManagers() const
2674 {
2675     QList<ScheduleManager*> lst;
2676     foreach (ScheduleManager *sm, m_managers) {
2677         lst << sm;
2678         lst << sm->allChildren();
2679     }
2680     return lst;
2681 }
2682 
uniqueScheduleName() const2683 QString Project::uniqueScheduleName() const {
2684     //debugPlan;
2685     QString n = i18n("Plan");
2686     bool unique = findScheduleManagerByName(n) == 0;
2687     if (unique) {
2688         return n;
2689     }
2690     n += " %1";
2691     int i = 1;
2692     for (; true; ++i) {
2693         unique = findScheduleManagerByName(n.arg(i)) == 0;
2694         if (unique) {
2695             break;
2696         }
2697     }
2698     return n.arg(i);
2699 }
2700 
addScheduleManager(ScheduleManager * sm,ScheduleManager * parent,int index)2701 void Project::addScheduleManager(ScheduleManager *sm, ScheduleManager *parent, int index)
2702 {
2703     int row = parent == 0 ? m_managers.count() : parent->childCount();
2704     if (index >= 0 && index < row) {
2705         row = index;
2706     }
2707     if (parent == 0) {
2708         emit scheduleManagerToBeAdded(parent, row);
2709         m_managers.insert(row, sm);
2710     } else {
2711         emit scheduleManagerToBeAdded(parent, row);
2712         sm->setParentManager(parent, row);
2713     }
2714     if (sm->managerId().isEmpty()) {
2715         sm->setManagerId(uniqueScheduleManagerId());
2716     }
2717     Q_ASSERT(! m_managerIdMap.contains(sm->managerId()));
2718     m_managerIdMap.insert(sm->managerId(), sm);
2719 
2720     emit scheduleManagerAdded(sm);
2721     emit projectChanged();
2722     //debugPlan<<"Added:"<<sm->name()<<", now"<<m_managers.count();
2723 }
2724 
takeScheduleManager(ScheduleManager * sm)2725 int Project::takeScheduleManager(ScheduleManager *sm)
2726 {
2727     foreach (ScheduleManager *s, sm->children()) {
2728         takeScheduleManager(s);
2729     }
2730     if (sm->scheduling()) {
2731         sm->stopCalculation();
2732     }
2733     int index = -1;
2734     if (sm->parentManager()) {
2735         int index = sm->parentManager()->indexOf(sm);
2736         if (index >= 0) {
2737             emit scheduleManagerToBeRemoved(sm);
2738             sm->setParentManager(0);
2739             m_managerIdMap.remove(sm->managerId());
2740             emit scheduleManagerRemoved(sm);
2741             emit projectChanged();
2742         }
2743     } else {
2744         index = indexOf(sm);
2745         if (index >= 0) {
2746             emit scheduleManagerToBeRemoved(sm);
2747             m_managers.removeAt(indexOf(sm));
2748             m_managerIdMap.remove(sm->managerId());
2749             emit scheduleManagerRemoved(sm);
2750             emit projectChanged();
2751         }
2752     }
2753     return index;
2754 }
2755 
swapScheduleManagers(ScheduleManager * from,ScheduleManager * to)2756 void Project::swapScheduleManagers(ScheduleManager *from, ScheduleManager *to)
2757 {
2758     emit scheduleManagersSwapped(from, to);
2759 }
2760 
moveScheduleManager(ScheduleManager * sm,ScheduleManager * newparent,int newindex)2761 void Project::moveScheduleManager(ScheduleManager *sm, ScheduleManager *newparent, int newindex)
2762 {
2763     //debugPlan<<sm->name()<<newparent<<newindex;
2764     emit scheduleManagerToBeMoved(sm);
2765     if (! sm->parentManager()) {
2766         m_managers.removeAt(indexOf(sm));
2767     }
2768     sm->setParentManager(newparent, newindex);
2769     if (! newparent) {
2770         m_managers.insert(newindex, sm);
2771     }
2772     emit scheduleManagerMoved(sm, newindex);
2773 }
2774 
isScheduleManager(void * ptr) const2775 bool Project::isScheduleManager(void *ptr) const
2776 {
2777     const ScheduleManager *sm = static_cast<ScheduleManager*>(ptr);
2778     if (indexOf(sm) >= 0) {
2779         return true;
2780     }
2781     foreach (ScheduleManager *p, m_managers) {
2782         if (p->isParentOf(sm)) {
2783             return true;
2784         }
2785     }
2786     return false;
2787 }
2788 
createScheduleManager(const QString & name)2789 ScheduleManager *Project::createScheduleManager(const QString &name)
2790 {
2791     //debugPlan<<name;
2792     ScheduleManager *sm = new ScheduleManager(*this, name);
2793     return sm;
2794 }
2795 
createScheduleManager()2796 ScheduleManager *Project::createScheduleManager()
2797 {
2798     //debugPlan;
2799     return createScheduleManager(uniqueScheduleName());
2800 }
2801 
uniqueScheduleManagerId() const2802 QString Project::uniqueScheduleManagerId() const
2803 {
2804     QString ident = KRandom::randomString(10);
2805     while (m_managerIdMap.contains(ident)) {
2806         ident = KRandom::randomString(10);
2807     }
2808     return ident;
2809 }
2810 
isBaselined(long id) const2811 bool Project::isBaselined(long id) const
2812 {
2813     if (id == ANYSCHEDULED) {
2814         foreach (ScheduleManager *sm, allScheduleManagers()) {
2815             if (sm->isBaselined()) {
2816                 return true;
2817             }
2818         }
2819         return false;
2820     }
2821     Schedule *s = schedule(id);
2822     return s == 0 ? false : s->isBaselined();
2823 }
2824 
createSchedule(const QString & name,Schedule::Type type)2825 MainSchedule *Project::createSchedule(const QString& name, Schedule::Type type)
2826 {
2827     //debugPlan<<"No of schedules:"<<m_schedules.count();
2828     MainSchedule *sch = new MainSchedule();
2829     sch->setName(name);
2830     sch->setType(type);
2831     addMainSchedule(sch);
2832     return sch;
2833 }
2834 
addMainSchedule(MainSchedule * sch)2835 void Project::addMainSchedule(MainSchedule *sch)
2836 {
2837     if (sch == 0) {
2838         return;
2839     }
2840     //debugPlan<<"No of schedules:"<<m_schedules.count();
2841     long i = 1; // keep this positive (negative values are special...)
2842     while (m_schedules.contains(i)) {
2843         ++i;
2844     }
2845     sch->setId(i);
2846     sch->setNode(this);
2847     addSchedule(sch);
2848 }
2849 
removeCalendarId(const QString & id)2850 bool Project::removeCalendarId(const QString &id)
2851 {
2852     //debugPlan <<"id=" << id;
2853     return calendarIdDict.remove(id);
2854 }
2855 
insertCalendarId(const QString & id,Calendar * calendar)2856 void Project::insertCalendarId(const QString &id, Calendar *calendar)
2857 {
2858     //debugPlan <<"id=" << id <<":" << calendar->name();
2859     calendarIdDict.insert(id, calendar);
2860 }
2861 
changed(Node * node,int property)2862 void Project::changed(Node *node, int property)
2863 {
2864     if (m_parent == 0) {
2865         Node::changed(node, property); // reset cache
2866         if (property != Node::TypeProperty) {
2867             // add/remove node is handled elsewhere
2868             emit nodeChanged(node, property);
2869             emit projectChanged();
2870         }
2871         return;
2872     }
2873     Node::changed(node, property);
2874 }
2875 
changed(ResourceGroup * group)2876 void Project::changed(ResourceGroup *group)
2877 {
2878     //debugPlan;
2879     emit resourceGroupChanged(group);
2880     emit projectChanged();
2881 }
2882 
changed(ScheduleManager * sm,int property)2883 void Project::changed(ScheduleManager *sm, int property)
2884 {
2885     emit scheduleManagerChanged(sm, property);
2886     emit projectChanged();
2887 }
2888 
changed(MainSchedule * sch)2889 void Project::changed(MainSchedule *sch)
2890 {
2891     //debugPlan<<sch->id();
2892     emit scheduleChanged(sch);
2893     emit projectChanged();
2894 }
2895 
sendScheduleToBeAdded(const ScheduleManager * sm,int row)2896 void Project::sendScheduleToBeAdded(const ScheduleManager *sm, int row)
2897 {
2898     emit scheduleToBeAdded(sm, row);
2899 }
2900 
sendScheduleAdded(const MainSchedule * sch)2901 void Project::sendScheduleAdded(const MainSchedule *sch)
2902 {
2903     //debugPlan<<sch->id();
2904     emit scheduleAdded(sch);
2905     emit projectChanged();
2906 }
2907 
sendScheduleToBeRemoved(const MainSchedule * sch)2908 void Project::sendScheduleToBeRemoved(const MainSchedule *sch)
2909 {
2910     //debugPlan<<sch->id();
2911     emit scheduleToBeRemoved(sch);
2912 }
2913 
sendScheduleRemoved(const MainSchedule * sch)2914 void Project::sendScheduleRemoved(const MainSchedule *sch)
2915 {
2916     //debugPlan<<sch->id();
2917     emit scheduleRemoved(sch);
2918     emit projectChanged();
2919 }
2920 
changed(Resource * resource)2921 void Project::changed(Resource *resource)
2922 {
2923     emit resourceChanged(resource);
2924     emit projectChanged();
2925 }
2926 
changed(Calendar * cal)2927 void Project::changed(Calendar *cal)
2928 {
2929     emit calendarChanged(cal);
2930     emit projectChanged();
2931 }
2932 
changed(StandardWorktime * w)2933 void Project::changed(StandardWorktime *w)
2934 {
2935     emit standardWorktimeChanged(w);
2936     emit projectChanged();
2937 }
2938 
addRelation(Relation * rel,bool check)2939 bool Project::addRelation(Relation *rel, bool check)
2940 {
2941     if (rel->parent() == 0 || rel->child() == 0) {
2942         return false;
2943     }
2944     if (check && !legalToLink(rel->parent(), rel->child())) {
2945         return false;
2946     }
2947     emit relationToBeAdded(rel, rel->parent()->numDependChildNodes(), rel->child()->numDependParentNodes());
2948     rel->parent()->addDependChildNode(rel);
2949     rel->child()->addDependParentNode(rel);
2950     emit relationAdded(rel);
2951     emit projectChanged();
2952     return true;
2953 }
2954 
takeRelation(Relation * rel)2955 void Project::takeRelation(Relation *rel)
2956 {
2957     emit relationToBeRemoved(rel);
2958     rel->parent() ->takeDependChildNode(rel);
2959     rel->child() ->takeDependParentNode(rel);
2960     emit relationRemoved(rel);
2961     emit projectChanged();
2962 }
2963 
setRelationType(Relation * rel,Relation::Type type)2964 void Project::setRelationType(Relation *rel, Relation::Type type)
2965 {
2966     emit relationToBeModified(rel);
2967     rel->setType(type);
2968     emit relationModified(rel);
2969     emit projectChanged();
2970 }
2971 
setRelationLag(Relation * rel,const Duration & lag)2972 void Project::setRelationLag(Relation *rel, const Duration &lag)
2973 {
2974     emit relationToBeModified(rel);
2975     rel->setLag(lag);
2976     emit relationModified(rel);
2977     emit projectChanged();
2978 }
2979 
flatNodeList(Node * parent)2980 QList<Node*> Project::flatNodeList(Node *parent)
2981 {
2982     QList<Node*> lst;
2983     Node *p = parent == 0 ? this : parent;
2984     //debugPlan<<p->name()<<lst.count();
2985     foreach (Node *n, p->childNodeIterator()) {
2986         lst.append(n);
2987         if (n->numChildren() > 0) {
2988             lst += flatNodeList(n);
2989         }
2990     }
2991     return lst;
2992 }
2993 
setSchedulerPlugins(const QMap<QString,SchedulerPlugin * > & plugins)2994 void Project::setSchedulerPlugins(const QMap<QString, SchedulerPlugin*> &plugins)
2995 {
2996     m_schedulerPlugins = plugins;
2997     debugPlan<<m_schedulerPlugins;
2998 }
2999 
emitLocaleChanged()3000 void Project::emitLocaleChanged()
3001 {
3002     emit localeChanged();
3003 }
3004 
useSharedResources() const3005 bool Project::useSharedResources() const
3006 {
3007     return m_useSharedResources;
3008 }
3009 
setUseSharedResources(bool on)3010 void Project::setUseSharedResources(bool on)
3011 {
3012     m_useSharedResources = on;
3013 }
3014 
isSharedResourcesLoaded() const3015 bool Project::isSharedResourcesLoaded() const
3016 {
3017     return m_sharedResourcesLoaded;
3018 }
3019 
setSharedResourcesLoaded(bool on)3020 void Project::setSharedResourcesLoaded(bool on)
3021 {
3022     m_sharedResourcesLoaded = on;
3023 }
3024 
setSharedResourcesFile(const QString & file)3025 void Project::setSharedResourcesFile(const QString &file)
3026 {
3027     m_sharedResourcesFile = file;
3028 }
3029 
sharedResourcesFile() const3030 QString Project::sharedResourcesFile() const
3031 {
3032     return m_sharedResourcesFile;
3033 }
3034 
setSharedProjectsUrl(const QUrl & url)3035 void Project::setSharedProjectsUrl(const QUrl &url)
3036 {
3037     m_sharedProjectsUrl = url;
3038 }
3039 
sharedProjectsUrl() const3040 QUrl Project::sharedProjectsUrl() const
3041 {
3042     return m_sharedProjectsUrl;
3043 }
3044 
setLoadProjectsAtStartup(bool value)3045 void Project::setLoadProjectsAtStartup(bool value)
3046 {
3047     m_loadProjectsAtStartup = value;
3048 }
3049 
loadProjectsAtStartup() const3050 bool Project::loadProjectsAtStartup() const
3051 {
3052     return m_loadProjectsAtStartup;
3053 }
3054 
taskModules(bool includeLocal) const3055 QList<QUrl> Project::taskModules(bool includeLocal) const
3056 {
3057     if (!includeLocal && m_useLocalTaskModules) {
3058         QList<QUrl> lst = m_taskModules;
3059         lst.removeAll(m_localTaskModulesPath);
3060         return lst;
3061     }
3062     return m_taskModules;
3063 }
3064 
setTaskModules(const QList<QUrl> modules,bool useLocalTaskModules)3065 void Project::setTaskModules(const QList<QUrl> modules, bool useLocalTaskModules)
3066 {
3067     m_taskModules = modules;
3068     m_useLocalTaskModules = useLocalTaskModules;
3069     if (m_useLocalTaskModules && m_localTaskModulesPath.isValid()) {
3070         m_taskModules.prepend(m_localTaskModulesPath);
3071     }
3072     emit taskModulesChanged(m_taskModules);
3073 }
3074 
useLocalTaskModules() const3075 bool Project::useLocalTaskModules() const
3076 {
3077     return m_useLocalTaskModules;
3078 }
3079 
setUseLocalTaskModules(bool value,bool emitChanged)3080 void Project::setUseLocalTaskModules(bool value, bool emitChanged)
3081 {
3082     if (m_useLocalTaskModules) {
3083         m_taskModules.removeAll(m_localTaskModulesPath);
3084     }
3085     m_useLocalTaskModules = value;
3086     if (m_useLocalTaskModules && m_localTaskModulesPath.isValid()) {
3087         m_taskModules.prepend(m_localTaskModulesPath);
3088     }
3089     if (emitChanged) {
3090         emit taskModulesChanged(m_taskModules);
3091     }
3092 }
3093 
setLocalTaskModulesPath(const QUrl & url)3094 void Project::setLocalTaskModulesPath(const QUrl &url)
3095 {
3096     m_taskModules.removeAll(m_localTaskModulesPath);
3097     m_localTaskModulesPath = url;
3098     if (m_useLocalTaskModules && url.isValid()) {
3099         m_taskModules.prepend(url);
3100     }
3101     emit taskModulesChanged(m_taskModules);
3102 }
3103 
3104 }  //KPlato namespace
3105