OSDN Git Service

- Rewrote scheduler to be more accurate
[tjqt4port/tj2qt4.git] / taskjuggler / Project.cpp
1 /*
2  * Project.cpp - TaskJuggler
3  *
4  * Copyright (c) 2001 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 version 2 of the GNU General Public License as
8  * published by the Free Software Foundation.
9  *
10  * $Id$
11  */
12
13 #include <config.h>
14 #include <stdio.h>
15
16 #include <qdict.h>
17
18 #include "Project.h"
19 #include "Utility.h"
20
21 #define COL_DEFAULT "#fffadd"
22 #define COL_WEEKEND "#ffec80"
23 #define COL_BOOKED "#ffc0a3"
24 #define COL_HEADER "#a5c2ff"
25 #define COL_MILESTONE "#ff2a2a"
26
27 Project::Project()
28 {
29         taskList.setAutoDelete(TRUE);
30         priority = 50;
31         start = 0;
32         end = 0;
33         minEffort = 0.0;
34         maxEffort = 1.0;
35         rate = 0.0;
36         /* The 'closed' flag may be used for container classes to hide all
37          * sub tasks. */
38         addAllowedFlag("closed");
39         // The 'hidden' flag may be used to hide the task in all reports.
40         addAllowedFlag("hidden");
41         htmlTaskReport = "";
42         htmlTaskReportStart = 0;
43         htmlTaskReportEnd = 0;
44         htmlResourceReport = "";
45         htmlResourceReportStart = 0;
46         htmlResourceReportEnd = 0;
47 }
48
49 bool
50 Project::addTask(Task* t)
51 {
52         taskList.append(t);
53         return TRUE;
54 }
55
56 bool
57 Project::pass2()
58 {
59         QDict<Task> idHash;
60         bool error = FALSE;
61
62         // Create hash to map task IDs to pointers.
63         for (Task* t = taskList.first(); t != 0; t = taskList.next())
64         {
65                 idHash.insert(t->getId(), t);
66         }
67
68         // Create cross links from dependency lists.
69         for (Task* t = taskList.first(); t != 0; t = taskList.next())
70         {
71                 if (!t->xRef(idHash))
72                         error = TRUE;
73         }
74
75         TaskList sortedTasks(taskList);
76         sortedTasks.setSorting(TaskList::PrioDown);
77         sortedTasks.sort();
78
79         const time_t scheduleGranularity = ONEHOUR;
80         for (int day = start; day < end; day += scheduleGranularity)
81         {
82                 bool done;
83                 do
84                 {
85                         done = TRUE;
86                         for (Task* t = sortedTasks.first(); t != 0; t = sortedTasks.next())
87                                 if (!t->schedule(day, scheduleGranularity))
88                                         done = FALSE;
89                 } while (!done);
90         }
91
92         if (unscheduledTasks() > 0)
93         {
94                 fprintf(stderr, "Can't schedule some tasks. Giving up!\n");
95         }
96         else
97                 checkSchedule();
98
99         return error;
100 }
101
102 bool
103 Project::reportHTMLHeader(FILE* f)
104 {
105         fprintf(f, "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\">\n"
106                         "<! Generated by TaskJuggler v"VERSION">"
107                         "<html>\n"
108                         "<head>\n"
109                         "<title>Task Report</title>\n"
110                         "</head>\n"
111                         "<body>\n");
112         return TRUE;
113 }
114
115 bool
116 Project::reportHTMLFooter(FILE* f)
117 {
118         fprintf(f, "<br><font size=\"-2\">%s - TaskJuggler v"
119                         VERSION"</font></body>\n"
120                         "</html>\n", version.latin1());
121         return TRUE;
122 }
123
124 bool
125 Project::reportHTMLTaskList()
126 {
127         /* TODO: This is pretty much a quick hack. Using FILE is bad. latin1()
128          * does not work for non ASCII characters! */
129         if (htmlTaskReport == "")
130                 return TRUE;
131
132         if (htmlTaskReportStart == 0)
133                 htmlTaskReportStart = start;
134         if (htmlTaskReportEnd == 0)
135                 htmlTaskReportEnd = 0;
136
137         FILE* f;
138         if ((f = fopen(htmlTaskReport, "w")) == NULL)
139         {
140                 fprintf(stderr, "Cannot open report file %s!\n",
141                                 htmlTaskReport.latin1());
142                 return FALSE;
143         }
144         reportHTMLHeader(f);
145         fprintf(f, "<table border=\"0\" cellpadding=\"1\">\n");
146         fprintf(f, "<tr>");
147         for (QStringList::Iterator it = htmlTaskReportColumns.begin();
148                  it != htmlTaskReportColumns.end(); ++it )
149         {
150                 if (*it == "no")
151                         fprintf(f, "<td bgcolor=\""COL_HEADER"\" rowspan=\"2\">"
152                                         "<h2>No.</h2></td>");
153                 else if (*it == "taskId")
154                         fprintf(f, "<td bgcolor=\""COL_HEADER"\" rowspan=\"2\">"
155                                         "<h2>Task ID</h2></td>");
156                 else if (*it == "name")
157                         fprintf(f, "<td bgcolor=\""COL_HEADER"\" rowspan=\"2\">"
158                                         "<h2>Task</h2></td>");
159                 else if (*it == "start")
160                         fprintf(f, "<td bgcolor=\""COL_HEADER"\" rowspan=\"2\">"
161                                         "<h2>Start</h2></td>");
162                 else if (*it == "end")
163                         fprintf(f, "<td bgcolor=\""COL_HEADER"\" rowspan=\"2\">"
164                                         "<h2>End</h2></td>");
165                 else if (*it == "minStart")
166                         fprintf(f, "<td bgcolor=\""COL_HEADER"\" rowspan=\"2\">"
167                                         "<h2>Min. Start</h2></td>");
168                 else if (*it == "maxStart")
169                         fprintf(f, "<td bgcolor=\""COL_HEADER"\" rowspan=\"2\">"
170                                         "<h2>Max. Start</h2></td>");
171                 else if (*it == "resources")
172                         fprintf(f, "<td bgcolor=\""COL_HEADER"\" rowspan=\"2\">"
173                                         "<h2>Resources</h2></td>");
174                 else if (*it == "depends")
175                         fprintf(f, "<td bgcolor=\""COL_HEADER"\" rowspan=\"2\">"
176                                         "<h2>Dependencies</h2></td>");
177                 else if (*it == "follows")
178                         fprintf(f, "<td bgcolor=\""COL_HEADER"\" rowspan=\"2\">"
179                                         "<h2>Following Tasks</h2></td>");
180                 else if (*it == "note")
181                         fprintf(f, "<td bgcolor=\""COL_HEADER"\" rowspan=\"2\">"
182                                         "<h2>Note</h2></td>");
183                 else if (*it == "daily")
184                 {
185                         fprintf(f, "<td bgcolor=\""COL_HEADER"\" rowspan=\"2\"></td>");
186                         htmlMonthHeader(f, htmlTaskReportStart, htmlTaskReportEnd);
187                 }
188                 else
189                 {
190                         fprintf(stderr, "Unknown Column '%s' for HTML Task Report\n",
191                                         (*it).latin1());
192                         return FALSE;
193                 }
194         }
195         fprintf(f, "</tr>");
196         fprintf(f, "<tr>");
197         for (QStringList::Iterator it = htmlTaskReportColumns.begin();
198                  it != htmlTaskReportColumns.end(); ++it )
199         {
200                 if (*it == "daily")
201                         htmlDayHeader(f, htmlTaskReportStart, htmlTaskReportEnd);
202         }
203         fprintf(f, "</tr>\n");
204
205         int i = 1;
206         QDict<int> idxDict;
207         idxDict.setAutoDelete(TRUE);
208         for (Task* t = taskList.first(); t != 0; t = taskList.next(), ++i)
209                 idxDict.insert(t->getId(), new int(i));
210
211         i = 1;
212         for (Task* t = taskList.first(); t != 0; t = taskList.next(), ++i)
213         {
214                 if (t->hasFlag("hidden"))
215                         continue;
216
217                 fprintf(f, "<tr valign=\"center\">");
218                 for (QStringList::Iterator it = htmlTaskReportColumns.begin();
219                          it != htmlTaskReportColumns.end(); ++it )
220                 {
221                         if (*it == "no")
222                                 fprintf(f, "<td bgcolor=\""COL_DEFAULT"\" rowspan=\"2\">"
223                                                 "%d.</td>", i);
224                         else if (*it == "taskId")
225                                 fprintf(f, "<td bgcolor=\""COL_DEFAULT"\" rowspan=\"2\""
226                                                 " nowrap>%s</td>", t->getId().latin1());
227                         else if (*it == "name")
228                         {
229                                 int indent;
230                                 Task* tp = t->getParent();
231                                 QString spaces = "";
232                                 for (indent = 0; tp != 0; ++indent)
233                                 {
234                                         spaces += "&nbsp;&nbsp;&nbsp;&nbsp;";
235                                         tp = tp->getParent();
236                                 }
237                                 fprintf(f, "<td bgcolor=\""COL_DEFAULT"\" rowspan=\"2\""
238                                                 " nowrap>%s<font size=\"%c%d\">%s</font></td>\n",
239                                                 spaces.latin1(), 2 - indent < 0 ? '-' : '+',
240                                                 2 - indent < 0 ? -(2 - indent) : 2 - indent,
241                                                 t->getName().latin1());
242                         }
243                         else if (*it == "start")
244                                 fprintf(f, "<td bgcolor=\""COL_DEFAULT"\" rowspan=\"2\" %s>"
245                                                 "%s</td>\n",
246                                                 t->isStartOk() ? "" : "bgcolor=\"red\"",
247                                                 t->getStartISO().latin1());
248                         else if (*it == "end")
249                                 fprintf(f, "<td bgcolor=\""COL_DEFAULT"\" rowspan=\"2\" %s>"
250                                                 "%s</td>\n",
251                                                 t->isEndOk() ? "" : "bgcolor=\"red\"",
252                                                 t->getEndISO().latin1());
253                         else if (*it == "minStart")
254                                 fprintf(f, "<td bgcolor=\""COL_DEFAULT"\" rowspan=\"2\">"
255                                                 "%s</td>\n",
256                                                 time2ISO(t->getMinStart()).latin1());
257                         else if (*it == "maxStart")
258                                 fprintf(f, "<td bgcolor=\""COL_DEFAULT"\" rowspan=\"2\">"
259                                                 "%s</td>\n",
260                                                 time2ISO(t->getMaxStart()).latin1());
261                         else if (*it == "resources")
262                         {
263                                 fprintf(f, "<td bgcolor=\""COL_DEFAULT"\" rowspan=\"2\">"
264                                                 "<font size=\"-2\">");
265                                 bool first = TRUE;
266                                 for (Resource* r = t->firstBookedResource(); r != 0;
267                                          r = t->nextBookedResource())
268                                 {
269                                         fprintf(f, "%s%s", first ? "" : ", ",
270                                                         r->getName().latin1());
271                                         first = FALSE;
272                                 }
273                                 fprintf(f, "</font></td>\n");
274                         }
275                         else if (*it == "depends")
276                         {
277                                 fprintf(f, "<td bgcolor=\""COL_DEFAULT"\" rowspan=\"2\">"
278                                                 "<font size=\"-2\">");
279                                 bool first = TRUE;
280                                 for (Task* d = t->firstPrevious(); d != 0;
281                                          d = t->nextPrevious())
282                                 {
283                                         fprintf(f, "%s%d", first ? "" : ", ",
284                                                         *(idxDict[d->getId()]));
285                                         first = FALSE;
286                                 }
287                                 fprintf(f, "</font></td>\n");
288                         }
289                         else if (*it == "follows")
290                         {
291                                 fprintf(f, "<td bgcolor=\""COL_DEFAULT"\" rowspan=\"2\">"
292                                                 "<font size=\"-2\">");
293                                 bool first = TRUE;
294                                 for (Task* d = t->firstFollower(); d != 0;
295                                          d = t->nextFollower())
296                                 {
297                                         fprintf(f, "%s%d", first ? "" : ", ",
298                                                         *(idxDict[d->getId()]));
299                                         first = FALSE;
300                                 }
301                                 fprintf(f, "</font></td>\n");
302                         }
303                         else if (*it == "note")
304                         {
305                                 fprintf(f, "<td bgcolor=\""COL_DEFAULT"\" rowspan=\"2\">"
306                                                 "<font size=\"-2\">%s</font></td>\n",
307                                                 htmlFilter(t->getNote()).latin1());
308                         }
309                         else if (*it == "daily")
310                         {
311                                 fprintf(f, "<td bgcolor=\""COL_HEADER"\"><font size=\"-4\">"
312                                                 "Plan</font></td>");
313                                 const int oneDay = 60 * 60 * 24;
314                                 for (time_t day = htmlTaskReportStart;
315                                          day < htmlTaskReportEnd; day += oneDay)
316                                 {
317                                         double load = t->getLoadOnDay(day);
318                                         QString bgCol = COL_DEFAULT;
319                                         if (t->isMilestone() && t->isActiveToday(day))
320                                                 bgCol = COL_MILESTONE;
321                                         else if (isWeekend(day))
322                                                 bgCol = COL_WEEKEND;
323                                         else if (t->isActiveToday(day))
324                                                 bgCol = COL_BOOKED;
325                                         if (load > 0.0)
326                                                 fprintf(f,
327                                                                 "<td bgcolor=\"%s\"><font size=\"-3\">%3.1f</font></td>",
328                                                                 bgCol.latin1(), load);
329                                         else
330                                                 fprintf(f, "<td bgcolor=\"%s\"></td>",
331                                                                 bgCol.latin1());
332                                 }
333                         }
334                 }
335                 fprintf(f, "</tr>");
336
337                 fprintf(f, "<tr>\n");
338                 for (QStringList::Iterator it = htmlTaskReportColumns.begin();
339                          it != htmlTaskReportColumns.end(); ++it )
340                 {
341                         if (*it == "daily")
342                         {
343                                 fprintf(f, "<td bgcolor=\""COL_HEADER"\"><font size=\"-4\">"
344                                                 "Actual</font></td>");
345                                 const int oneDay = 60 * 60 * 24;
346                                 for (time_t day = htmlTaskReportStart;
347                                          day < htmlTaskReportEnd; day += oneDay)
348                                         fprintf(f, "<td bgcolor=\"%s\"></td>",
349                                                         isWeekend(day) ? COL_WEEKEND : COL_DEFAULT);
350                         }
351                 }
352                 fprintf(f, "</tr>\n");
353         }
354         fprintf(f, "</table>");
355         reportHTMLFooter(f);
356
357         fclose(f);
358         return TRUE;
359 }
360
361 bool
362 Project::reportHTMLResourceList()
363 {
364         /* TODO: This is pretty much a quick hack. Using FILE is bad. latin1()
365          * does not work for non ASCII characters! */
366         if (htmlResourceReport == "")
367                 return TRUE;
368         FILE* f;
369         if ((f = fopen(htmlResourceReport, "w")) == NULL)
370         {
371                 fprintf(stderr, "Cannot open report file %s!\n",
372                                 htmlResourceReport.latin1());
373                 return FALSE;
374         }
375         reportHTMLHeader(f);
376         fprintf(f, "<table border=\"1\" cellpadding=\"3\">\n");
377         fprintf(f, "<tr>");
378         fprintf(f, "<td rowspan=\"2\"></td>");
379         htmlMonthHeader(f, htmlResourceReportStart, htmlResourceReportEnd); 
380         fprintf(f, "</tr>\n");
381
382         fprintf(f, "<tr>");
383         htmlDayHeader(f, htmlResourceReportStart, htmlResourceReportEnd);
384         fprintf(f, "</tr>\n");
385
386         const int oneDay = 60 * 60 * 24;
387         for (Resource* r = resourceList.first(); r != 0;
388                  r = resourceList.next())
389         {
390                 fprintf(f, "<tr>");
391                 fprintf(f, "<td nowrap><b>%s</b></td>", r->getName().latin1());
392                 for (time_t day = htmlResourceReportStart; day < htmlResourceReportEnd;
393                          day += oneDay)
394                 {
395                         double load = r->getLoadOnDay(day);
396                         QString s = "";
397                         if (load > 0.0)
398                                 s.sprintf("%3.1f", load);
399                         fprintf(f, "<td %s>%s</td>",
400                                         isWeekend(day) ? "bgcolor=\""COL_WEEKEND"\"" : "",
401                                         s.latin1());
402                 }
403                 fprintf(f, "</tr>\n");
404
405                 for (Task* t = taskList.first(); t != 0; t = taskList.next())
406                         if (r->isAssignedTo(t))
407                         {
408                                 fprintf(f, "<tr>");
409                                 fprintf(f, "<td nowrap>&nbsp;&nbsp;&nbsp;&nbsp;<font size=\"-1\">%s</font></td>",
410                                                 t->getName().latin1());
411                                 for (time_t day = htmlResourceReportStart;
412                                          day < htmlResourceReportEnd; day += oneDay)
413                                 {
414                                         double load = r->getLoadOnDay(day, t);
415                                         QString s = "";
416                                         if (load > 0.0)
417                                                 s.sprintf("%3.1f", load);
418                                         fprintf(f, "<td %s><font size=\"-1\">%s</font></td>",
419                                                         isWeekend(day) ? "bgcolor=\""COL_WEEKEND"\"" : "",
420                                                         s.latin1());
421                                 }
422                                 fprintf(f, "</tr>\n");
423                         }
424         }
425
426         fprintf(f, "</table>");
427         reportHTMLFooter(f);
428
429         fclose(f);
430         return TRUE;
431 }
432
433 void
434 Project::htmlDayHeader(FILE* f, time_t s, time_t e)
435 {
436         const int oneDay = 60 * 60 * 24;
437
438         for (time_t day = s; day < e; day += oneDay)
439         {
440                 int dom = dayOfMonth(day);
441                 fprintf(f, "<td bgcolor=\"%s\"><font size=\"-2\">"
442                                 "&nbsp;%s%d</font></td>",
443                                 isWeekend(day) ? COL_WEEKEND : COL_HEADER,
444                                 dom < 10 ? "&nbsp;" : "", dom);
445         }
446 }
447
448 void
449 Project::htmlMonthHeader(FILE* f, time_t s, time_t e)
450 {
451         const int oneDay = 60 * 60 * 24;
452
453         for (time_t day = s; day < e; day += oneDay * daysLeftInMonth(day))
454         {
455                 int left = daysLeftInMonth(day);
456                 if (left > (e - day) / oneDay)
457                         left = (e - day) / oneDay;
458                 fprintf(f, "<td bgcolor=\""COL_HEADER"\" colspan=\"%d\" "
459                                 "align=\"center\"><font size=\"+2\"><b>%s</b></font></td>",
460                                 left, monthAndYear(day));
461         }
462 }
463
464 void
465 Project::printText()
466 {
467         printf("ID  Task Name    Start      End\n");
468         int i = 0;
469         for (Task* t = taskList.first(); t != 0; t = taskList.next(), ++i)
470         {
471                 printf("%2d. %-12s %10s %10s\n",
472                            i, t->getName().latin1(),
473                            t->getStartISO().latin1(), t->getEndISO().latin1());
474         }
475
476         printf("\n\n");
477         resourceList.printText();
478 }
479
480 int
481 Project::unscheduledTasks()
482 {
483         int cntr = 0;
484         for (Task* t = taskList.first(); t != 0; t = taskList.next())
485                 if (!t->isScheduled())
486                         cntr++;
487
488         return cntr;
489 }
490
491 bool
492 Project::checkSchedule()
493 {
494         for (Task* t = taskList.first(); t != 0; t = taskList.next())
495                 if (!t->scheduleOK())
496                         return FALSE;
497
498         return TRUE;
499 }
500
501 QString
502 Project::htmlFilter(const QString& s)
503 {
504         // TODO: All special characters must be html-ized.
505         return s;
506 }