OSDN Git Service

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