OSDN Git Service

Attempt to contain rounding errors.
[tjqt4port/tj2qt4.git] / taskjuggler / Project.cpp
1 /*
2  * Project.cpp - TaskJuggler
3  *
4  * Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006
5  * by Chris Schlaeger <cs@kde.org>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of version 2 of the GNU General Public License as
9  * published by the Free Software Foundation.
10  *
11  * $Id$
12  */
13
14 #include "Project.h"
15
16 #include <stdlib.h>
17
18 #include "TjMessageHandler.h"
19 #include "Scenario.h"
20 #include "Shift.h"
21 #include "Account.h"
22 #include "Resource.h"
23 #include "HTMLTaskReport.h"
24 #include "HTMLResourceReport.h"
25 #include "HTMLAccountReport.h"
26 #include "HTMLWeeklyCalendar.h"
27 #include "HTMLStatusReport.h"
28 #include "CSVTaskReport.h"
29 #include "CSVResourceReport.h"
30 #include "CSVAccountReport.h"
31 #include "ExportReport.h"
32 #include "ReportXML.h"
33 #include "UsageLimits.h"
34 #include "CustomAttributeDefinition.h"
35
36 DebugController DebugCtrl;
37
38 Project::Project() :
39     QObject(),
40     start(0),
41     end(0),
42     now(0),
43     allowRedefinitions(false),
44     weekStartsMonday(true),
45     name(),
46     version(),
47     copyright(),
48     customer(),
49     timeZone(),
50     timeFormat("%Y-%m-%d %H:%M"),
51     shortTimeFormat("%H:%M"),
52     currency(),
53     currencyDigits(3),
54     numberFormat("-", "", ",", ".", 1),
55     currencyFormat("(", ")", ",", ".", 0),
56     priority(500),
57     minEffort(0.0),
58     resourceLimits(0),
59     rate(0.0),
60     dailyWorkingHours(8.0),
61     yearlyWorkingDays(260.714),
62     workingHours(),
63     scheduleGranularity(suggestTimingResolution()),
64     allowedFlags(),
65     projectIDs(),
66     currentId(),
67     maxErrors(0),
68     journal(),
69     vacationList(),
70     scenarioList(),
71     taskList(),
72     resourceList(),
73     accountList(),
74     shiftList(),
75     originalTaskList(),
76     originalResourceList(),
77     originalAccountList(),
78     taskAttributes(),
79     resourceAttributes(),
80     accountAttributes(),
81     xmlreport(0),
82     reports(),
83     sourceFiles(),
84     breakFlag(false)
85 {
86     /* Pick some reasonable initial number since we don't know the
87      * project time frame yet. */
88     initUtility(20000);
89
90     vacationList.setAutoDelete(true);
91     accountAttributes.setAutoDelete(true);
92     taskAttributes.setAutoDelete(true);
93     resourceAttributes.setAutoDelete(true);
94     reports.setAutoDelete(true);
95
96     new Scenario(this, "plan", "Plan", 0);
97     scenarioList.createIndex(true);
98     scenarioList.createIndex(false);
99
100     setNow(time(0));
101
102     /* Initialize working hours with default values that match the Monday -
103      * Friday 9 - 6 (with 1 hour lunch break) pattern used by many western
104      * countries. */
105     // Sunday
106     workingHours[0] = new QPtrList<Interval>();
107     workingHours[0]->setAutoDelete(true);
108
109     for (int i = 1; i < 6; ++i)
110     {
111         workingHours[i] = new QPtrList<Interval>();
112         workingHours[i]->setAutoDelete(true);
113         workingHours[i]->append(new Interval(9 * ONEHOUR, 12 * ONEHOUR - 1));
114         workingHours[i]->append(new Interval(13 * ONEHOUR, 18 * ONEHOUR - 1));
115     }
116
117     // Saturday
118     workingHours[6] = new QPtrList<Interval>();
119     workingHours[6]->setAutoDelete(true);
120 }
121
122 Project::~Project()
123 {
124     taskList.deleteContents();
125     resourceList.deleteContents();
126     Resource::deleteStaticData();
127
128     accountList.deleteContents();
129     shiftList.deleteContents();
130     scenarioList.deleteContents();
131
132     delete resourceLimits;
133
134     // Remove support for 1.0 XML reports for next major release. */
135     delete xmlreport;
136
137     for (int i = 0; i < 7; ++i)
138         delete workingHours[i];
139     exitUtility();
140 }
141
142 void
143 Project::addSourceFile(const QString& f)
144 {
145     if (sourceFiles.find(f) == sourceFiles.end())
146         sourceFiles.append(f);
147 }
148
149 QStringList
150 Project::getSourceFiles() const
151 {
152     return sourceFiles;
153 }
154
155 void
156 Project::setProgressInfo(const QString& i)
157 {
158     emit updateProgressInfo(i);
159 }
160
161 void
162 Project::setProgressBar(int i, int of)
163 {
164     emit updateProgressBar(i, of);
165 }
166
167 bool
168 Project::setTimeZone(const QString& tz)
169 {
170     if (!setTimezone(tz))
171         return false;
172
173     timeZone = tz;
174     return true;
175 }
176
177 Scenario*
178 Project::getScenario(int sc) const
179 {
180     int i = 0;
181     for (ScenarioListIterator sli(scenarioList); sli; ++sli)
182         if (i++ == sc)
183             return *sli;
184     return 0;
185 }
186
187 const QString&
188 Project::getScenarioName(int sc) const
189 {
190     int i = 0;
191     for (ScenarioListIterator sli(scenarioList); sli; ++sli)
192         if (i++ == sc)
193             return (*sli)->getName();
194
195     return QString::null;
196 }
197
198 const QString&
199 Project::getScenarioId(int sc) const
200 {
201     int i = 0;
202     for (ScenarioListIterator sli(scenarioList); sli; ++sli)
203         if (i++ == sc)
204             return (*sli)->getId();
205
206     return QString::null;
207 }
208
209 int
210 Project::getScenarioIndex(const QString& id) const
211 {
212     return scenarioList.getIndex(id);
213 }
214
215 void
216 Project::setNow(time_t n)
217 {
218     /* Align 'now' time to timing resolution. If the resolution is
219      * changed later, this has to be done again. */
220     now = (n / scheduleGranularity) * scheduleGranularity;
221 }
222
223 void
224 Project::setWorkingHours(int day, const QPtrList<Interval>& l)
225 {
226     if (day < 0 || day > 6)
227         qFatal("day out of range");
228     delete workingHours[day];
229
230     // Create a deep copy of the interval list.
231     workingHours[day] = new QPtrList<Interval>;
232     workingHours[day]->setAutoDelete(true);
233     for (QPtrListIterator<Interval> pli(l); pli; ++pli)
234         workingHours[day]->append(new Interval(**pli));
235 }
236
237 bool
238 Project::addId(const QString& id, bool changeCurrentId)
239 {
240     if (projectIDs.findIndex(id) != -1)
241         return false;
242     else
243         projectIDs.append(id);
244
245     if (changeCurrentId)
246         currentId = id;
247
248     return true;
249 }
250
251 QString
252 Project::getIdIndex(const QString& i) const
253 {
254     int idx;
255     if ((idx = projectIDs.findIndex(i)) == -1)
256         return QString("?");
257     QString idxStr;
258     do
259     {
260         idxStr = QChar('A' + idx % ('Z' - 'A')) + idxStr;
261         idx /= 'Z' - 'A';
262     } while (idx > 'Z' - 'A');
263
264     return idxStr;
265 }
266
267 void
268 Project::addScenario(Scenario* s)
269 {
270     scenarioList.append(s);
271
272     /* This is not too efficient, but since there are usually only a few
273      * scenarios in a project, this doesn't hurt too much. */
274     scenarioList.createIndex(true);
275     scenarioList.createIndex(false);
276 }
277
278 void
279 Project::deleteScenario(Scenario* s)
280 {
281     scenarioList.removeRef(s);
282 }
283
284 void
285 Project::setResourceLimits(UsageLimits* l)
286 {
287     if (resourceLimits)
288         delete resourceLimits;
289     resourceLimits = l;
290 }
291
292 void
293 Project::addTask(Task* t)
294 {
295     taskList.append(t);
296 }
297
298 void
299 Project::deleteTask(Task* t)
300 {
301     taskList.removeRef(t);
302 }
303
304 bool
305 Project::addTaskAttribute(const QString& id, CustomAttributeDefinition* cad)
306 {
307     if (taskAttributes.find(id))
308         return false;
309
310     taskAttributes.insert(id, cad);
311     return true;
312 }
313
314 const CustomAttributeDefinition*
315 Project::getTaskAttribute(const QString& id) const
316 {
317     return taskAttributes[id];
318 }
319
320 void
321 Project::addShift(Shift* s)
322 {
323     shiftList.append(s);
324 }
325
326 void
327 Project::deleteShift(Shift* s)
328 {
329     shiftList.removeRef(s);
330 }
331
332 void
333 Project::addResource(Resource* r)
334 {
335     resourceList.append(r);
336 }
337
338 void
339 Project::deleteResource(Resource* r)
340 {
341     resourceList.removeRef(r);
342 }
343
344 bool
345 Project::addResourceAttribute(const QString& id,
346                               CustomAttributeDefinition* cad)
347 {
348     if (resourceAttributes.find(id))
349         return false;
350
351     resourceAttributes.insert(id, cad);
352     return true;
353 }
354
355 const CustomAttributeDefinition*
356 Project::getResourceAttribute(const QString& id) const
357 {
358     return resourceAttributes[id];
359 }
360
361 void
362 Project::addAccount(Account* a)
363 {
364     accountList.append(a);
365 }
366
367 void
368 Project::deleteAccount(Account* a)
369 {
370     accountList.removeRef(a);
371 }
372
373 bool
374 Project::addAccountAttribute(const QString& id,
375                               CustomAttributeDefinition* cad)
376 {
377     if (accountAttributes.find(id))
378         return false;
379
380     accountAttributes.insert(id, cad);
381     return true;
382 }
383
384 const CustomAttributeDefinition*
385 Project::getAccountAttribute(const QString& id) const
386 {
387     return accountAttributes[id];
388 }
389
390 bool
391 Project::isWorkingDay(time_t wd) const
392 {
393     return !(workingHours[dayOfWeek(wd, false)]->isEmpty() ||
394              isVacation(wd));
395 }
396
397 bool
398 Project::isWorkingTime(time_t wd) const
399 {
400     if (isVacation(wd))
401         return false;
402
403     int dow = dayOfWeek(wd, false);
404     for (QPtrListIterator<Interval> ili(*(workingHours[dow])); *ili != 0; ++ili)
405     {
406         if ((*ili)->contains(secondsOfDay(wd)))
407             return true;
408     }
409     return false;
410 }
411
412 bool
413 Project::isWorkingTime(const Interval& iv) const
414 {
415     if (isVacation(iv.getStart()))
416         return false;
417
418     int dow = dayOfWeek(iv.getStart(), false);
419     for (QPtrListIterator<Interval> ili(*(workingHours[dow])); *ili != 0; ++ili)
420     {
421         if ((*ili)->contains(Interval(secondsOfDay(iv.getStart()),
422                                   secondsOfDay(iv.getEnd()))))
423             return true;
424     }
425     return false;
426 }
427
428 int
429 Project::calcWorkingDays(const Interval& iv) const
430 {
431     int workingDays = 0;
432
433     for (time_t s = midnight(iv.getStart()); s <= iv.getEnd();
434          s = sameTimeNextDay(s))
435         if (isWorkingDay(s))
436             workingDays++;
437
438     return workingDays;
439 }
440
441 double
442 Project::convertToDailyLoad(long secs) const
443 {
444     return (((double) secs) / (dailyWorkingHours * ONEHOUR));
445 }
446
447 double
448 Project::quantizeLoad(double load) const
449 {
450     return load;
451     return ((double) (((long) (load * dailyWorkingHours * (double) ONEHOUR)) /
452                                scheduleGranularity)) /
453                       ((dailyWorkingHours * (double) ONEHOUR)) *
454                        (double) scheduleGranularity;
455 }
456
457 long int
458 Project::convertToSlots(double effort) const
459 {
460     return ((long int) ((effort * dailyWorkingHours * (double) ONEHOUR)) /
461             scheduleGranularity);
462 }
463
464 void
465 Project::addJournalEntry(JournalEntry* entry)
466 {
467     journal.inSort(entry);
468 }
469
470 Journal::Iterator
471 Project::getJournalIterator() const
472 {
473     return Journal::Iterator(journal);
474 }
475
476 bool
477 Project::pass2(bool noDepCheck)
478 {
479     int oldErrors = TJMH.getErrors();
480
481     if (taskList.isEmpty())
482     {
483         TJMH.errorMessage(i18n("The project does not contain any tasks."));
484         return false;
485     }
486
487     QDict<Task> idHash;
488
489     /* The optimum size for the localtime hash is twice the number of time
490      * slots times 2 (because of timeslot and timeslot - 1s). */
491     initUtility(4 * ((end - start) / scheduleGranularity));
492
493     // Generate sequence numbers for all lists.
494     taskList.createIndex(true);
495     resourceList.createIndex(true);
496     accountList.createIndex(true);
497     shiftList.createIndex(true);
498
499     // Initialize random generator.
500     srand((int) start);
501
502     // Create hash to map task IDs to pointers.
503     for (TaskListIterator tli(taskList); *tli != 0; ++tli)
504     {
505         idHash.insert((*tli)->getId(), *tli);
506     }
507     // Create cross links from dependency lists.
508     for (TaskListIterator tli(taskList); *tli != 0; ++tli)
509         (*tli)->xRef(idHash);
510
511     for (TaskListIterator tli(taskList); *tli != 0; ++tli)
512     {
513         // Set dates according to implicit dependencies
514         (*tli)->implicitXRef();
515
516         // Sort allocations properly
517         (*tli)->sortAllocations();
518
519         // Save so far booked resources as specified resources
520         (*tli)->saveSpecifiedBookedResources();
521     }
522
523     // Save a copy of all manually booked resources.
524     for (ResourceListIterator rli(resourceList); *rli != 0; ++rli)
525         (*rli)->saveSpecifiedBookings();
526
527     /* Now we can copy the missing values from the plan scenario to the other
528      * scenarios. */
529     if (scenarioList.count() > 1)
530     {
531         for (ScenarioListIterator sli(scenarioList[0]->getSubListIterator());
532              *sli; ++sli)
533             overlayScenario(0, (*sli)->getSequenceNo() - 1);
534     }
535
536     // Now check that all tasks have sufficient data to be scheduled.
537     setProgressInfo(i18n("Checking scheduling data..."));
538     bool error = false;
539     for (ScenarioListIterator sci(scenarioList); *sci; ++sci)
540         for (TaskListIterator tli(taskList); *tli != 0; ++tli)
541             if (!(*tli)->preScheduleOk((*sci)->getSequenceNo() - 1))
542             {
543                 error = true;
544             }
545     if (error)
546         return false;
547
548     if (!noDepCheck)
549     {
550         setProgressInfo(i18n("Searching for dependency loops ..."));
551         if (DEBUGPS(1))
552             tjDebug("Searching for dependency loops ...");
553         // Check all tasks for dependency loops.
554         LDIList chkedTaskList;
555         for (TaskListIterator tli(taskList); *tli != 0; ++tli)
556             if ((*tli)->loopDetector(chkedTaskList))
557                 return false;
558
559         setProgressInfo(i18n("Searching for underspecified tasks ..."));
560         if (DEBUGPS(1))
561             tjDebug("Searching for underspecified tasks ...");
562         for (ScenarioListIterator sci(scenarioList); *sci; ++sci)
563             for (TaskListIterator tli(taskList); *tli != 0; ++tli)
564                 if (!(*tli)->checkDetermination((*sci)->getSequenceNo() - 1))
565                     error = true;
566
567         if (error)
568             return false;
569     }
570
571     return TJMH.getErrors() == oldErrors;
572 }
573
574 bool
575 Project::scheduleScenario(Scenario* sc)
576 {
577     int oldErrors = TJMH.getErrors();
578
579     setProgressInfo(i18n("Scheduling scenario %1...").arg(sc->getId()));
580
581     int scIdx = sc->getSequenceNo() - 1;
582     prepareScenario(scIdx);
583
584     if (!schedule(scIdx))
585     {
586         if (DEBUGPS(2))
587             tjDebug(i18n("Scheduling errors in scenario '%1'.")
588                    .arg(sc->getId()));
589         if (breakFlag)
590             return false;
591     }
592     finishScenario(scIdx);
593
594     for (ResourceListIterator rli(resourceList); *rli != 0; ++rli)
595     {
596         if (!(*rli)->bookingsOk(scIdx))
597             break;
598     }
599
600     return TJMH.getErrors() == oldErrors;
601 }
602
603 void
604 Project::completeBuffersAndIndices()
605 {
606     for (TaskListIterator tli(taskList); *tli != 0; ++tli)
607         (*tli)->computeBuffers();
608
609     /* Create indices for all lists according to their default sorting
610      * criteria. */
611     taskList.createIndex();
612     resourceList.createIndex();
613     accountList.createIndex();
614     shiftList.createIndex();
615 }
616
617 bool
618 Project::scheduleAllScenarios()
619 {
620     bool schedulingOk = true;
621     for (ScenarioListIterator sci(scenarioList); *sci; ++sci)
622         if ((*sci)->getEnabled())
623         {
624             if (DEBUGPS(1))
625                 tjDebug(i18n("Scheduling scenario '%1' ...")
626                        .arg((*sci)->getId()));
627
628             if (!scheduleScenario(*sci))
629                 schedulingOk = false;
630             if (breakFlag)
631                 return false;
632         }
633
634     completeBuffersAndIndices();
635
636     return schedulingOk;
637 }
638
639 void
640 Project::overlayScenario(int base, int sc)
641 {
642     for (TaskListIterator tli(taskList); *tli != 0; ++tli)
643         (*tli)->overlayScenario(base, sc);
644
645     for (ScenarioListIterator sli(scenarioList[sc]->getSubListIterator());
646          *sli; ++sli)
647         overlayScenario(sc, (*sli)->getSequenceNo() - 1);
648 }
649
650 void
651 Project::prepareScenario(int sc)
652 {
653     for (ResourceListIterator rli(resourceList); *rli != 0; ++rli)
654         (*rli)->prepareScenario(sc);
655
656     for (TaskListIterator tli(taskList); *tli != 0; ++tli)
657         (*tli)->prepareScenario(sc);
658
659     /* First we compute the criticalness of the individual task without their
660      * dependency context. */
661     for (TaskListIterator tli(taskList); *tli != 0; ++tli)
662         (*tli)->computeCriticalness(sc);
663
664     /* Then we compute the path criticalness that represents the criticalness
665      * of a task taking their dependency context into account. */
666     for (TaskListIterator tli(taskList); *tli != 0; ++tli)
667         (*tli)->computePathCriticalness(sc);
668
669     for (TaskListIterator tli(taskList); *tli != 0; ++tli)
670         (*tli)->propagateInitialValues(sc);
671
672     if (DEBUGTS(4))
673     {
674         tjDebug("Allocation probabilities for the resources:");
675         for (ResourceListIterator rli(resourceList); *rli != 0; ++rli)
676             qDebug("Resource %s: %f%%",
677                    (*rli)->getId().latin1(),
678                    (*rli)->getAllocationProbability(sc));
679         tjDebug("Criticalnesses of the tasks with respect to resource "
680                "availability:");
681         for (TaskListIterator tli(taskList); *tli != 0; ++tli)
682             qDebug("Task %s: %-5.1f %-5.1f", (*tli)->getId().latin1(),
683                    (*tli)->getCriticalness(sc),
684                    (*tli)->getPathCriticalness(sc));
685     }
686 }
687
688 void
689 Project::finishScenario(int sc)
690 {
691     for (ResourceListIterator rli(resourceList); *rli != 0; ++rli)
692         (*rli)->finishScenario(sc);
693     for (TaskListIterator tli(taskList); *tli != 0; ++tli)
694         (*tli)->finishScenario(sc);
695
696     /* We need to have finished the scenario for all tasks before we can
697      * calculate the completion degree. */
698     for (TaskListIterator tli(taskList); *tli != 0; ++tli)
699         (*tli)->calcCompletionDegree(sc);
700
701     /* If the user has not set the minSlackRate to 0 we look for critical
702      * pathes. */
703     if (getScenario(sc)->getMinSlackRate() > 0.0)
704     {
705         setProgressInfo(i18n("Computing critical pathes..."));
706         /* The critical path detector needs to know the end of the last task.
707          * So we have to find this out first. */
708         time_t maxEnd = 0;
709         for (TaskListIterator tli(taskList); *tli != 0; ++tli)
710         if (maxEnd < (*tli)->getEnd(sc))
711             maxEnd = (*tli)->getEnd(sc);
712
713         for (TaskListIterator tli(taskList); *tli != 0; ++tli)
714             (*tli)->checkAndMarkCriticalPath
715                 (sc, getScenario(sc)->getMinSlackRate(), maxEnd);
716     }
717 }
718
719 bool
720 Project::schedule(int sc)
721 {
722     int oldErrors = TJMH.getErrors();
723
724     // The scheduling function only cares about leaf tasks. Container tasks
725     // are scheduled automatically when all their childern are scheduled. So
726     // we create a task list that only contains leaf tasks.
727     TaskList allLeafTasks;
728     for (TaskListIterator tli(taskList); *tli != 0; ++tli)
729         if (!(*tli)->hasSubs())
730             allLeafTasks.append(*tli);
731
732     allLeafTasks.setSorting(CoreAttributesList::PrioDown, 0);
733     allLeafTasks.setSorting(CoreAttributesList::PathCriticalnessDown, 1);
734     allLeafTasks.setSorting(CoreAttributesList::SequenceUp, 2);
735     allLeafTasks.sort();
736
737     /* The workItems list contains all tasks that are ready to be scheduled at
738      * any given iteration. When a tasks has been scheduled completely, this
739      * list needs to be updated again as some tasks may now have become ready
740      * to be scheduled. */
741     TaskList workItems;
742     int sortedTasks = 0;
743     for (TaskListIterator tli(allLeafTasks); *tli != 0; ++tli)
744     {
745         if ((*tli)->isReadyForScheduling())
746             workItems.append(*tli);
747         if ((*tli)->isSchedulingDone())
748             sortedTasks++;
749     }
750
751     bool done;
752     /* While the scheduling process progresses, the list contains more and
753      * more scheduled tasks. We use the cleanupTimer to remove those in
754      * certain intervals. As we potentially have already completed tasks in
755      * the list when we start, we initialize the timer with a very large
756      * number so the first round of cleanup is done right after the first
757      * scheduling pass. */
758     breakFlag = false;
759     bool runAwayFound = false;
760     do
761     {
762         done = true;
763         time_t slot = 0;
764         int priority = 0;
765         double pathCriticalness = 0.0;
766         Task::SchedulingInfo schedulingInfo = Task::ASAP;
767
768         /* The task list is sorted by priority. The priority decreases towards
769          * the end of the list. We iterate through the list and look for a
770          * task that can be scheduled. It the determines the time slot that
771          * will be scheduled during this run for all subsequent tasks as well.
772          */
773         for (TaskListIterator tli(workItems); *tli != 0; ++tli)
774         {
775             if (slot == 0)
776             {
777                 /* No time slot has been set yet. Check if this task can be
778                  * scheduled and provides a suggestion. */
779                 slot = (*tli)->nextSlot(scheduleGranularity);
780                 /* If not, try the next task. */
781                 if (slot == 0)
782                     continue;
783                 priority = (*tli)->getPriority();
784                 pathCriticalness = (*tli)->getPathCriticalness(sc);
785                 schedulingInfo = (*tli)->getScheduling();
786
787                 if (DEBUGPS(4))
788                     qDebug("Task '%s' (Prio %d, Direction: %d) requests "
789                            "slot %s",
790                            (*tli)->getId().latin1(), (*tli)->getPriority(),
791                            (*tli)->getScheduling(),
792                            time2ISO(slot).latin1());
793                 /* If the task wants a time slot outside of the project time
794                  * frame, we flag this task as a runaway and go to the next
795                  * task. */
796                 if (slot < start ||
797                     slot > (end - (time_t) scheduleGranularity + 1))
798                 {
799                     (*tli)->setRunaway();
800                     runAwayFound = true;
801                     slot = 0;
802                     continue;
803                 }
804             }
805             done = false;
806             /* Each task has a scheduling direction (forward or backward)
807              * depending on it's constrains. The task with the highest
808              * priority/pathCriticalness determins the time slot and hence the
809              * scheduling direction. Since tasks that have the other direction
810              * cannot the scheduled then, we have to stop this run as soon as
811              * we hit a task that runs in the other direction. If we would not
812              * do this, tasks with lower priority/pathCriticalness  would grab
813              * resources form tasks with higher priority. */
814             if ((*tli)->getScheduling() != schedulingInfo &&
815                 !(*tli)->isMilestone())
816             {
817                 if (DEBUGPS(4))
818                     qDebug("Changing scheduling direction to %d due to task "
819                            "'%s'", (*tli)->getScheduling(),
820                            (*tli)->getId().latin1());
821                 break;
822             }
823             /* We must avoid that lower priority tasks get resources even
824              * though there are higher priority tasks that are ready to be
825              * scheduled but have a non-adjacent last slot. If two tasks have
826              * the same priority the pathCriticalness is being used. */
827             if ((*tli)->getPriority() < priority ||
828                 ((*tli)->getPriority() == priority &&
829                  (*tli)->getPathCriticalness(sc) < pathCriticalness))
830                 break;
831
832             // Schedule this task for the current time slot.
833             if ((*tli)->schedule(sc, slot, scheduleGranularity))
834             {
835                 workItems.clear();
836                 int oldSortedTasks = sortedTasks;
837                 sortedTasks = 0;
838                 for (TaskListIterator tli(allLeafTasks); *tli != 0; ++tli)
839                 {
840                     if ((*tli)->isReadyForScheduling())
841                         workItems.append(*tli);
842                     if ((*tli)->isSchedulingDone())
843                         sortedTasks++;
844                 }
845                 // Update the progress bar after every 10th completed tasks.
846                 if (oldSortedTasks / 10 != sortedTasks / 10)
847                 {
848                     setProgressBar(sortedTasks - allLeafTasks.count(),
849                                    sortedTasks);
850                     setProgressInfo
851                         (i18n("Scheduling scenario %1 at %2")
852                          .arg(getScenarioId(sc)).arg(time2tjp(slot)));
853                 }
854             }
855         }
856     } while (!done && !breakFlag);
857
858     if (breakFlag)
859     {
860         setProgressInfo("");
861         setProgressBar(0, 0);
862         TJMH.errorMessage(i18n("Scheduling aborted on user request"));
863         return false;
864     }
865
866     if (runAwayFound)
867         for (TaskListIterator tli(taskList); *tli != 0; ++tli)
868             if ((*tli)->isRunaway())
869             {
870                 if ((*tli)->getScheduling() == Task::ASAP)
871                     (*tli)->errorMessage
872                         (i18n("End of task '%1' does not fit into the "
873                               "project time frame. Try using a later project "
874                               "end date.")
875                          .arg((*tli)->getId()));
876                 else
877                     (*tli)->errorMessage
878                         (i18n("Start of task '%1' does not fit into the "
879                               "project time frame. Try using an earlier "
880                               "project start date.").arg((*tli)->getId()));
881             }
882
883     if (TJMH.getErrors() == oldErrors)
884         setProgressBar(100, 100);
885
886     /* Check that the resulting schedule meets all the requirements that the
887      * user has specified. */
888     setProgressInfo(i18n("Checking schedule of scenario %1")
889                     .arg(getScenarioId(sc)));
890     checkSchedule(sc);
891
892     return TJMH.getErrors() == oldErrors;
893 }
894
895 void
896 Project::breakScheduling()
897 {
898     breakFlag = true;
899 }
900
901 bool
902 Project::checkSchedule(int sc) const
903 {
904     int oldErrors = TJMH.getErrors();
905     for (TaskListIterator tli(taskList); *tli != 0; ++tli)
906     {
907         /* Only check top-level tasks, since they recursively check their sub
908          * tasks. */
909         if ((*tli)->getParent() == 0)
910             (*tli)->scheduleOk(sc);
911         if (maxErrors > 0 && TJMH.getErrors() >= maxErrors)
912         {
913             TJMH.errorMessage
914                 (i18n("Too many errors in %1 scenario. Giving up.")
915                  .arg(getScenarioId(sc)));
916             return false;
917         }
918     }
919
920     return TJMH.getErrors() == oldErrors;
921 }
922
923 Report*
924 Project::getReport(uint idx) const
925 {
926     QPtrListIterator<Report> it(reports);
927     for (uint i = 0; *it && i < idx; ++it, ++i)
928         ;
929     return *it;
930 }
931
932 QPtrListIterator<Report>
933 Project::getReportListIterator() const
934 {
935     return QPtrListIterator<Report>(reports);
936 }
937
938 bool
939 Project::generateReports() const
940 {
941     // Generate reports
942     int errors = 0;
943     for (QPtrListIterator<Report> ri(reports); *ri != 0; ++ri)
944     {
945         // We generate all but Qt*Reports. Those are for the GUI version.
946         if (strncmp((*ri)->getType(), "Qt", 2) != 0)
947         {
948             if (DEBUGPS(1))
949                 tjDebug(i18n("Generating report '%1' ...")
950                        .arg((*ri)->getFileName()));
951
952             if (!(*ri)->generate())
953                 errors++;
954         }
955     }
956
957     generateXMLReport();
958
959     return errors == 0;
960 }
961
962 bool Project::generateXMLReport() const
963 {
964     if ( xmlreport )
965         return xmlreport->generate();
966     else
967         return false;
968 }