OSDN Git Service

b5f6c3523a2533bdecefa2b3cdf6140b636d42ae
[tjqt4port/tj2qt4.git] / taskjuggler / Task.cpp
1 /*
2  * task.cpp - TaskJuggler
3  *
4  * Copyright (c) 2001, 2002 by Chris Schlaeger <cs@suse.de>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of version 2 of the GNU General Public License as
8  * published by the Free Software Foundation.
9  *
10  * $Id$
11  */
12
13 /* -- DTD --
14  <!-- Task element, child of projects and for subtasks -->
15  <!ELEMENT Task         (Index, Name, ProjectID, Priority, complete,
16                          Type,
17                          ParentTask*, Note*,
18                          minStart, maxStart,
19                          minEnd, maxEnd,
20                          actualStart, actualEnd,
21                          planStart, planEnd,
22                          startBufferSize*, ActualStartBufferEnd*, PlanStartBufferEnd*,
23                          endBufferSize*, ActualEndBufferStart*, PlanEndBufferStart*,
24                          Resource*,
25                          SubTasks*, Previous*, Follower*,
26                          Allocations*, bookedResources* )>
27  <!ATTLIST Task         Id CDATA #REQUIRED>
28  <!ELEMENT Index        (#PCDATA)>
29  <!ELEMENT Name         (#PCDATA)>
30  <!ELEMENT ProjectID    (#PCDATA)>
31  <!ELEMENT Priority     (#PCDATA)>
32  <!ELEMENT complete     (#PCDATA)>
33  <!ELEMENT Type         (#PCDATA)>
34  <!ELEMENT ParentTask   (#PCDATA)>
35  <!ELEMENT Note         (#PCDATA)>
36  <!ELEMENT minStart     (#PCDATA)>
37  <!ELEMENT maxStart     (#PCDATA)>
38  <!ELEMENT minEnd       (#PCDATA)>
39  <!ELEMENT maxEnd       (#PCDATA)>
40  <!ELEMENT actualStart  (#PCDATA)>
41  <!ELEMENT actualEnd    (#PCDATA)>
42  <!ELEMENT planStart    (#PCDATA)>
43  <!ELEMENT planEnd      (#PCDATA)>
44  <!ELEMENT startBufferSize (#PCDATA)>
45  <!ELEMENT ActualStartBufferEnd (#PCDATA)>
46  <!ELEMENT PlanStartBufferEnd   (#PCDATA)>
47  <!ELEMENT endBufferSize        (#PCDATA)>
48  <!ELEMENT ActualEndBufferStart (#PCDATA)>
49  <!ELEMENT PlanEndBufferStart    (#PCDATA)>
50  <!ELEMENT SubTasks     (Task+)>
51  <!ELEMENT Previous     (#PCDATA)>
52  <!ELEMENT Follower     (#PCDATA)>
53  <!ELEMENT bookedResources (ResourceID+)>
54
55  <!-- Date values contain human readable date -->
56  <!ATTLIST minStart
57            humanReadable CDATA #REQUIRED>
58  <!ATTLIST maxStart
59            humanReadable CDATA #REQUIRED>
60  <!ATTLIST minEnd
61            humanReadable CDATA #REQUIRED>
62  <!ATTLIST maxEnd
63            humanReadable CDATA #REQUIRED>
64  <!ATTLIST actualStart
65            humanReadable CDATA #REQUIRED>
66  <!ATTLIST actualEnd
67            humanReadable CDATA #REQUIRED>
68  <!ATTLIST planStart
69            humanReadable CDATA #REQUIRED>
70  <!ATTLIST planEnd
71            humanReadable CDATA #REQUIRED>
72  <!ATTLIST ActualStartBufferEnd
73            humanReadable CDATA #REQUIRED>
74  <!ATTLIST PlanStartBufferEnd
75            humanReadable CDATA #REQUIRED>
76  <!ATTLIST ActualEndBufferStart
77            humanReadable CDATA #REQUIRED>
78  <!ATTLIST PlanEndBufferStart
79            humanReadable CDATA #REQUIRED>
80    /-- DTD --/
81 */
82  
83 #include <stdio.h>
84 #include <stdlib.h>
85
86 #include "debug.h"
87 #include "Task.h"
88 #include "Project.h"
89 #include "Allocation.h"
90
91 int Task::debugLevel = 0;
92 int Task::debugMode = -1;
93
94 Task*
95 TaskList::getTask(const QString& id)
96 {
97         for (Task* t = first(); t != 0; t = next())
98                 if (t->getId() == id)
99                         return t;
100
101         return 0;
102 }
103
104 Task::Task(Project* proj, const QString& id_, const QString& n, Task* p,
105                    const QString& f, int l)
106         : CoreAttributes(proj, id_, n, p), file(f), line(l)
107 {
108         allocations.setAutoDelete(TRUE);
109
110         scheduling = ASAP;
111         milestone = FALSE;
112         account = 0;
113         lastSlot = 0;
114         doneEffort = 0.0;
115         doneDuration = 0.0;
116         doneLength = 0.0;
117         schedulingDone = FALSE;
118         responsible = 0;
119
120         scenarios[0].startBuffer = 0.0;
121         scenarios[0].endBuffer = 0.0;
122         scenarios[0].startCredit = 0.0;
123         scenarios[0].endCredit = 0.0;
124         
125         if (p)
126         {
127                 // Inherit flags from parent task.
128                 for (QStringList::Iterator it = p->flags.begin();
129                          it != p->flags.end(); ++it)
130                         addFlag(*it);
131
132                 // Set attributes that are inherited from parent task.
133                 projectId = p->projectId;
134                 priority = p->priority;
135                 minStart = p->minStart;
136                 maxStart = p->maxEnd;
137                 minEnd = p->minStart; 
138                 maxEnd = p->maxEnd;
139                 responsible = p->responsible;
140                 account = p->account;
141                 scheduling = p->scheduling;
142
143                 // Inherit depends from parent. Relative IDs need to get another '!'.
144                 dependsIds = p->dependsIds;
145                 for (QStringList::Iterator it = dependsIds.begin();
146                          it != dependsIds.end(); ++it)
147                 {
148                         if ((*it)[0] == '!')
149                                 *it = '!' + *it;
150                 }
151                 
152                 // Inherit precedes from parent. Relative IDs need to get another '!'.
153                 precedesIds = p->precedesIds;
154                 for (QStringList::Iterator it = precedesIds.begin();
155                          it != precedesIds.end(); ++it)
156                 {
157                         if ((*it)[0] == '!')
158                                 *it = '!' + *it;
159                 }
160         }
161         else
162         {
163                 // Set attributes that are inherited from global attributes.
164                 projectId = proj->getCurrentId();
165                 priority = proj->getPriority();
166                 minStart = minEnd = proj->getStart();
167                 maxStart = maxEnd = proj->getEnd();
168         }
169
170         start = end = 0;
171         duration = length = effort = 0.0;
172 }
173
174 bool
175 Task::addDepends(const QString& rid)
176 {
177         dependsIds.append(rid);
178         return TRUE;
179 }
180
181 bool
182 Task::addPrecedes(const QString& rid)
183 {
184         precedesIds.append(rid);
185         return TRUE;
186 }
187
188 void
189 Task::fatalError(const char* msg, ...) const
190 {
191         va_list ap;
192         va_start(ap, msg);
193         char buf[1024];
194         vsnprintf(buf, 1024, msg, ap);
195         va_end(ap);
196         
197         qWarning("%s:%d:%s\n", file.latin1(), line, buf);
198 }
199
200 void
201 Task::schedule(time_t& date, time_t slotDuration)
202 {
203         // Has the task been scheduled already or is it a container?
204         if (schedulingDone || !sub.isEmpty())
205                 return;
206
207         if (DEBUGTS(15))
208                 qWarning("Trying to schedule %s at %s",
209                                  id.latin1(), time2tjp(date).latin1());
210
211         if (scheduling == Task::ASAP)
212         {
213                 if (start == 0)
214                         return;
215                 if (lastSlot == 0)
216                 {
217                         lastSlot = start - 1;
218                         doneEffort = 0.0;
219                         doneDuration = 0.0;
220                         doneLength = 0.0;
221                         workStarted = FALSE;
222                         tentativeEnd = date + slotDuration - 1;
223                         if (DEBUGTS(5))
224                                 qWarning("Scheduling of %s starts at %s (%s)",
225                                                  id.latin1(), time2tjp(lastSlot).latin1(),
226                                                  time2tjp(date).latin1());
227                 }
228                 /* Do not schedule anything if the time slot is not directly
229                  * following the time slot that was previously scheduled. */
230                 if (!((date - slotDuration <= lastSlot) && (lastSlot < date)))
231                         return;
232                 lastSlot = date + slotDuration - 1;
233         }
234         else
235         {
236                 if (end == 0)
237                         return;
238                 if (lastSlot == 0)
239                 {
240                         lastSlot = end + 1;
241                         doneEffort = 0.0;
242                         doneDuration = 0.0;
243                         doneLength = 0.0;
244                         workStarted = FALSE;
245                         tentativeStart = date;
246                         if (DEBUGTS(5))
247                                 qWarning("Scheduling of ALAP task %s starts at %s (%s)",
248                                                  id.latin1(), time2tjp(lastSlot).latin1(),
249                                                  time2tjp(date).latin1());
250                 }
251                 /* Do not schedule anything if the current time slot is not
252                  * directly preceeding the previously scheduled time slot. */
253                 if (!((date + slotDuration <= lastSlot) &&
254                         (lastSlot < date + 2 * slotDuration)))
255                         return;
256                 lastSlot = date;
257         }
258
259         if (DEBUGTS(10))
260                 qWarning("Scheduling %s at %s",
261                                  id.latin1(), time2tjp(date).latin1());
262
263         if ((duration > 0.0) || (length > 0.0))
264         {
265                 /* Length specifies the number of working days (as daily load)
266                  * and duration specifies the number of calender days. */
267                 if (!allocations.isEmpty())
268                         bookResources(date, slotDuration);
269
270                 doneDuration += ((double) slotDuration) / ONEDAY;
271                 if (project->isWorkingDay(date))
272                         doneLength += ((double) slotDuration) / ONEDAY;
273
274                 if (DEBUGTS(10))
275                         qWarning("Length: %f/%f   Duration: %f/%f",
276                                          doneLength, length,
277                                          doneDuration, duration);
278                 // Check whether we are done with this task.
279                 if ((length > 0.0 && doneLength >= length * 0.999999) ||
280                         (duration > 0.0 && doneDuration >= duration * 0.999999))
281                 {
282                         if (scheduling == ASAP)
283                         {
284                                 if (doneEffort > 0.0)
285                                 {
286                                         end = tentativeEnd;
287                                         date = end - slotDuration + 1;
288                                 }
289                                 else
290                                         end = date + slotDuration - 1;
291                                 propagateEnd();
292                         }
293                         else
294                         {
295                                 if (doneEffort > 0.0)
296                                 {
297                                         start = tentativeStart;
298                                         date = start;
299                                 }
300                                 else
301                                         start = date;
302                                 propagateStart();
303                         }
304                         schedulingDone = TRUE;
305                         return;
306                 }
307         }
308         else if (effort > 0.0)
309         {
310                 /* The effort of the task has been specified. We have to look
311                  * how much the resources can contribute over the following
312                  * workings days until we have reached the specified
313                  * effort. */
314                 bookResources(date, slotDuration);
315                 // Check whether we are done with this task.
316                 if (doneEffort >= effort)
317                 {
318                         if (scheduling == ASAP)
319                         {
320                                 end = tentativeEnd;
321                                 propagateEnd();
322                         }
323                         else
324                         {
325                                 start = tentativeStart;
326                                 propagateStart();
327                         }
328                         schedulingDone = TRUE;
329                         return;
330                 }
331         }
332         else if (milestone)
333         {
334                 // Task is a milestone.
335                 if (scheduling == ASAP)
336                 {
337                         end = start - 1;
338                         propagateEnd();
339                 }
340                 else
341                 {
342                         start = end + 1;
343                         propagateStart();
344                 }
345                 return;
346         }
347         else if (start != 0 && end != 0)
348         {
349                 // Task with start and end date but no duration criteria.
350                 if (!allocations.isEmpty() && !project->isVacation(date))
351                         bookResources(date, slotDuration);
352
353                 if ((scheduling == ASAP && (date + slotDuration) >= end) ||
354                         (scheduling == ALAP && date <= start))
355                 {
356                         schedulingDone = TRUE;
357                         return;
358                 }
359         }
360
361         return;
362 }
363
364 bool
365 Task::scheduleContainer(bool safeMode)
366 {
367         if (schedulingDone)
368                 return TRUE;
369
370         Task* t;
371         time_t nstart = 0;
372         time_t nend = 0;
373
374         // Check that this is really a container task
375         if ((t = subFirst()))
376         {
377                 if (t->start == 0 || t->end == 0)
378                         return TRUE;
379                 nstart = t->start;
380                 nend = t->end;
381         }
382         else
383                 return TRUE;
384
385         for (t = subNext() ; t != 0; t = subNext())
386         {
387                 /* Make sure that all sub tasks have been scheduled. If not we
388                  * can't yet schedule this task. */
389                 if (t->start == 0 || t->end == 0)
390                         return TRUE;
391
392                 if (t->start < nstart)
393                         nstart = t->start;
394                 if (t->end > nend)
395                         nend = t->end;
396         }
397
398         if (start == 0 || (!depends.isEmpty() && start < nstart))
399         {
400                 start = nstart;
401                 propagateStart(safeMode);
402         }
403
404         if (end == 0 || (!precedes.isEmpty() && nend < end))
405         {
406                 end = nend;
407                 propagateEnd(safeMode);
408         }
409
410         schedulingDone = TRUE;
411
412         return FALSE;
413 }
414
415 void
416 Task::propagateStart(bool safeMode)
417 {
418         if (start == 0)
419                 return;
420
421         if (DEBUGTS(11))
422                 qWarning("PS1: Setting start of %s to %s",
423                                  id.latin1(), time2tjp(start).latin1());
424
425         /* If one end of a milestone is fixed, then the other end can be set as
426          * well. */
427         if (milestone && end == 0)
428         {
429                 end = start - 1;
430                 schedulingDone = TRUE;
431                 propagateEnd(safeMode);
432         }
433
434         /* Set start date to all previous that have no start date yet, but are
435          * ALAP task or have no duration. */
436         for (Task* t = previous.first(); t != 0; t = previous.next())
437                 if (t->end == 0 && t->latestEnd() != 0 &&
438                         (t->scheduling == ALAP || 
439                          (t->effort == 0.0 && t->length == 0.0 && t->duration == 0.0 &&
440                           !t->milestone)))
441                 {
442                         t->end = t->latestEnd();
443                         if (DEBUGTS(11))
444                                 qWarning("PS2: Setting end of %s to %s",
445                                                  t->id.latin1(), time2tjp(t->end).latin1());
446                         /* Recursively propagate the end date */
447                         t->propagateEnd(safeMode);
448                 }
449
450         /* Propagate start time to sub tasks which have only an implicit
451          * dependancy on the parent task. Do not touch container tasks. */
452         for (Task* t = subFirst(); t != 0; t = subNext())
453         {
454                 if (t->start == 0 && t->previous.isEmpty() &&
455                         t->sub.isEmpty() && t->scheduling == ASAP)
456                 {
457                         t->start = start;
458                         if (DEBUGTS(11))
459                                 qWarning("PS3: Setting start of %s to %s",
460                                                  t->id.latin1(), time2tjp(t->start).latin1());   
461                         /* Recursively propagate the start date */
462                         t->propagateStart(safeMode);
463                 }
464         }
465
466         if (safeMode && parent)
467                 getParent()->scheduleContainer(TRUE);
468 }
469
470 void
471 Task::propagateEnd(bool safeMode)
472 {
473         if (end == 0)
474                 return;
475
476         if (DEBUGTS(11))
477                 qWarning("PE1: Setting end of %s to %s",
478                                  id.latin1(), time2tjp(end).latin1());
479
480         /* If one end of a milestone is fixed, then the other end can be set as
481          * well. */
482         if (milestone && start == 0)
483         {
484                 start = end + 1;
485                 schedulingDone = TRUE;
486                 propagateStart(safeMode);
487         }
488
489         /* Set start date to all followers that have no start date yet, but are
490          * ASAP task or have no duration. */
491         for (Task* t = followers.first(); t != 0; t = followers.next())
492                 if (t->start == 0 && t->earliestStart() != 0 && 
493                         (t->scheduling == ASAP ||
494                          (t->effort == 0.0 && t->length == 0.0 && t->duration == 0.0 &&
495                           !t->milestone)))
496                 {
497                         t->start = t->earliestStart();
498                         if (DEBUGTS(11))
499                                 qWarning("PE2: Setting start of %s to %s",
500                                                  t->id.latin1(), time2tjp(t->start).latin1());
501                         /* Recursively propagate the start date */
502                         t->propagateStart(safeMode);
503                 }
504         /* Propagate end time to sub tasks which have only an implicit
505          * dependancy on the parent task. Do not touch container tasks. */
506         for (Task* t = subFirst(); t != 0; t = subNext())
507                 if (t->end == 0 && t->followers.isEmpty() &&
508                         t->sub.isEmpty() && t->scheduling == ALAP)
509                 {
510                         t->end = end;
511                         if (DEBUGTS(11))
512                                 qWarning("PE3: Setting end of %s to %s",
513                                                  t->id.latin1(), time2tjp(t->end).latin1());
514                         /* Recursively propagate the end date */
515                         t->propagateEnd(safeMode);
516                 }
517
518         if (safeMode && parent)
519                 getParent()->scheduleContainer(TRUE);
520 }
521
522 void
523 Task::propagateInitialValues()
524 {
525         if (start != 0)
526                 propagateStart(FALSE);
527         if (end != 0)
528                 propagateEnd(FALSE);
529         // Check if the some data of sub tasks can already be propagated.
530         if (!sub.isEmpty())
531                 scheduleContainer(TRUE);
532 }
533
534 void
535 Task::setRunaway()
536 {
537         schedulingDone = TRUE;
538         runAway = TRUE;
539 }
540
541 bool
542 Task::isRunaway()
543 {
544         /* If a container task has runaway sub tasts, it is very likely that they
545          * are the culprits. So we don't report such a container task as runaway.
546          */
547         for (Task* t = subFirst(); t; t = subNext())
548                 if (t->isRunaway())
549                         return FALSE;
550         
551         return runAway;
552 }
553
554 bool
555 Task::bookResources(time_t date, time_t slotDuration)
556 {
557         bool allocFound = FALSE;
558
559         /* If the time slot overlaps with a specified shift interval, the
560          * time slot must also be within the specified working hours of that
561          * shift interval. */
562         if (!shifts.isOnShift(Interval(date, date + slotDuration - 1)))
563         {
564                 if (DEBUGRS(15))
565                         qDebug("Task %s is not active at %s", id.latin1(),
566                                    time2tjp(date).latin1());
567                 return FALSE;
568         }               
569                 
570         for (Allocation* a = allocations.first();
571                  a != 0 && (effort == 0.0 || doneEffort < effort);
572                  a = allocations.next())
573         {
574                 /* If a shift has been defined for a resource for this task, there
575                  * must be a shift interval defined for this day and the time must
576                  * be within the working hours of that shift. */
577                 if (!a->isOnShift(Interval(date, date + slotDuration - 1)))
578                 {
579                         if (DEBUGRS(15))
580                                 qDebug("Allocation not on shift at %s",
581                                            time2tjp(date).latin1());
582                         continue;
583                 }
584                 /* If the allocation has be marked persistent and a resource
585                  * has already been picked, try to book this resource again. If the
586                  * resource is not available there will be no booking for this
587                  * time slot. */
588                 if (a->isPersistent() && a->getLockedResource())
589                         bookResource(a->getLockedResource(), date, slotDuration,
590                                                  a->getLoad());
591                 else
592                 {
593                         QPtrList<Resource> cl = createCandidateList(date, a);
594                         for (Resource* r = cl.first(); r != 0; r = cl.next())
595                                 if (bookResource(r, date, slotDuration, a->getLoad()))
596                                 {
597                                         allocFound = TRUE;
598                                         a->setLockedResource(r);
599                                         break;
600                                 }
601                 }
602         }
603         return allocFound;
604 }
605
606 bool
607 Task::bookResource(Resource* r, time_t date, time_t slotDuration,
608                                    int loadFactor)
609 {
610         bool booked = FALSE;
611         double intervalLoad = project->convertToDailyLoad(slotDuration);
612
613         for (Resource* rit = r->subResourcesFirst(); rit != 0;
614                  rit = r->subResourcesNext())
615         {
616                 if ((*rit).isAvailable(date, slotDuration, loadFactor, this))
617                 {
618                         (*rit).book(new Booking(
619                                 Interval(date, date + slotDuration - 1), this,
620                                 account ? account->getKotrusId() : QString(""),
621                                 projectId));
622                         addBookedResource(rit);
623
624                         /* Move the start date to make sure that there is
625                          * some work going on at the start date. */
626                         if (!workStarted)
627                         {
628                                 if (scheduling == ASAP)
629                                         start = date;
630                                 else if (scheduling == ALAP)
631                                         end = date + slotDuration - 1;
632                                 else
633                                         qFatal("Unknown scheduling mode");
634                                 workStarted = TRUE;
635                         }
636
637                         tentativeStart = date;
638                         tentativeEnd = date + slotDuration - 1;
639                         doneEffort += intervalLoad * (*rit).getEfficiency();
640
641                         if (DEBUGTS(6))
642                                 qDebug(" Booked resource %s (Effort: %f)",
643                                            (*rit).getId().latin1(), doneEffort);
644                         booked = TRUE;
645                 }
646         }
647         return booked;
648 }
649
650 QPtrList<Resource>
651 Task::createCandidateList(time_t date, Allocation* a)
652 {
653         /* This function generates a list of resources that could be allocated to
654          * the task. The order of the list is determined by the specified
655          * selection function of the alternatives list. From this list, the
656          * first available resource is picked later on. */
657         QPtrList<Resource> candidates = a->getCandidates();
658         QPtrList<Resource> cl;
659         
660         /* We try to minimize resource changes for consecutives time slots. So
661          * the resource used for the previous time slot is put to the 1st position
662          * of the list. */
663         if (a->getLockedResource())
664         {
665                 cl.append(a->getLockedResource());
666                 candidates.remove(a->getLockedResource());
667                 /* When an allocation is booked the resource is saved as locked
668              * resource. */
669                 a->setLockedResource(0);
670         }
671         switch (a->getSelectionMode())
672         {
673                 case Allocation::order:
674                         while (candidates.first())
675                         {
676                                 cl.append(candidates.first());
677                                 candidates.remove(candidates.first());
678                         }
679                         break;
680                 case Allocation::minLoaded:
681                 {
682                         while (!candidates.isEmpty())
683                         {
684                                 double minLoad = 0;
685                                 Resource* minLoaded = 0;
686                                 for (Resource* r = candidates.first(); r != 0;
687                                          r = candidates.next())
688                                 {
689                                         double load =
690                                                 r->getCurrentLoad(Interval(project->getStart(),
691                                                                                                    date), 0) /
692                                                 r->getMaxEffort();
693                                         if (minLoaded == 0 || load < minLoad)
694                                         {
695                                                 minLoad = load;
696                                                 minLoaded = r;
697                                         }
698                                 }
699                                 cl.append(minLoaded);
700                                 candidates.remove(minLoaded);
701                         }
702                         break;
703                 }
704                 case Allocation::maxLoaded:
705                 {
706                         while (!candidates.isEmpty())
707                         {
708                                 double maxLoad = 0;
709                                 Resource* maxLoaded = 0;
710                                 for (Resource* r = candidates.first(); r != 0;
711                                          r = candidates.next())
712                                 {
713                                         double load =
714                                                 r->getCurrentLoad(Interval(project->getStart(),
715                                                                                                    date), 0) /
716                                                 r->getMaxEffort();
717                                         if (maxLoaded == 0 || load > maxLoad)
718                                         {
719                                                 maxLoad = load;
720                                                 maxLoaded = r;
721                                         }
722                                 }
723                                 cl.append(maxLoaded);
724                                 candidates.remove(maxLoaded);
725                         }
726                         break;
727                 }
728                 case Allocation::random:
729                 {
730                         while (candidates.first())
731                         {
732                                 cl.append(candidates.at(rand() % candidates.count()));
733                                 candidates.remove(candidates.first());
734                         }
735                         break;
736                 }
737                 default:
738                         qFatal("Illegal selection mode %d", a->getSelectionMode());
739         }
740
741         return cl;
742 }
743
744 bool
745 Task::isCompleted(int sc, time_t date) const
746 {
747         if (scenarios[sc].complete != -1)
748         {
749                 // some completion degree was specified.
750                 return ((scenarios[sc].complete / 100.0) *
751                                 (scenarios[sc].end - scenarios[sc].start) 
752                                 + scenarios[sc].start) > date;
753         }
754         
755
756         return (project->getNow() > date);
757 }
758
759 time_t
760 Task::earliestStart()
761 {
762         time_t date = 0;
763         for (Task* t = depends.first(); t != 0; t = depends.next())
764         {
765                 // All tasks this task depends on must have an end date set.
766                 if (t->end == 0)
767                         return 0;
768                 if (t->end > date)
769                         date = t->end;
770         }
771         if (date == 0)
772                 return 0;
773
774         return date + 1;
775 }
776
777 time_t
778 Task::latestEnd()
779 {
780         time_t date = 0;
781         for (Task* t = precedes.first(); t != 0; t = precedes.next())
782         {
783                 // All tasks this task preceeds must have a start date set.
784                 if (t->start == 0)
785                         return 0;
786                 if (date == 0 || t->start < date)
787                         date = t->start;
788         }
789         if (date == 0)
790                 return 0;
791
792         return date - 1;
793 }
794
795 double
796 Task::getCalcDuration(int sc) const
797 {
798         time_t delta = scenarios[sc].end - scenarios[sc].start;
799         if (delta < ONEDAY)
800                 return (project->convertToDailyLoad(delta));
801         else
802                 return (double) delta / ONEDAY;
803 }
804
805 double
806 Task::getLoad(int sc, const Interval& period, Resource* resource)
807 {
808         double load = 0.0;
809
810         if (subFirst())
811         {
812                 for (Task* t = subFirst(); t != 0; t = subNext())
813                         load += t->getLoad(sc, period, resource);
814         }
815
816         if (resource)
817                 load += resource->getLoad(sc, period, this);
818         else
819                 for (Resource* r = scenarios[sc].bookedResources.first(); r != 0;
820                          r = scenarios[sc].bookedResources.next())
821                         load += r->getLoad(sc, period, this);
822
823         return load;
824 }
825
826 double
827 Task::getCredits(int sc, const Interval& period, Resource* resource,
828                                  bool recursive)
829 {
830         double credits = 0.0;
831
832         if (recursive && subFirst())
833         {
834                 for (Task* t = subFirst(); t != 0; t = subNext())
835                         credits += t->getCredits(sc, period, resource, recursive);
836         }
837
838         if (resource)
839                 credits += resource->getCredits(sc, period, this);
840         else
841                 for (Resource* r = scenarios[sc].bookedResources.first(); r != 0;
842                          r = scenarios[sc].bookedResources.next())
843                         credits += r->getCredits(sc, period, this);
844
845         if (period.contains(scenarios[sc].start))
846                 credits += scenarios[sc].startCredit;
847         if (period.contains(scenarios[sc].end))
848                 credits += scenarios[sc].endCredit;
849
850         return credits;
851 }
852
853 bool
854 Task::xRef(QDict<Task>& hash)
855 {
856         bool error = FALSE;
857
858         if (DEBUGPF(2))
859                 qDebug("Creating cross references for task %s ...", id.latin1());
860         
861         for (QStringList::Iterator it = dependsIds.begin();
862                  it != dependsIds.end(); ++it)
863         {
864                 QString absId = resolveId(*it);
865                 Task* t;
866                 if ((t = hash.find(absId)) == 0)
867                 {
868                         fatalError(QString("Unknown dependency '") + absId + "'");
869                         error = TRUE;
870                 }
871                 else if (depends.find(t) != -1)
872                 {
873                         fatalError(QString("No need to specify dependency %1 multiple "
874                                                            "times.").arg(absId));
875                         // Make it a warning only for the time beeing.
876                         // error = TRUE; 
877                 }
878                 else
879                 {
880                         depends.append(t);
881                         previous.append(t);
882                         t->followers.append(this);
883                         if (DEBUGPF(11))
884                                 qDebug("Registering follower %s with task %s",
885                                            id.latin1(), t->getId().latin1());
886                 }
887         }
888
889         for (QStringList::Iterator it = precedesIds.begin();
890                  it != precedesIds.end(); ++it)
891         {
892                 QString absId = resolveId(*it);
893                 Task* t;
894                 if ((t = hash.find(absId)) == 0)
895                 {
896                         fatalError(QString("Unknown dependency '") + absId + "'");
897                         error = TRUE;
898                 }
899                 else if (precedes.find(t) != -1)
900                 {
901                         fatalError(QString("No need to specify dependency '") + absId +
902                                                            "' twice.");
903                         error = TRUE;
904                 }
905                 else
906                 {
907                         precedes.append(t);
908                         followers.append(t);
909                         t->previous.append(this);
910                         if (DEBUGPF(11))
911                                 qDebug("Registering predecessor %s with task %s",
912                                            id.latin1(), t->getId().latin1());
913                 }
914         }
915
916         return !error;
917 }
918
919 void
920 Task::implicitXRef()
921 {
922         /* Propagate implicit dependencies. If a task has no specified start or
923          * end date and no start or end dependencies, we check if a parent task
924          * has an explicit start or end date which can be used. */
925         if (!sub.isEmpty() || milestone)
926                 return;
927
928         bool planDurationSpec = scenarios[0].duration > 1 ||
929                 scenarios[0].length > 0 || scenarios[0].effort > 0;
930         bool actualDurationSpec = scenarios[1].duration > 0 ||
931                 scenarios[1].length > 0 || scenarios[1].effort > 0 || planDurationSpec;
932
933         if ((scenarios[0].start == 0 || scenarios[1].start == 0) &&
934                 depends.isEmpty())
935                 for (Task* tp = getParent(); tp; tp = tp->getParent())
936                 {
937                         if (tp->scenarios[0].start != 0 && scenarios[0].start == 0 &&
938                                 (scheduling == ASAP || !planDurationSpec))
939                         {
940                                 if (DEBUGPF(11))
941                                         qDebug("Setting plan start of %s to %s", id.latin1(),
942                                                    time2ISO(tp->scenarios[0].start).latin1());
943                                 scenarios[0].start = tp->scenarios[0].start;
944                         }
945                         if (tp->scenarios[1].start != 0 && scenarios[1].start == 0 &&
946                                 (scheduling == ASAP || !actualDurationSpec))
947                         {
948                                 if (DEBUGPF(11))
949                                         qDebug("Setting actual start of %s to %s", id.latin1(),
950                                                    time2ISO(tp->scenarios[1].start).latin1());
951                                 scenarios[1].start = tp->scenarios[1].start;
952                         }
953                 }
954         /* And the same for end values */
955         if ((scenarios[0].end == 0 || scenarios[1].end == 0) && precedes.isEmpty())
956                 for (Task* tp = getParent(); tp; tp = tp->getParent())
957                 {
958                         if (tp->scenarios[0].end != 0 && scenarios[0].end == 0 &&
959                                 (scheduling == ALAP || !planDurationSpec))
960                         {
961                                 if (DEBUGPF(11))
962                                         qDebug("Setting plan end of %s to %s", id.latin1(),
963                                                    time2ISO(tp->scenarios[0].end).latin1());
964                                 scenarios[0].end = tp->scenarios[0].end;
965                         }
966                         if (tp->scenarios[1].end != 0 && scenarios[1].end == 0 &&
967                                 (scheduling == ALAP || !actualDurationSpec))
968                         {
969                                 if (DEBUGPF(11))
970                                         qDebug("Setting actual end of %s to %s", id.latin1(),
971                                                    time2ISO(tp->scenarios[1].end).latin1());
972                                 scenarios[1].end = tp->scenarios[1].end;
973                         }
974                 }
975 }
976
977 bool
978 Task::hasYoungerBrother()
979 {
980         bool previousHasSameParent = FALSE;
981         int previousIndex = previous.at();
982         for (Task* p = previous.first(); 
983                  p && !previousHasSameParent;
984                  p = previous.next())
985         {
986                 if (parent == p->parent)
987                         previousHasSameParent = TRUE;
988         }
989         previous.at(previousIndex);
990         return previousHasSameParent;
991 }
992
993 bool
994 Task::hasOlderBrother()
995 {
996         bool followerHasSameParent = FALSE;
997         int followersIndex = followers.at();
998         for (Task* f = followers.first(); 
999                  f && !followerHasSameParent;
1000                  f = followers.next())
1001         {
1002                 if (parent == f->parent)
1003                         followerHasSameParent = TRUE;
1004         }
1005
1006         followers.at(followersIndex);
1007         return followerHasSameParent;
1008 }
1009 bool
1010 Task::loopDetector()
1011 {
1012         /* Only check top-level tasks. All other tasks will be checked then as
1013          * well. */
1014         if (parent)
1015                 return FALSE;
1016         if (DEBUGPF(2))
1017                 qWarning("Running loop detector for task %s", id.latin1());
1018         // Check ASAP tasks
1019         if (loopDetection(LDIList(), FALSE, LoopDetectorInfo::fromParent))
1020                 return TRUE;
1021         // Check ALAP tasks
1022         if (loopDetection(LDIList(), TRUE, LoopDetectorInfo::fromParent))
1023                 return TRUE;
1024         return FALSE;
1025 }
1026
1027 bool
1028 Task::loopDetection(LDIList list, bool atEnd, LoopDetectorInfo::FromWhere
1029                                         caller)
1030 {
1031         if (DEBUGPF(10))
1032                 qWarning("%sloopDetection at %s (%s)",
1033                                  QString().fill(' ', list.count()).latin1(), id.latin1(),
1034                                  atEnd ? "End" : "Start");
1035         
1036         LoopDetectorInfo thisTask(this, atEnd);
1037         
1038         /* If we find the current task (with same position) in the list, we have
1039          * detected a loop. */
1040         LDIList::iterator it;
1041         if ((it = list.find(thisTask)) != list.end())
1042         {
1043                 QString loopChain;
1044                 for ( ; it != list.end(); ++it)
1045                 {
1046                         loopChain += QString("%1 (%2) -> ")
1047                                 .arg((*it).getTask()->getId())
1048                                 .arg((*it).getAtEnd() ? "End" : "Start");
1049                         /*
1050                         (*it).getTask()->fatalError("%s (%s) is part of loop",
1051                                                                                 (*it).getTask()->getId().latin1(),
1052                                                                                 (*it).getAtEnd() ?
1053                                                                                 "End" : "Start");
1054                                                                                 */
1055                 }
1056                 loopChain += QString("%1 (%2)").arg(id)
1057                         .arg(atEnd ? "End" : "Start");
1058                 fatalError("Dependency loop detected: %s", loopChain.latin1());
1059                 return TRUE;
1060         }
1061         list.append(thisTask);
1062
1063         /* Now we have to traverse the graph in the direction of the specified
1064          * dependencies. 'precedes' and 'depends' specify dependencies in the
1065          * opposite direction of the flow of the tasks. So we have to make sure
1066          * that we do not follow the arcs in the direction that precedes and
1067          * depends points us. Parent/Child relationships also specify a
1068          * dependency. The scheduling mode of the child determines the direction
1069          * of the flow. With help of the 'caller' parameter we make sure that we
1070          * only visit childs if we were referred to the task by a non-parent-child
1071          * relationship. */
1072         if (!atEnd)
1073         {
1074                 CoreAttributesList subCopy = sub;
1075                 if (caller == LoopDetectorInfo::fromPrev ||
1076                         caller == LoopDetectorInfo::fromParent)
1077                         /* If we were not called from a sub task we check all sub tasks.*/
1078                         for (Task* t = (Task*) subCopy.first(); t;
1079                                  t = (Task*) subCopy.next())
1080                         {
1081                                 if (DEBUGPF(15))
1082                                         qWarning("%sChecking sub task %s of %s",
1083                                                          QString().fill(' ', list.count()).latin1(),
1084                                                          t->getId().latin1(),
1085                                                          id.latin1());
1086                                 if (t->loopDetection(list, FALSE,
1087                                                                          LoopDetectorInfo::fromParent))
1088                                         return TRUE;
1089                         }
1090                 
1091                 if (scheduling == ASAP && sub.isEmpty())
1092                 {
1093                         /* Leaf task are followed in their scheduling direction. So we
1094                          * move from the task start to the task end. */
1095                         if (DEBUGPF(15))
1096                                 qWarning("%sChecking end of task %s",
1097                                                  QString().fill(' ', list.count()).latin1(),
1098                                                  id.latin1());
1099                         if (loopDetection(list, TRUE, LoopDetectorInfo::fromOtherEnd))
1100                                 return TRUE;
1101                 }
1102                 if (caller == LoopDetectorInfo::fromSub ||
1103                         caller == LoopDetectorInfo::fromOtherEnd || 
1104                         (caller == LoopDetectorInfo::fromPrev && scheduling == ALAP))
1105                 {
1106                         if (parent)
1107                         {
1108                                 if (DEBUGPF(15))
1109                                         qWarning("%sChecking parent task of %s",
1110                                                          QString().fill(' ', list.count()).latin1(),    
1111                                                          id.latin1());          
1112                                 if (getParent()->loopDetection(list, FALSE,
1113                                                                                            LoopDetectorInfo::fromSub))
1114                                         return TRUE;
1115                         }
1116                         
1117                         /* Now check all previous tasks that had explicit precedes on this
1118                          * task. */
1119                         CoreAttributesList previousCopy = previous;
1120                         for (Task* t = (Task*) previousCopy.first(); t;
1121                                  t = (Task*) previousCopy.next())
1122                                 if (t->precedes.find(this) != -1)
1123                                 {
1124                                         if (DEBUGPF(15))
1125                                                 qWarning("%sChecking previous %s of task %s",
1126                                                                  QString().fill(' ', list.count()).latin1(),
1127                                                                  t->getId().latin1(), id.latin1());
1128                                         if(t->loopDetection(list, TRUE, LoopDetectorInfo::fromSucc))
1129                                                 return TRUE;
1130                                 }
1131                 }
1132         }
1133         else
1134         {
1135                 CoreAttributesList subCopy = sub;
1136                 if (caller == LoopDetectorInfo::fromSucc ||
1137                         caller == LoopDetectorInfo::fromParent)
1138                         /* If we were not called from a sub task we check all sub tasks.*/
1139                         for (Task* t = (Task*) subCopy.first(); t;
1140                                  t = (Task*) subCopy.next())
1141                         {
1142                                 if (DEBUGPF(15))
1143                                         qWarning("%sChecking sub task %s of %s",
1144                                                          QString().fill(' ', list.count()).latin1(),    
1145                                                          t->getId().latin1(), id.latin1());
1146                                 if (t->loopDetection(list, TRUE,
1147                                                                          LoopDetectorInfo::fromParent))
1148                                         return TRUE;
1149                         }
1150                 
1151                 if (scheduling == ALAP && sub.isEmpty())
1152                 {
1153                         /* Leaf task are followed in their scheduling direction. So we
1154                          * move from the task end to the task start. */
1155                         if (DEBUGPF(15))
1156                                 qWarning("%sChecking start of task %s",
1157                                                  QString().fill(' ', list.count()).latin1(),    
1158                                                  id.latin1());
1159                         if (loopDetection(list, FALSE, LoopDetectorInfo::fromOtherEnd))
1160                                 return TRUE;
1161                 }
1162                 if (caller == LoopDetectorInfo::fromOtherEnd ||
1163                         caller == LoopDetectorInfo::fromSub ||
1164                         (caller == LoopDetectorInfo::fromSucc && scheduling == ASAP))
1165                 {
1166                         if (parent)
1167                         {
1168                                 if (DEBUGPF(15))
1169                                         qWarning("%sChecking parent task of %s",
1170                                                          QString().fill(' ', list.count()).latin1(),    
1171                                                          id.latin1());          
1172                                 if (getParent()->loopDetection(list, TRUE,
1173                                                                                            LoopDetectorInfo::fromSub))
1174                                         return TRUE;
1175                         }
1176
1177                         /* Now check all following tasks that have explicit depends on this
1178                          * task. */
1179                         CoreAttributesList followersCopy = followers;
1180                         for (Task* t = (Task*) followersCopy.first(); t;
1181                                  t = (Task*) followersCopy.next())
1182                                 if (t->depends.find(this) != -1)
1183                                 {
1184                                         if (DEBUGPF(15))
1185                                                 qWarning("%sChecking follower %s of task %s",
1186                                                                  QString().fill(' ', list.count()).latin1(),    
1187                                                                  t->getId().latin1(), id.latin1());
1188                                         if (t->loopDetection(list, FALSE,
1189                                                                                  LoopDetectorInfo::fromPrev))
1190                                                 return TRUE;
1191                                 }
1192                 }
1193         }
1194
1195         if (DEBUGPF(10))
1196                 qWarning("%sNo loops found in %s (%s)",
1197                                  QString().fill(' ', list.count()).latin1(),    
1198                                  id.latin1(), atEnd ? "End" : "Start");
1199         return FALSE;
1200 }
1201
1202 QString
1203 Task::resolveId(QString relId)
1204 {
1205         /* Converts a relative ID to an absolute ID. Relative IDs start
1206          * with a number of bangs. A set of bangs means 'Name of the n-th
1207          * parent task' with n being the number of bangs. */
1208         if (relId[0] != '!')
1209                 return relId;
1210
1211         Task* t = this;
1212         unsigned int i;
1213         for (i = 0; i < relId.length() && relId.mid(i, 1) == "!"; ++i)
1214         {
1215                 if (t == 0)
1216                 {
1217                         fatalError(QString("Illegal relative ID '") + relId + "'");
1218                         return relId;
1219                 }
1220                 t = t->getParent();
1221         }
1222         if (t)
1223                 return t->id + "." + relId.right(relId.length() - i);
1224         else
1225                 return relId.right(relId.length() - i);
1226 }
1227
1228 bool
1229 Task::hasStartDependency(int sc)
1230 {
1231         /* Checks whether the task has a start specification for the 
1232          * scenario. This can be a fixed start time or a dependency on another
1233          * task's end or an implicit dependency on the fixed start time of a
1234          * parent task. */
1235         if (scenarios[sc].start != 0 || !depends.isEmpty())
1236                 return TRUE;
1237         for (Task* p = getParent(); p; p = p->getParent())
1238                 if (p->scenarios[sc].start != 0)
1239                         return TRUE;
1240         return FALSE;
1241 }
1242
1243 bool
1244 Task::hasEndDependency(int sc)
1245 {
1246         /* Checks whether the task has an end specification for the 
1247          * scenario. This can be a fixed end time or a dependency on another
1248          * task's start or an implicit dependency on the fixed end time of a
1249          * parent task. */
1250         if (scenarios[sc].end != 0 || !precedes.isEmpty())
1251                 return TRUE;
1252         for (Task* p = getParent(); p; p = p->getParent())
1253                 if (p->scenarios[sc].end != 0)
1254                         return TRUE;
1255         return FALSE;
1256 }
1257
1258 bool
1259 Task::preScheduleOk()
1260 {
1261         for (int sc = 0; sc < project->getMaxScenarios(); sc++)
1262         {
1263                 if (scenarios[sc].effort > 0 && allocations.count() == 0)
1264                 {
1265                         fatalError(QString
1266                                            ("No allocations specified for effort based task %1 "
1267                                                 "in %2 scenario")
1268                                            .arg(1).arg(project->getScenarioName(sc)));
1269                         return FALSE;
1270                 }
1271
1272                 if (scenarios[sc].startBuffer + scenarios[sc].endBuffer >= 100.0)
1273                 {
1274                         fatalError(QString
1275                                 ("Start and end buffers may not overlap in %2 scenario. "
1276                                  "So their sum must be smaller then 100%.")
1277                                 .arg(project->getScenarioName(sc)));
1278                         return FALSE;
1279                 }
1280
1281                 // Check plan values.
1282                 int durationSpec = 0;
1283                 if (scenarios[sc].effort > 0.0)
1284                         durationSpec++;
1285                 if (scenarios[sc].length > 0.0)
1286                         durationSpec++;
1287                 if (scenarios[sc].duration > 0.0)
1288                         durationSpec++;
1289                 if (durationSpec > 1)
1290                 {
1291                         fatalError(QString("Task %1 may only have one duration "
1292                                                            "criteria in %2 scenario.").arg(id)
1293                                            .arg(project->getScenarioName(sc)));
1294                         return FALSE;
1295                 }
1296
1297                 /*
1298                 |: fixed start or end date
1299                 -: no fixed start or end date
1300                 M: Milestone
1301                 D: start or end dependency
1302                 x->: ASAP task with duration criteria
1303                 <-x: ALAP task with duration criteria
1304                 -->: ASAP task without duration criteria
1305                 <--: ALAP task without duration criteria
1306                  */
1307                 if (!sub.isEmpty())
1308                 {
1309                         if (durationSpec != 0)
1310                         {
1311                                 fatalError(QString
1312                                                    ("Container task %1 may not have a plan duration "
1313                                                         "criteria in %2 scenario").arg(id)
1314                                                    .arg(project->getScenarioName(sc)));
1315                                 return FALSE;
1316                         }
1317                 }
1318                 else if (milestone)
1319                 {
1320                         if (durationSpec != 0)
1321                         {
1322                                 fatalError(QString
1323                                                    ("Milestone %1 may not have a plan duration "
1324                                                         "criteria in %2 scenario").arg(id)
1325                                                    .arg(project->getScenarioName(sc)));
1326                                 return FALSE;
1327                         }
1328                         /*
1329                         |  M -   ok     |D M -   ok     - M -   err1   -D M -   ok
1330                         |  M |   err2   |D M |   err2   - M |   ok     -D M |   ok
1331                         |  M -D  ok     |D M -D  ok     - M -D  ok     -D M -D  ok
1332                         |  M |D  err2   |D M |D  err2   - M |D  ok     -D M |D  ok
1333                          */
1334                         /* err1: no start and end
1335                         - M -
1336                          */
1337                         if (!hasStartDependency(sc) && !hasEndDependency(sc))
1338                         {
1339                                 fatalError(QString("Milestone %1 must have a start or end "
1340                                                                    "specification in %2 scenario.")
1341                                                    .arg(id).arg(project->getScenarioName(sc)));
1342                                 return FALSE;
1343                         }
1344                         /* err2: different start and end
1345                         |  M |
1346                         |  M |D
1347                         |D M |
1348                         |D M |D
1349                          */
1350                         if (scenarios[sc].start != 0 && scenarios[sc].end != 0 && 
1351                                 scenarios[sc].start != scenarios[sc].end + 1)
1352                         {
1353                                 fatalError(QString
1354                                                    ("Milestone %1 may not have both a start "
1355                                                         "and an end specification that do not "
1356                                                         "match in %2 scenario.").arg(id)
1357                                                    .arg(project->getScenarioName(sc)));
1358                                 return FALSE;
1359                         }
1360                 }
1361                 else
1362                 {
1363                         /*
1364                         Error table for non-container, non-milestone tasks:
1365
1366                         | x-> -   ok     |D x-> -   ok     - x-> -   err3   -D x-> -   ok
1367                         | x-> |   err1   |D x-> |   err1   - x-> |   err3   -D x-> |   err1
1368                         | x-> -D  ok     |D x-> -D  ok     - x-> -D  err3   -D x-> -D  ok
1369                         | x-> |D  err1   |D x-> |D  err1   - x-> |D  err3   -D x-> |D  err1
1370                         | --> -   err2   |D --> -   err2   - --> -   err3   -D --> -   err2
1371                         | --> |   ok     |D --> |   ok     - --> |   err3   -D --> |   ok
1372                         | --> -D  ok     |D --> -D  ok     - --> -D  err3   -D --> -D  ok
1373                         | --> |D  ok     |D --> |D  ok     - --> |D  err3   -D --> |D  ok
1374                         | <-x -   err4   |D <-x -   err4   - <-x -   err4   -D <-x -   err4
1375                         | <-x |   err1   |D <-x |   err1   - <-x |   ok     -D <-x |   ok
1376                         | <-x -D  err1   |D <-x -D  err1   - <-x -D  ok     -D <-x -D  ok
1377                         | <-x |D  err1   |D <-x |D  err1   - <-x |D  ok     -D <-x |D  ok
1378                         | <-- -   err4   |D <-- -   err4   - <-- -   err4   -D <-- -   err4
1379                         | <-- |   ok     |D <-- |   ok     - <-- |   err2   -D <-- |   ok
1380                         | <-- -D  ok     |D <-- -D  ok     - <-- -D  err2   -D <-- -D  ok
1381                         | <-- |D  ok     |D <-- |D  ok     - <-- |D  err2   -D <-- |D  ok
1382                          */
1383                         /*
1384                         err1: Overspecified (12 cases)
1385                         |  x-> |
1386                         |  <-x |
1387                         |  x-> |D
1388                         |  <-x |D
1389                         |D x-> |
1390                         |D <-x |
1391                         |D <-x |D
1392                         |D x-> |D
1393                         -D x-> |
1394                         -D x-> |D
1395                         |D <-x -D
1396                         |  <-x -D
1397                          */
1398                         if (((scenarios[sc].start != 0 && scenarios[sc].end != 0) ||
1399                                  (hasStartDependency(sc) && scenarios[sc].start == 0 &&
1400                                   scenarios[sc].end != 0 && scheduling == ASAP) ||
1401                                  (scenarios[sc].start != 0 && scheduling == ALAP &&
1402                                   hasEndDependency(sc) && scenarios[sc].end == 0)) &&
1403                                 durationSpec != 0)
1404                         {
1405                                 fatalError(QString("Task %1 has a start, an end and a "
1406                                                                    "duration specification for %2 scenario.")
1407                                                    .arg(id).arg(project->getScenarioName(sc)));
1408                                 return FALSE;
1409                         }       
1410                         /*
1411                         err2: Underspecified (6 cases)
1412                         |  --> -
1413                         |D --> -
1414                         -D --> -
1415                         -  <-- |
1416                         -  <-- |D
1417                         -  <-- -D
1418                          */
1419                         if ((hasStartDependency(sc) ^ hasEndDependency(sc)) &&
1420                                 durationSpec == 0)
1421                         {
1422                                 fatalError(QString
1423                                                    ("Task %1 has only a start or end specification "
1424                                                         "but no plan duration for the %2 scenario.")
1425                                                    .arg(id).arg(project->getScenarioName(sc)));
1426                                 return FALSE;
1427                         }
1428                         /*
1429                         err3: ASAP + Duration must have fixed start (8 cases)
1430                         -  x-> -
1431                         -  x-> |
1432                         -  x-> -D
1433                         -  x-> |D
1434                         -  --> -
1435                         -  --> |
1436                         -  --> -D
1437                         -  --> |D
1438                          */
1439                         if (!hasStartDependency(sc) && scheduling == ASAP)
1440                         {
1441                                 fatalError(QString
1442                                                    ("Task %1 needs a start specification to be "
1443                                                         "scheduled in ASAP mode in the %2 scenario.")
1444                                                    .arg(id).arg(project->getScenarioName(sc)));
1445                                 return FALSE;
1446                         }
1447                         /*
1448                         err4: ALAP + Duration must have fixed end (8 cases)
1449                         -  <-x -
1450                         |  <-x -
1451                         |D <-x -
1452                         -D <-x -
1453                         -  <-- -
1454                         |  <-- -
1455                         -D <-- -
1456                         |D <-- -
1457                          */
1458                         if (!hasEndDependency(sc) && scheduling == ALAP)
1459                         {
1460                                 fatalError(QString
1461                                                    ("Task %1 needs an end specification to be "
1462                                                         "scheduled in ALAP mode in the %2 scenario.")
1463                                                    .arg(id).arg(project->getScenarioName(sc)));
1464                                 return FALSE;
1465                         }
1466                 }
1467         }
1468         double intervalLoad =
1469                 project->convertToDailyLoad(project->getScheduleGranularity());
1470
1471         for (Allocation* a = allocations.first(); a != 0; a = allocations.next())
1472         {
1473                 if (a->getLoad() < intervalLoad * 100.0)
1474                 {
1475                         qWarning("Warning: Load is smaller than scheduling granularity "
1476                                          "(Task: %s, Resource: %s). Minimal load is %.2f.",
1477                                          id.latin1(), a->first()->getId().latin1(),
1478                                          intervalLoad + 0.005);
1479                         a->setLoad((int) (intervalLoad * 100.0));
1480                 }
1481         }
1482
1483         return TRUE;
1484 }
1485
1486 bool
1487 Task::scheduleOk(int& errors, QString scenario)
1488 {
1489         /* It is of little use to report errors of container tasks, if any of
1490          * their sub tasks has errors. */
1491         int currErrors = errors;
1492         for (Task* t = subFirst(); t; t = subNext())
1493                 t->scheduleOk(errors, scenario);
1494         if (errors > currErrors)
1495         {
1496                 if (DEBUGPS(2))
1497                         qWarning(QString("Scheduling errors in sub tasks of %1.")
1498                                          .arg(id));
1499                 return FALSE;
1500         }
1501         
1502         /* Runaway errors have already been reported. Since the data of this task
1503          * is very likely completely bogus, we just return FALSE. */
1504         if (runAway)
1505                 return FALSE;
1506         
1507         /* If any of the dependant tasks is a runAway, we can safely surpress all
1508          * other error messages. */
1509         for (Task* t = depends.first(); t; t = depends.next())
1510                 if (t->runAway)
1511                         return FALSE;
1512         for (Task* t = precedes.first(); t; t = precedes.next())
1513                 if (t->runAway)
1514                         return FALSE;
1515
1516         if (start == 0)
1517         {
1518                 // Only report this for leaf tasks.
1519                 if (DEBUGPS(1) || sub.isEmpty())
1520                         fatalError(QString("Task '%1' has no %2 start time.")
1521                                            .arg(id).arg(scenario.lower()));
1522                 errors++;
1523                 return FALSE;
1524         }
1525         if (start < minStart)
1526         {
1527                 fatalError(QString("%1 start time of task %2 is too early\n"
1528                                                    "Date is:  %3\n"
1529                                                    "Limit is: %4")
1530                                    .arg(scenario).arg(id).arg(time2tjp(start))
1531                                    .arg(time2tjp(minStart)));
1532                 errors++;
1533                 return FALSE;
1534         }
1535         if (maxStart < start)
1536         {
1537                 fatalError(QString("%1 start time of task %2 is too late\n"
1538                                                    "Date is:  %3\n"
1539                                                    "Limit is: %4")
1540                                    .arg(scenario).arg(id)
1541                                    .arg(time2tjp(start)).arg(time2tjp(maxStart)));
1542                 errors++;
1543                 return FALSE;
1544         }
1545         if (end == 0)
1546         {
1547                 // Only report this for leaf tasks.
1548                 if (DEBUGPS(1) || sub.isEmpty())
1549                         fatalError(QString("Task '%1' has no %2 end time.")
1550                                            .arg(id).arg(scenario.lower()));
1551                 return FALSE;
1552         }
1553         if (end + (milestone ? 1 : 0) < minEnd)
1554         {
1555                 fatalError(QString("%1 end time of task %2 is too early\n"
1556                                                    "Date is:  %3\n"
1557                                                    "Limit is: %4")
1558                                    .arg(scenario).arg(id)
1559                                    .arg(time2tjp(end + (milestone ? 1 : 0)))
1560                                    .arg(time2tjp(minEnd)));
1561                 errors++;
1562                 return FALSE;
1563         }
1564         if (maxEnd < end + (milestone ? 1 : 0))
1565         {
1566                 fatalError(QString("%1 end time of task %2 is too late\n"
1567                                                    "Date is:  %2\n"
1568                                                    "Limit is: %3")
1569                                    .arg(scenario).arg(id)
1570                                    .arg(time2tjp(end + (milestone ? 1 : 0)))
1571                                    .arg(time2tjp(maxEnd)));
1572                 errors++;
1573                 return FALSE;
1574         }
1575         if (!sub.isEmpty())
1576         {
1577                 // All sub task must fit into their parent task.
1578                 for (Task* t = subFirst(); t != 0; t = subNext())
1579                 {
1580                         if (start > t->start)
1581                         {
1582                                 if (!t->runAway)
1583                                 {
1584                                         fatalError(QString("Task %1 has ealier %2 start than "
1585                                                                            "parent")
1586                                                            .arg(id).arg(scenario.lower()));
1587                                         errors++;
1588                                 }
1589                                 return FALSE;
1590                         }
1591                         if (end < t->end)
1592                         {
1593                                 if (!t->runAway)
1594                                 {
1595                                         fatalError(QString("Task %1 has later %2 end than parent")
1596                                                            .arg(id).arg(scenario.lower()));
1597                                         errors++;
1598                                 }
1599                                 return FALSE;
1600                         }
1601                 }
1602         }
1603
1604         // Check if all previous tasks end before start of this task.
1605         for (Task* t = previous.first(); t != 0; t = previous.next())
1606                 if (t->end > start && !t->runAway)
1607                 {
1608                         fatalError(QString("Impossible dependency:\n"
1609                                                            "Task %1 ends at %2 but needs to preceed\n"
1610                                                            "task %3 which has a %4 start time of %5")
1611                                            .arg(t->id).arg(time2tjp(t->end).latin1())
1612                                            .arg(id).arg(scenario.lower()).arg(time2tjp(start)));
1613                         errors++;
1614                         return FALSE;
1615                 }
1616         // Check if all following task start after this tasks end.
1617         for (Task* t = followers.first(); t != 0; t = followers.next())
1618                 if (end > t->start && !t->runAway)
1619                 {
1620                         fatalError(QString("Impossible dependency:\n"
1621                                                            "Task %1 starts at %2 but needs to follow\n"
1622                                                            "task %3 which has a %4 end time of %5")
1623                                            .arg(t->id).arg(time2tjp(t->start))
1624                                            .arg(id).arg(scenario.lower()).arg(time2tjp(end)));
1625                         errors++;
1626                         return FALSE;
1627                 }
1628
1629         if (!schedulingDone)
1630         {
1631                 fatalError(QString("Task %1 has not been marked completed.\n"
1632                                                    "It is scheduled to last from %2 to %3.\n"
1633                                                    "This might be a bug in the TaskJuggler scheduler.")
1634                                    .arg(id).arg(time2tjp(start)).arg(time2tjp(end)));
1635                 errors++;
1636                 return FALSE;
1637         }
1638         
1639         return TRUE;
1640 }
1641
1642 time_t
1643 Task::nextSlot(time_t slotDuration)
1644 {
1645         if (schedulingDone || !sub.isEmpty())
1646                 return 0;
1647
1648         if (scheduling == ASAP && start != 0)
1649         {
1650                 if (effort == 0.0 && length == 0.0 && duration == 0.0 && !milestone &&
1651                         end == 0)
1652                         return 0;
1653
1654                 if (lastSlot == 0)
1655                         return start;
1656                 return lastSlot + 1;
1657         }
1658         if (scheduling == ALAP && end != 0)
1659         {
1660                 if (effort == 0.0 && length == 0.0 && duration == 0.0 && !milestone &&
1661                         start == 0)
1662                         return 0;
1663                 if (lastSlot == 0)
1664                         return end - slotDuration + 1;
1665                 return lastSlot - slotDuration;
1666         }
1667
1668         return 0;
1669 }
1670
1671 bool
1672 Task::isActive(int sc, const Interval& period) const
1673 {
1674         return period.overlaps(Interval(scenarios[sc].start,
1675                                                                         milestone ? scenarios[sc].start :
1676                                                                         scenarios[sc].end));
1677 }
1678
1679 void
1680 Task::getSubTaskList(TaskList& tl)
1681 {
1682         for (Task* t = subFirst(); t != 0; t = subNext())
1683         {
1684                 tl.append(t);
1685                 t->getSubTaskList(tl);
1686         }
1687 }
1688
1689 bool
1690 Task::isSubTask(Task* tsk)
1691 {
1692         for (Task* t = subFirst(); t != 0; t = subNext())
1693                 if (t == tsk || t->isSubTask(tsk))
1694                         return TRUE;
1695
1696         return FALSE;
1697 }
1698
1699 void
1700 Task::overlayScenario(int sc)
1701 {
1702         /* Scenario 0 is always the baseline. If another scenario does not provide
1703          * a certain value, the value from the plan scenario is copied over. */
1704         if (scenarios[sc].start == 0.0)
1705                 scenarios[sc].start = scenarios[0].start;
1706         if (scenarios[sc].end == 0.0)
1707                 scenarios[sc].end = scenarios[0].end;
1708         if (scenarios[sc].duration == 0.0)
1709                 scenarios[sc].duration =  scenarios[0].duration;
1710         if (scenarios[sc].length == 0.0)
1711                 scenarios[sc].length = scenarios[0].length;
1712         if (scenarios[sc].effort == 0.0)
1713                 scenarios[sc].effort = scenarios[0].effort;
1714         if (scenarios[sc].startBuffer < 0.0)
1715                 scenarios[sc].startBuffer = scenarios[0].startBuffer;
1716         if (scenarios[sc].endBuffer < 0.0)
1717                 scenarios[sc].endBuffer = scenarios[0].endBuffer;
1718         if (scenarios[sc].startCredit < 0.0)
1719                 scenarios[sc].startCredit = scenarios[0].startCredit;
1720         if (scenarios[sc].endCredit < 0.0)
1721                 scenarios[sc].endCredit = scenarios[0].endCredit;
1722         if (scenarios[sc].complete == -1)
1723                 scenarios[sc].complete = scenarios[0].complete;
1724 }
1725
1726 bool
1727 Task::hasExtraValues(int sc) const
1728 {
1729         return scenarios[sc].start != 0 || scenarios[sc].end != 0 ||
1730                 scenarios[sc].length != 0 || scenarios[sc].duration != 0 ||
1731                 scenarios[sc].effort != 0 || scenarios[sc].complete != -1 ||
1732                 scenarios[sc].startBuffer >= 0.0 || scenarios[sc].endBuffer >= 0.0 ||
1733                 scenarios[sc].startCredit >= 0.0 || scenarios[sc].endCredit >= 0.0;
1734 }
1735
1736 void
1737 Task::prepareScenario(int sc)
1738 {
1739         start = scenarios[sc].start;
1740         end = scenarios[sc].end;
1741
1742         duration = scenarios[sc].duration;
1743         length = scenarios[sc].length;
1744         effort = scenarios[sc].effort;
1745         lastSlot = 0;
1746         schedulingDone = scenarios[sc].scheduled;
1747         runAway = FALSE;
1748         bookedResources.clear();
1749         bookedResources = scenarios[sc].bookedResources;
1750 }
1751
1752 void
1753 Task::finishScenario(int sc)
1754 {
1755         scenarios[sc].start = start;
1756         scenarios[sc].end = end;
1757         scenarios[sc].duration = doneDuration;
1758         scenarios[sc].length = doneLength;
1759         scenarios[sc].effort = doneEffort;
1760         scenarios[sc].bookedResources = bookedResources;
1761         scenarios[sc].scheduled = schedulingDone;
1762 }
1763
1764 void
1765 Task::computeBuffers()
1766 {
1767         int sg = project->getScheduleGranularity();
1768         for (int sc = 0; sc < project->getMaxScenarios(); sc++)
1769         {
1770                 scenarios[sc].startBufferEnd = scenarios[sc].start - 1;
1771                 scenarios[sc].endBufferStart = scenarios[sc].end + 1;
1772
1773         
1774                 if (duration > 0.0)
1775                 {
1776                         if (scenarios[sc].startBuffer > 0.0)
1777                                 scenarios[sc].startBufferEnd = scenarios[sc].start +
1778                                         (time_t) ((scenarios[sc].end - scenarios[sc].start) * 
1779                                                           scenarios[sc].startBuffer / 100.0);
1780                         if (scenarios[sc].endBuffer > 0.0)
1781                                 scenarios[sc].endBufferStart = scenarios[sc].end -
1782                                         (time_t) ((scenarios[sc].end - scenarios[sc].start) * 
1783                                                           scenarios[sc].endBuffer / 100.0);
1784                 }
1785                 else if (length > 0.0)
1786                 {
1787                         double l;
1788                         if (scenarios[sc].startBuffer > 0.0)
1789                         {
1790                                 for (l = 0.0; scenarios[sc].startBufferEnd < scenarios[sc].end;
1791                                          scenarios[sc].startBufferEnd += sg)
1792                                 {
1793                                         if (project->isWorkingDay(scenarios[sc].startBufferEnd))
1794                                         l += (double) sg / ONEDAY;
1795                                         if (l >= scenarios[sc].length *
1796                                                 scenarios[sc].startBuffer / 100.0)
1797                                                 break;
1798                                 }
1799                         }
1800                         if (scenarios[sc].endBuffer > 0.0)
1801                         {
1802                                 for (l = 0.0; scenarios[sc].endBufferStart > 
1803                                          scenarios[sc].start; scenarios[sc].endBufferStart -= sg)
1804                                 {
1805                                         if (project->isWorkingDay(scenarios[sc].endBufferStart))
1806                                                 l += (double) sg / ONEDAY;
1807                                         if (l >= scenarios[sc].length * 
1808                                                 scenarios[sc].endBuffer / 100.0)
1809                                                 break;
1810                                 }
1811                         }
1812                 }
1813                 else if (effort > 0.0)
1814                 {
1815                         double e;
1816                         if (scenarios[sc].startBuffer > 0.0)
1817                         {
1818                                 for (e = 0.0; scenarios[sc].startBufferEnd < scenarios[sc].end; 
1819                                          scenarios[sc].startBufferEnd += sg)
1820                                 {
1821                                         e += getLoad(sc, 
1822                                                                  Interval(scenarios[sc].startBufferEnd,
1823                                                                                   scenarios[sc].startBufferEnd + sg));
1824                                         if (e >= scenarios[sc].effort * 
1825                                                 scenarios[sc].startBuffer / 100.0)
1826                                                 break;
1827                                 }
1828                         }
1829                         if (scenarios[sc].endBuffer > 0.0)
1830                         {
1831                                 for (e = 0.0; scenarios[sc].endBufferStart > 
1832                                          scenarios[sc].start; scenarios[sc].endBufferStart -= sg)
1833                                 {
1834                                         e += getLoad(sc, 
1835                                                                  Interval(scenarios[sc].endBufferStart - sg,
1836                                                                                   scenarios[sc].endBufferStart));
1837                                         if (e >= scenarios[sc].effort * 
1838                                                 scenarios[sc].endBuffer / 100.0)
1839                                                 break;
1840                                 }
1841                         }
1842                 }
1843         }
1844 }
1845
1846 double Task::getCompleteAtTime(int sc, time_t timeSpot) const
1847 {
1848    if( scenarios[sc].complete != -1 ) return( scenarios[sc].complete );
1849
1850    time_t start = getStart(sc);
1851    time_t end = getEnd(sc);
1852
1853    if( timeSpot > end ) return 100.0;
1854    if( timeSpot < start ) return 0.0;
1855    
1856    time_t interval = end - start;
1857    time_t done = timeSpot - start;
1858
1859    return 100./interval*done;
1860 }
1861
1862
1863 QDomElement Task::xmlElement( QDomDocument& doc, bool /* absId */ )
1864 {
1865    QDomElement taskElem = doc.createElement( "Task" );
1866    QDomElement tempElem;
1867
1868    QString idStr = getId();
1869 /*   if( !absId )
1870       idStr = idStr.section( '.', -1 ); */
1871       
1872    taskElem.setAttribute( "Id", idStr );
1873
1874    QDomText t;
1875    taskElem.appendChild( ReportXML::createXMLElem( doc, "Index", QString::number(getIndex()) ));
1876    taskElem.appendChild( ReportXML::createXMLElem( doc, "Name", getName() ));
1877    taskElem.appendChild( ReportXML::createXMLElem( doc, "ProjectID", projectId ));
1878    taskElem.appendChild( ReportXML::createXMLElem( doc, "Priority", QString::number(getPriority())));
1879
1880    double cmplt = getCompleteAtTime( Task::Plan, getProject()->getNow());
1881    taskElem.appendChild( ReportXML::createXMLElem( doc, "complete", QString::number(cmplt, 'f', 1) ));
1882
1883    QString tType = "Milestone";
1884    if( !isMilestone() )
1885    {
1886       if( isContainer() )
1887          tType = "Container";
1888       else
1889          tType = "Task";
1890       
1891    }
1892    taskElem.appendChild( ReportXML::createXMLElem( doc, "Type", tType  ));
1893
1894    CoreAttributes *parent = getParent();
1895    if( parent )
1896       taskElem.appendChild( ReportXML::ReportXML::createXMLElem( doc, "ParentTask", parent->getId()));
1897          
1898    if( !note.isEmpty())
1899       taskElem.appendChild( ReportXML::createXMLElem( doc, "Note", getNote()));
1900    
1901    tempElem = ReportXML::createXMLElem( doc, "minStart", QString::number( minStart ));
1902    tempElem.setAttribute( "humanReadable", time2ISO( minStart ));
1903    taskElem.appendChild( tempElem );
1904    
1905    tempElem = ReportXML::createXMLElem( doc, "maxStart", QString::number( maxStart ));
1906    tempElem.setAttribute( "humanReadable", time2ISO( maxStart ));
1907    taskElem.appendChild( tempElem );
1908
1909    tempElem = ReportXML::createXMLElem( doc, "minEnd", QString::number( minEnd ));
1910    tempElem.setAttribute( "humanReadable", time2ISO( minEnd ));
1911    taskElem.appendChild( tempElem );
1912
1913    tempElem = ReportXML::createXMLElem( doc, "maxEnd", QString::number( maxEnd ));
1914    tempElem.setAttribute( "humanReadable", time2ISO( maxEnd ));
1915    taskElem.appendChild( tempElem );
1916    
1917    tempElem = ReportXML::createXMLElem( doc, "actualStart", QString::number( scenarios[1].start ));
1918    tempElem.setAttribute( "humanReadable", time2ISO( scenarios[1].start ));
1919    taskElem.appendChild( tempElem );
1920
1921    tempElem = ReportXML::createXMLElem( doc, "actualEnd", QString::number( scenarios[1].end ));
1922    tempElem.setAttribute( "humanReadable", time2ISO( scenarios[1].end ));
1923    taskElem.appendChild( tempElem );
1924    
1925    tempElem = ReportXML::createXMLElem( doc, "planStart", QString::number( scenarios[0].start ));
1926    tempElem.setAttribute( "humanReadable", time2ISO( scenarios[0].start ));
1927    taskElem.appendChild( tempElem );
1928
1929    tempElem = ReportXML::createXMLElem( doc, "planEnd", QString::number( scenarios[0].end ));
1930    tempElem.setAttribute( "humanReadable", time2ISO( scenarios[0].end ));
1931    taskElem.appendChild( tempElem );
1932
1933    /* Start- and Endbuffer */
1934    if( getStartBuffer(Task::Plan) > 0.01 )
1935    {
1936       /* startbuffer exists */
1937       tempElem = ReportXML::createXMLElem( doc, "startBufferSize",
1938                                                                                    QString::number(
1939                                                                                                                    getStartBuffer(Task::Plan)));
1940       taskElem.appendChild( tempElem );
1941
1942       tempElem = ReportXML::createXMLElem( doc, "ActualStartBufferEnd",
1943                                            QString::number( getStartBufferEnd(Task::Actual)));
1944       tempElem.setAttribute( "humanReadable",
1945                                                          time2ISO(getStartBufferEnd(Task::Actual)));
1946       taskElem.appendChild( tempElem );
1947
1948       tempElem = ReportXML::createXMLElem( doc, "PlanStartBufferEnd",
1949                                            QString::number( getStartBufferEnd(Task::Plan)));
1950       tempElem.setAttribute( "humanReadable",
1951                                                          time2ISO(getStartBufferEnd(Task::Plan)));
1952       taskElem.appendChild( tempElem );
1953       
1954    }
1955
1956    if( getEndBuffer(Task::Plan) > 0.01 )
1957    {
1958       /* startbuffer exists */
1959       tempElem = ReportXML::createXMLElem( doc, "EndBufferSize",
1960                                                                                    QString::number(
1961                                                                                                                    getEndBuffer(Task::Plan)));
1962       taskElem.appendChild( tempElem );
1963
1964       tempElem = ReportXML::createXMLElem( doc, "ActualEndBufferStart",
1965                                            QString::number( getEndBufferStart(Task::Actual)));
1966       tempElem.setAttribute( "humanReadable",
1967                                                          time2ISO(getEndBufferStart(Task::Actual)));
1968       taskElem.appendChild( tempElem );
1969
1970       tempElem = ReportXML::createXMLElem( doc, "PlanEndBufferStart",
1971                                            QString::number( getEndBufferStart(Task::Plan)));
1972       tempElem.setAttribute( "humanReadable",
1973                                                          time2ISO(getStartBufferEnd(Task::Plan)));
1974       taskElem.appendChild( tempElem );
1975    }
1976
1977    /* Responsible persons */
1978    if( getResponsible() )
1979       taskElem.appendChild( getResponsible()->xmlIDElement( doc ));      
1980    
1981    /* Now start the subtasks */
1982    int cnt = 0;
1983    QDomElement subTaskElem = doc.createElement( "SubTasks" );
1984    for (Task* t = subFirst(); t != 0; t = subNext())
1985    {
1986       if( t != this )
1987       {
1988          QDomElement sTask = t->xmlElement( doc, false );
1989          subTaskElem.appendChild( sTask );
1990          cnt++;
1991       }
1992    }
1993    if( cnt > 0 )
1994       taskElem.appendChild( subTaskElem);
1995
1996    /* list of tasks by id which are previous */
1997    if( previous.count() > 0 )
1998    {
1999       TaskList tl( previous );
2000       for (Task* t = tl.first(); t != 0; t = tl.next())
2001       { 
2002          if( t != this )
2003          {
2004             taskElem.appendChild( ReportXML::createXMLElem( doc, "Previous", t->getId()));
2005          }
2006       }
2007    }
2008    
2009    /* list of tasks by id which follow */
2010    if( followers.count() > 0 )
2011    {
2012       TaskList tl( followers );
2013       for (Task* t = tl.first(); t != 0; t = tl.next())
2014       { 
2015          if( t != this )
2016          {
2017             taskElem.appendChild( ReportXML::createXMLElem( doc, "Follower", t->getId()));
2018          }
2019       }
2020    }
2021
2022    /** Allocations and Booked Resources
2023     *  With the following code, the task in XML contains simply a list of all Allocations
2024     *  wiht the ResourceID for which resource the allocation is. After that, there comes
2025     *  a list of all Resources, again having the Resource Id as key. That could be put
2026     *  in a hirarchy like
2027     *  <Resource Id="dev2" >Larry Bono
2028     *       <Income>1000</Income>
2029     *       <Allocation>
2030     *          <Load>100</Load>
2031     *          <Persistent>Yes</Persistent>
2032     *       </Allocation>
2033     *  </Resource>
2034     *
2035     *  But we do not ;-) to have full flexibility. 
2036     *  
2037     */
2038    /* Allocations */
2039    if( allocations.count() > 0 )
2040    {
2041       QPtrList<Allocation> al(allocations);
2042       for (Allocation* a = al.first(); a != 0; a = al.next())
2043       {
2044          taskElem.appendChild( a->xmlElement( doc ));
2045       }
2046    }
2047
2048    /* booked Ressources */
2049    if( bookedResources.count() > 0 )
2050    {    
2051       QPtrList<Resource> br(bookedResources);
2052       for (Resource* r = br.first(); r != 0; r = br.next())
2053       {
2054          taskElem.appendChild( r->xmlIDElement( doc ));
2055       }
2056    }
2057
2058    return( taskElem );
2059 }
2060
2061 bool
2062 TaskList::isSupportedSortingCriteria(CoreAttributesList::SortCriteria sc)
2063 {
2064         switch (sc)
2065         {
2066         case TreeMode:
2067         case PlanStartUp:
2068         case PlanStartDown:
2069         case ActualStartUp:
2070         case ActualStartDown:
2071         case PlanEndUp:
2072         case PlanEndDown:
2073         case ActualEndUp:
2074         case ActualEndDown:
2075         case PrioUp:
2076         case PrioDown:
2077         case ResponsibleUp:
2078         case ResponsibleDown:
2079                 return TRUE;
2080         default:
2081                 return CoreAttributesList::isSupportedSortingCriteria(sc);
2082         }               
2083 }
2084
2085 int
2086 TaskList::compareItemsLevel(Task* t1, Task* t2, int level)
2087 {
2088         if (level < 0 || level >= maxSortingLevel)
2089                 return -1;
2090
2091         switch (sorting[level])
2092         {
2093         case TreeMode:
2094                 if (level == 0)
2095                         return compareTreeItemsT(this, t1, t2);
2096                 else
2097                         return t1->getSequenceNo() == t2->getSequenceNo() ? 0 :
2098                                 t1->getSequenceNo() < t2->getSequenceNo() ? -1 : 1;
2099         case PlanStartUp:
2100                 return t1->scenarios[0].start == t2->scenarios[0].start ? 0 :
2101                         t1->scenarios[0].start < t2->scenarios[0].start ? -1 : 1;
2102         case PlanStartDown:
2103                 return t1->scenarios[0].start == t2->scenarios[0].start ? 0 :
2104                         t1->scenarios[0].start > t2->scenarios[0].start ? -1 : 1;
2105         case ActualStartUp:
2106                 return t1->scenarios[1].start == t2->scenarios[1].start ? 0 :
2107                         t1->scenarios[1].start < t2->scenarios[1].start ? -1 : 1;
2108         case ActualStartDown:
2109                 return t1->scenarios[1].start == t2->scenarios[1].start ? 0 :
2110                         t1->scenarios[1].start > t2->scenarios[1].start ? -1 : 1;
2111         case PlanEndUp:
2112                 return t1->scenarios[0].end == t2->scenarios[0].end ? 0 :
2113                         t1->scenarios[0].end < t2->scenarios[0].end ? -1 : 1;
2114         case PlanEndDown:
2115                 return t1->scenarios[0].end == t2->scenarios[0].end ? 0 :
2116                         t1->scenarios[0].end > t2->scenarios[0].end ? -1 : 1;
2117         case ActualEndUp:
2118                 return t1->scenarios[1].end == t2->scenarios[1].end ? 0 :
2119                         t1->scenarios[1].end < t2->scenarios[1].end ? -1 : 1;
2120         case ActualEndDown:
2121                 return t1->scenarios[1].end == t2->scenarios[1].end ? 0 :
2122                         t1->scenarios[1].end > t2->scenarios[1].end ? -1 : 1;
2123         case PrioUp:
2124                 if (t1->priority == t2->priority)
2125                         return 0;
2126                 else
2127                         return (t1->priority - t2->priority);
2128         case PrioDown:
2129                 if (t1->priority == t2->priority)
2130                         return 0;
2131                 else
2132                         return (t2->priority - t1->priority);
2133         case ResponsibleUp:
2134         {
2135                 QString fn1;
2136                 t1->responsible->getFullName(fn1);
2137                 QString fn2;
2138                 t2->responsible->getFullName(fn2);
2139                 return - fn1.compare(fn2);
2140         }
2141         case ResponsibleDown:
2142         {
2143                 QString fn1;
2144                 t1->responsible->getFullName(fn1);
2145                 QString fn2;
2146                 t2->responsible->getFullName(fn2);
2147                 return fn1.compare(fn2);
2148         }
2149         default:
2150                 return CoreAttributesList::compareItemsLevel(t1, t2, level);
2151         }               
2152 }
2153
2154 int
2155 TaskList::compareItems(QCollection::Item i1, QCollection::Item i2)
2156 {
2157         Task* t1 = static_cast<Task*>(i1);
2158         Task* t2 = static_cast<Task*>(i2);
2159
2160         int res;
2161         for (int i = 0; i < CoreAttributesList::maxSortingLevel; ++i)
2162                 if ((res = compareItemsLevel(t1, t2, i)) != 0)
2163                         return res;
2164         return res;
2165 }
2166
2167 #ifdef HAVE_ICAL
2168 #ifdef HAVE_KDE
2169
2170 void Task::toTodo( KCal::Todo* todo, KCal::CalendarLocal* /* cal */ )
2171 {
2172    if( !todo ) return;
2173    QDateTime dt;
2174
2175    // todo->setReadOnly( true );
2176    
2177    /* Start-Time of the task */
2178    dt.setTime_t( getPlanStart() );
2179    todo->setDtStart( dt );
2180    todo->setHasDueDate( true );
2181    
2182    /* Due-Time of the todo -> plan End  */
2183    dt.setTime_t( getPlanEnd());
2184    todo->setDtDue( dt );
2185    todo->setHasStartDate(true);
2186
2187    /* Description and summary -> project ID */
2188    todo->setDescription( getNote() );
2189    todo->setSummary( getName() );
2190    todo->setPriority( getPriority() );
2191    todo->setCompleted( getComplete() );
2192
2193    /* Resources */
2194    QPtrList<Resource> resList;
2195    resList = getPlanBookedResources();
2196    QStringList strList;
2197    
2198    Resource *res = 0;
2199    for ( res = resList.first(); res; res = resList.next() )
2200    {
2201       strList.append( res->getName());
2202    }
2203    todo->setResources(strList);
2204    
2205 }
2206
2207 #endif /* HAVE_KDE */
2208 #endif /* HAVE_ICAL */
2209
2210 void Task::loadFromXML( QDomElement& parent, Project *project )
2211 {
2212    QDomElement elem = parent.firstChild().toElement();
2213
2214    for( ; !elem.isNull(); elem = elem.nextSibling().toElement() )
2215    {
2216       // qDebug(  "**Task -elemType: " + elem.tagName() );
2217       QString elemTagName = elem.tagName();
2218
2219       if( elemTagName == "Name" )
2220       {
2221         setName( elem.text());
2222       }
2223       else if( elemTagName == "SubTasks" )
2224       {                       
2225          QDomElement subTaskElem = elem.firstChild().toElement();
2226          for( ; !subTaskElem.isNull(); subTaskElem = subTaskElem.nextSibling().toElement() )
2227          {
2228             /* Recursive call for more tasks */
2229             QString stId = subTaskElem.attribute("Id");
2230             qDebug( "Recursing to elem " + stId );
2231             Task *t = new Task( project, stId, QString(), this, QString(), 0 );
2232             t->loadFromXML( subTaskElem, project );
2233             project->addTask(t);
2234             qDebug( "Recursing to elem " + stId + " <FIN>");
2235          }
2236       }
2237       else if( elemTagName == "Type" )
2238       {
2239          if( elem.text() == "Milestone" )
2240             setMilestone();
2241          /* Container and Task are detected automatically */
2242       }
2243       else if( elemTagName == "Previous" )
2244       {
2245          addDepends( elem.text() );
2246       }
2247       else if( elemTagName == "Follower" )
2248       {
2249          addPrecedes( elem.text() );
2250       }
2251       else if( elemTagName == "Index" )
2252          setIndex( elem.text().toUInt());
2253       else if( elemTagName == "Priority" )
2254         setPriority( elem.text().toInt() );
2255       else if( elemTagName == "complete" )
2256          setComplete(Task::Plan, elem.text().toInt() );
2257
2258       /* time-stuff: */
2259       else if( elemTagName == "minStart" )
2260          setMinStart( elem.text().toLong());
2261       else if( elemTagName == "maxStart" )
2262          setMaxStart( elem.text().toLong() );
2263       else if( elemTagName == "minEnd" )
2264          setMinEnd( elem.text().toLong() );
2265       else if( elemTagName == "maxEnd" )
2266          setMaxEnd( elem.text().toLong() );
2267       else if( elemTagName == "actualStart" )
2268          setStart(Task::Actual, elem.text().toLong() );
2269       else if( elemTagName == "actualEnd" )
2270          setEnd(Task::Actual, elem.text().toLong() );
2271       else if( elemTagName == "planStart" )
2272          setStart(Task::Plan, elem.text().toLong() );
2273       else if( elemTagName == "planEnd" )
2274          setEnd(Task::Plan, elem.text().toLong() );
2275    }
2276 }
2277