OSDN Git Service

2cb35e39e6750b6fbe98cc4dcb542aeb9644032d
[tjqt4port/tj2qt4.git] / TaskJugglerUI / TjReport.cpp
1 /*
2  * The TaskJuggler Project Management Software
3  *
4  * Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007
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 "TjReport.h"
15
16 #include <assert.h>
17
18 #include <qsplitter.h>
19 #include <qlayout.h>
20 #include <qheader.h>
21 #include <qcanvas.h>
22 #include <qdatetime.h>
23 #include <qtimer.h>
24 #include <qpopupmenu.h>
25 #include <qpaintdevicemetrics.h>
26
27 #include <klistview.h>
28 #include <klocale.h>
29 #include <kdebug.h>
30 #include <kiconloader.h>
31 #include <kapp.h>
32 #include <kcursor.h>
33 #include <kglobal.h>
34 #include <kglobalsettings.h>
35 #include <ktextbrowser.h>
36 #include <krun.h>
37 #include <kmessagebox.h>
38 #include <klistviewsearchline.h>
39
40 #include "Project.h"
41 #include "Task.h"
42 #include "Resource.h"
43 #include "Utility.h"
44 #include "ExpressionTree.h"
45 #include "Report.h"
46 #include "TableColumnFormat.h"
47 #include "TextAttribute.h"
48 #include "ReferenceAttribute.h"
49 #include "QtTaskReport.h"
50 #include "QtResourceReport.h"
51 #include "RichTextDisplay.h"
52 #include "TjPrintReport.h"
53 #include "TjGanttChart.h"
54 #include "TjObjPosTable.h"
55 #include "KPrinterWrapper.h"
56 #include "UsageLimits.h"
57 #include "ReportManager.h"
58 #include "ReportController.h"
59 #include "kdateedit.h"
60
61 TjReport::TjReport(QWidget* p, ReportManager* m, Report* rDef,
62                    const QString& n)
63     : TjUIReportBase(p, m, rDef, n)
64 {
65     loadingProject = false;
66     scaleMode = TjGanttChart::fitSize;
67
68     QVBoxLayout* vl = new QVBoxLayout(this, 0, 0);
69     reportFrame = new QWidget(this);
70     reportFrame->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
71
72     QHBoxLayout* hl = new QHBoxLayout(reportFrame, 0, 0);
73     splitter = new QSplitter(Horizontal, reportFrame);
74
75     listView = new KListView(splitter);
76     listView->setRootIsDecorated(true);
77     listView->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
78     listView->setAllColumnsShowFocus(true);
79     // The sorting does not work yet properly.
80     listView->header()->setClickEnabled(false);
81     listView->setItemMargin(2);
82
83     canvasFrame = new QWidget(splitter);
84     QVBoxLayout* vlChart = new QVBoxLayout(canvasFrame, 0, 0);
85     canvasFrame->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
86
87     ganttChart = new TjGanttChart(reportFrame);
88     objPosTable = 0;
89
90     ganttHeaderView = new QCanvasView(ganttChart->getHeaderCanvas(),
91                                       canvasFrame);
92     ganttHeaderView->setHScrollBarMode(QScrollView::AlwaysOff);
93     ganttHeaderView->setVScrollBarMode(QScrollView::AlwaysOff);
94
95     ganttChartView = new QCanvasView(ganttChart->getChartCanvas(),
96                                      canvasFrame);
97     ganttChartView->setVScrollBarMode(QScrollView::AlwaysOff);
98
99     reportController = new ReportController(this);
100     reportController->reportSearch->setListView(listView);
101     reportController->reportStart->setDate(time2qdate(rDef->getStart()));
102     reportController->reportEnd->setDate(time2qdate(rDef->getEnd()));
103
104     vl->addWidget(reportFrame);
105     vl->addWidget(reportController);
106     vlChart->addWidget(ganttHeaderView);
107     vlChart->addWidget(ganttChartView);
108     hl->addWidget(splitter);
109
110     statusBarUpdateTimer = delayTimer = 0;
111
112     connect(listView, SIGNAL(selectionChanged()),
113             this, SLOT(regenerateChart()));
114     connect(listView, SIGNAL(expanded(QListViewItem*)),
115             this, SLOT(expandReportItem(QListViewItem*)));
116     connect(listView, SIGNAL(collapsed(QListViewItem*)),
117             this, SLOT(collapsReportItem(QListViewItem*)));
118     connect(listView, SIGNAL(clicked(QListViewItem*, const QPoint&, int)),
119             this, SLOT(listClicked(QListViewItem*, const QPoint&, int)));
120     connect(listView,
121             SIGNAL(rightButtonPressed(QListViewItem*, const QPoint&, int)),
122             this, SLOT(doPopupMenu(QListViewItem*, const QPoint&, int)));
123     connect(listView->header(), SIGNAL(clicked(int)),
124             this, SLOT(listHeaderClicked(int)));
125     connect(ganttChartView, SIGNAL(contentsMoving(int, int)),
126             this, SLOT(syncVSlidersGantt2List(int, int)));
127     connect(listView, SIGNAL(contentsMoving(int, int)),
128             this, SLOT(syncVSlidersList2Gantt(int, int)));
129     connect(reportController->reportSearch, SIGNAL(textChanged(const QString&)),
130             this, SLOT(reportSearchTriggered(const QString&)));
131     connect(reportController->reportStart, SIGNAL(dateChanged(const QDate&)),
132             this, SLOT(setReportStart(const QDate&)));
133     connect(reportController->reportEnd, SIGNAL(dateChanged(const QDate&)),
134             this, SLOT(setReportEnd(const QDate&)));
135
136     indexColumns.insert("index");
137     indexColumns.insert("hierarchindex");
138     indexColumns.insert("hierarchno");
139     indexColumns.insert("no");
140     indexColumns.insert("seqno");
141     indexColumns.insert("name");
142
143     specialColumns.insert("daily");
144     specialColumns.insert("weekly");
145     specialColumns.insert("monthly");
146     specialColumns.insert("quarterly");
147     specialColumns.insert("yearly");
148 }
149
150 TjReport::~TjReport()
151 {
152     delete ganttChart;
153     delete objPosTable;
154     delete statusBarUpdateTimer;
155 }
156
157 void
158 TjReport::print()
159 {
160     KPrinter* printer = new KPrinter;
161     TjPrintReport* tjpr = 0;
162
163     printer->setFullPage(true);
164     printer->setResolution(300);
165     printer->setCreator(QString("TaskJuggler %1 - visit %2")
166                         .arg(VERSION).arg(TJURL));
167     if (!printer->setup(this, i18n("Print %1").arg(report->getFileName())))
168         goto done;
169
170     /* This is a hack to workaround the problem that the KPrinter settings
171      * not transferred to the QPrinter object when not printing to a file. */
172     ((KPrinterWrapper*) printer)->preparePrinting();
173
174     if ((tjpr = this->newPrintReport(printer)) == 0)
175         goto done;
176     tjpr->initialize();
177     tjpr->generate();
178
179     int xPages, yPages;
180     tjpr->getNumberOfPages(xPages, yPages);
181     if (!tjpr->beginPrinting())
182         goto done;
183
184     // This block avoids a compile error due to gotos crossing 'first'.
185     {
186         bool first = true;
187         for (int y = 0; y < yPages; ++y)
188             for (int x = 0; x < xPages; ++x)
189             {
190                 if (first)
191                     first = false;
192                 else
193                     printer->newPage();
194                 tjpr->printReportPage(x, y);
195             }
196         tjpr->endPrinting();
197     }
198
199 done:
200     delete tjpr;
201     delete printer;
202 }
203
204 void
205 TjReport::setFocus()
206 {
207     listView->setFocus();
208 }
209
210 bool
211 TjReport::event(QEvent* ev)
212 {
213     // Regenerate the chart in case of a palette change.
214     if (ev->type() == QEvent::ApplicationPaletteChange)
215     {
216         setGanttChartColors();
217         regenerateChart();
218     }
219     else if (ev->type() == QEvent::MouseButtonPress)
220         handleMouseEvent(static_cast<QMouseEvent*>(ev));
221
222     return QWidget::event(ev);
223 }
224
225 bool
226 TjReport::generateReport()
227 {
228     setLoadingProject(true);
229
230     setCursor(KCursor::waitCursor());
231     if (!this->generateList())
232     {
233         setLoadingProject(false);
234         setCursor(KCursor::arrowCursor());
235         return false;
236     }
237     setLoadingProject(false);
238     setCursor(KCursor::arrowCursor());
239
240     /* The first time we generate the report, the window has not been fully
241      * layouted yet. So we can't set the splitter to a good size and generate
242      * the gantt report immediately. We use a 200ms timer to delay the
243      * rendering. Hopefully by then the window has been layouted properly. */
244     triggerChartRegeneration(200);
245
246     delete statusBarUpdateTimer;
247     statusBarUpdateTimer = new QTimer(this);
248     connect(statusBarUpdateTimer, SIGNAL(timeout()),
249             this, SLOT(updateStatusBar()));
250     statusBarUpdateTimer->start(500, false);
251
252     return true;
253 }
254
255 void
256 TjReport::triggerChartRegeneration(int msDelay)
257 {
258     if (delayTimer == 0)
259     {
260         delayTimer = new QTimer(this);
261         connect(delayTimer, SIGNAL(timeout()),
262                 this, SLOT(regenerateChart()));
263     }
264     delayTimer->start(msDelay, true);
265 }
266
267 void
268 TjReport::regenerateChart()
269 {
270     delete delayTimer;
271     delayTimer = 0;
272
273     setCursor(KCursor::waitCursor());
274
275     prepareChart();
276
277     // When we are here, we have rendered the widgets at least once. So we can
278     // turn off manual mode.
279     scaleMode = TjGanttChart::manual;
280
281     ganttChart->getHeaderCanvas()->update();
282     ganttChart->getChartCanvas()->update();
283
284     setCursor(KCursor::arrowCursor());
285 }
286
287 void
288 TjReport::generateTaskListLine(const QtReportElement* reportElement,
289                                const Task* t, KListViewItem* lvi,
290                                const Resource* r)
291 {
292     assert(reportElement != 0);
293     assert(t != 0);
294     assert(lvi != 0);
295
296     // Skip the first two columns. They contain the hardwired task name and the
297     // sort index column.
298     int column = 2;
299     for (QPtrListIterator<TableColumnInfo>
300          ci = reportElement->getColumnsIterator(); *ci; ++ci, ++column)
301     {
302         /* The name and indices columns are automatically added as first
303          * columns, so we will just ignore them if the user has requested them
304          * as well. Calendar and chart columns get special treatment as well. */
305         if (indexColumns.find((*ci)->getName()) != indexColumns.end() ||
306             specialColumns.find((*ci)->getName()) != specialColumns.end() ||
307             (*ci)->getName() == "chart")
308         {
309             column--;
310             continue;
311         }
312
313         QString cellText;
314         QPixmap icon;
315
316         const TableColumnFormat* tcf =
317             reportElement->getColumnFormat((*ci)->getName());
318
319         if ((*ci)->getName() == "completed")
320         {
321             double calcedCompletionDegree =
322                 t->getCalcedCompletionDegree(scenario);
323             double providedCompletionDegree =
324                 t->getCompletionDegree(scenario);
325
326             if (calcedCompletionDegree < 0)
327             {
328                 if (calcedCompletionDegree == providedCompletionDegree)
329                 {
330                     cellText = i18n("in progress");
331                 }
332                 else
333                 {
334                     cellText = QString(i18n("%1% (in progress)"))
335                         .arg((int) providedCompletionDegree);
336                 }
337             }
338             else
339             {
340                 if (calcedCompletionDegree == providedCompletionDegree)
341                 {
342                     cellText = QString("%1%")
343                         .arg((int) providedCompletionDegree);
344                 }
345                 else
346                 {
347                     cellText = QString("%1% (%2%)")
348                             .arg((int) providedCompletionDegree)
349                             .arg((int) calcedCompletionDegree);
350                 }
351             }
352         }
353         else if ((*ci)->getName() == "completedeffort")
354         {
355             double val = 0.0;
356             if (!r && t->isLeaf())
357             {
358                 // Task line, no resource.
359                 val = t->getCompletedLoad(scenario);
360             }
361             else if (t && t->isLeaf())
362             {
363                 // Task line, nested into a resource
364                 const Project* project = report->getProject();
365                 time_t now = project->getNow();
366                 if (now < project->getStart())
367                     now = project->getStart();
368                 if (now > project->getEnd())
369                     now = project->getEnd();
370                 Interval iv = Interval(project->getStart(), now);
371                 val = t->getLoad(scenario, iv, r);
372             }
373             cellText = indent
374                 (reportElement->scaledLoad(val, tcf->realFormat),
375                  lvi, tcf->getHAlign() == TableColumnFormat::right);
376         }
377         else if ((*ci)->getName() == "cost")
378         {
379             double val = t->getCredits
380                 (scenario, Interval(reportElement->getStart(),
381                                     reportElement->getEnd()), Cost, r);
382             cellText = indent(tcf->realFormat.format(val, false),
383                               lvi, tcf->getHAlign() ==
384                               TableColumnFormat::right);
385         }
386         else if ((*ci)->getName() == "criticalness")
387         {
388             cellText = indent(QString().sprintf("%f",
389                                                 t->getCriticalness(scenario)),
390                               lvi, tcf->getHAlign() ==
391                               TableColumnFormat::right);
392         }
393         else if ((*ci)->getName() == "depends")
394         {
395             for (TaskListIterator it(t->getPreviousIterator()); *it != 0; ++it)
396             {
397                 if (!cellText.isEmpty())
398                     cellText += ", ";
399                 cellText += (*it)->getId();
400             }
401         }
402         else if ((*ci)->getName() == "duration")
403             cellText = reportElement->scaledDuration
404                 (t->getCalcDuration(scenario), tcf->realFormat);
405         else if ((*ci)->getName() == "effort")
406         {
407             double val = 0.0;
408             val = t->getLoad(scenario, Interval(t->getStart(scenario),
409                                                 t->getEnd(scenario)), r);
410             cellText = indent
411                 (reportElement->scaledLoad(val, tcf->realFormat),
412                  lvi, tcf->getHAlign() == TableColumnFormat::right);
413         }
414         else if ((*ci)->getName() == "end")
415             cellText = time2user(t->getEnd(scenario) + 1,
416                                  reportElement->getTimeFormat());
417         else if ((*ci)->getName() == "endbuffer")
418             cellText.sprintf("%3.0f", t->getEndBuffer(scenario));
419         else if ((*ci)->getName() == "endbufferstart")
420             cellText = time2user(t->getEndBufferStart(scenario),
421                                  reportElement->getTimeFormat());
422         else if ((*ci)->getName() == "follows")
423         {
424             for (TaskListIterator it(t->getFollowersIterator()); *it != 0; ++it)
425             {
426                 if (!cellText.isEmpty())
427                     cellText += ", ";
428                 cellText += (*it)->getId();
429             }
430         }
431         else if ((*ci)->getName() == "id")
432             cellText = t->getId();
433         else if ((*ci)->getName() == "maxend")
434             cellText = time2user(t->getMaxEnd(scenario),
435                                  reportElement->getTimeFormat());
436         else if ((*ci)->getName() == "maxstart")
437             cellText = time2user(t->getMaxStart(scenario),
438                                  reportElement->getTimeFormat());
439         else if ((*ci)->getName() == "minend")
440             cellText = time2user(t->getMinEnd(scenario),
441                                  reportElement->getTimeFormat());
442         else if ((*ci)->getName() == "minstart")
443             cellText = time2user(t->getMinStart(scenario),
444                                  reportElement->getTimeFormat());
445         else if ((*ci)->getName() == "note" && !t->getNote().isEmpty())
446         {
447             if (t->getNote().length() > 25 || isRichText(t->getNote()))
448                 icon = KGlobal::iconLoader()->
449                     loadIcon("document", KIcon::Small);
450             else
451                 cellText = t->getNote();
452         }
453         else if ((*ci)->getName() == "pathcriticalness")
454             cellText = indent(QString().sprintf
455                               ("%f", t->getPathCriticalness(scenario)),
456                               lvi, tcf->getHAlign() ==
457                               TableColumnFormat::right);
458         else if ((*ci)->getName() == "priority")
459             cellText = indent(QString().sprintf("%d", t->getPriority()),
460                               lvi, tcf->getHAlign() ==
461                               TableColumnFormat::right);
462         else if ((*ci)->getName() == "projectid")
463             cellText = t->getProjectId() + " (" +
464                 reportElement->getReport()->getProject()->getIdIndex
465                 (t->getProjectId()) + ")";
466         else if ((*ci)->getName() == "profit")
467         {
468             double val = t->getCredits
469                 (scenario, Interval(reportElement->getStart(),
470                                     reportElement->getEnd()), Revenue, r) -
471                 t->getCredits
472                 (scenario, Interval(reportElement->getStart(),
473                                     reportElement->getEnd()), Cost, r);
474             cellText = indent(tcf->realFormat.format(val, false),
475                               lvi, tcf->getHAlign() ==
476                               TableColumnFormat::right);
477         }
478         else if ((*ci)->getName() == "remainingeffort")
479         {
480             double val = 0.0;
481             if (!r && t->isLeaf())
482             {
483                 // Task line, no resource.
484                 val = t->getRemainingLoad(scenario);
485             }
486             else if (t && t->isLeaf())
487             {
488                 // Task line, nested into a resource
489                 const Project* project = report->getProject();
490                 time_t now = project->getNow();
491                 if (now < project->getStart())
492                     now = project->getStart();
493                 if (now > project->getEnd())
494                     now = project->getEnd();
495                 Interval iv = Interval(now, project->getEnd());
496                 val = t->getLoad(scenario, iv, r);
497             }
498             cellText = indent
499                 (reportElement->scaledLoad(val, tcf->realFormat),
500                  lvi, tcf->getHAlign() == TableColumnFormat::right);
501         }
502         else if ((*ci)->getName() == "resources")
503         {
504             for (ResourceListIterator rli
505                  (t->getBookedResourcesIterator(scenario)); *rli != 0; ++rli)
506             {
507                 if (!cellText.isEmpty())
508                     cellText += ", ";
509
510                 cellText += (*rli)->getName();
511             }
512         }
513         else if ((*ci)->getName() == "responsible")
514         {
515             if (t->getResponsible())
516                 cellText = t->getResponsible()->getName();
517         }
518         else if ((*ci)->getName() == "revenue")
519         {
520             double val = t->getCredits
521                 (scenario, Interval(reportElement->getStart(),
522                                     reportElement->getEnd()), Revenue, r);
523             cellText = indent(tcf->realFormat.format(val, false),
524                               lvi, tcf->getHAlign() ==
525                               TableColumnFormat::right);
526         }
527         else if ((*ci)->getName() == "scheduling")
528             cellText = t->getSchedulingText();
529         else if ((*ci)->getName() == "start")
530             cellText = time2user(t->getStart(scenario),
531                                  reportElement->getTimeFormat());
532         else if ((*ci)->getName() == "startbuffer")
533             cellText.sprintf("%3.0f", t->getStartBuffer(scenario));
534         else if ((*ci)->getName() == "startbufferend")
535             cellText = time2user(t->getStartBufferEnd(scenario),
536                                  reportElement->getTimeFormat());
537         else if ((*ci)->getName() == "status")
538         {
539             cellText = t->getStatusText(scenario);
540         }
541         else if ((*ci)->getName() == "statusnote")
542         {
543             if (t->getStatusNote(scenario).length() > 25 ||
544                 isRichText(t->getStatusNote(scenario)))
545                 icon = KGlobal::iconLoader()->
546                     loadIcon("document", KIcon::Small);
547             else
548                 cellText = t->getStatusNote(scenario);
549         }
550         else
551             generateCustomAttribute(t, (*ci)->getName(), cellText, icon);
552
553         lvi->setText(column, cellText);
554         if (!icon.isNull())
555             lvi->setPixmap(column, icon);
556     }
557 }
558
559 void
560 TjReport::generateResourceListLine(const QtReportElement* reportElement,
561                                    Resource* r, KListViewItem* lvi,
562                                    const Task* t)
563 {
564     assert(reportElement != 0);
565     assert(r != 0);
566     assert(lvi != 0);
567
568     // Skip the first colum. It contains the hardwired resource name.
569     int column = 2;
570     for (QPtrListIterator<TableColumnInfo>
571          ci = reportElement->getColumnsIterator(); *ci; ++ci, ++column)
572     {
573         /* The name and indices columns are automatically added as first
574          * columns, so we will just ignore them if the user has requested them
575          * as well. Calendar and chart columns get special treatment as well. */
576         if (indexColumns.find((*ci)->getName()) != indexColumns.end() ||
577             specialColumns.find((*ci)->getName()) != specialColumns.end() ||
578             (*ci)->getName() == "chart")
579         {
580             column--;
581             continue;
582         }
583
584         QString cellText;
585         QPixmap icon;
586         const TableColumnFormat* tcf =
587             reportElement->getColumnFormat((*ci)->getName());
588
589         if ((*ci)->getName() == "cost")
590         {
591             double val = r->getCredits
592                 (scenario, Interval(reportElement->getStart(),
593                                     reportElement->getEnd()), Cost, t);
594             cellText = indent(tcf->realFormat.format(val, false),
595                               lvi, tcf->getHAlign() ==
596                               TableColumnFormat::right);
597         }
598         else if ((*ci)->getName() == "efficiency")
599         {
600             cellText = QString().sprintf("%.1lf", r->getEfficiency());
601         }
602         else if ((*ci)->getName() == "effort")
603         {
604             double val = 0.0;
605             if (t)
606                 val = r->getEffectiveLoad
607                     (scenario, Interval(t->getStart(scenario),
608                                         t->getEnd(scenario)),
609                      AllAccounts, t);
610             else
611                 val = r->getEffectiveLoad
612                     (scenario, Interval(reportElement->getStart(),
613                                         reportElement->getEnd()));
614
615             cellText = indent
616                 (reportElement->scaledLoad(val, tcf->realFormat), lvi,
617                  tcf->getHAlign() == TableColumnFormat::right);
618         }
619         else if ((*ci)->getName() == "freeload")
620         {
621             if (!t)
622             {
623                 double val = 0.0;
624                 val = r->getEffectiveFreeLoad
625                     (scenario, Interval(reportElement->getStart(),
626                                         reportElement->getEnd()));
627                 cellText = indent
628                     (reportElement->scaledLoad(val, tcf->realFormat), lvi,
629                      tcf->getHAlign() == TableColumnFormat::right);
630             }
631         }
632         else if ((*ci)->getName() == "id")
633         {
634             cellText = r->getFullId();
635         }
636         else if ((*ci)->getName() == "maxeffort")
637         {
638             const UsageLimits* limits = r->getLimits();
639             if (limits == 0)
640                 cellText = i18n("no Limits");
641             else
642             {
643                 int sg = report->getProject()->getScheduleGranularity();
644                 if (limits->getDailyMax() > 0)
645                     cellText = i18n("D: %1h").arg(limits->getDailyMax() *
646                                                sg / (60 * 60));
647                 if (limits->getWeeklyMax() > 0)
648                 {
649                     if (!cellText.isEmpty())
650                         cellText += ", ";
651                     cellText += i18n("W: %1h").arg(limits->getWeeklyMax() *
652                                                 sg / (60 * 60));
653                 }
654                 if (limits->getMonthlyMax() > 0)
655                 {
656                     if (!cellText.isEmpty())
657                         cellText += ", ";
658                     cellText += i18n("M: %1d").arg(limits->getMonthlyMax() *
659                                                       sg / (60 * 60 * 24));
660                 }
661             }
662         }
663         else if ((*ci)->getName() == "projectids")
664             cellText = r->getProjectIDs
665                 (scenario, Interval(reportElement->getStart(),
666                                     reportElement->getEnd()));
667         else if ((*ci)->getName() == "rate")
668         {
669             cellText = indent(tcf->realFormat.format(r->getRate(), false),
670                               lvi, tcf->getHAlign() ==
671                               TableColumnFormat::right);
672         }
673         else if ((*ci)->getName() == "revenue")
674         {
675             double val = r->getCredits
676                 (scenario, Interval(reportElement->getStart(),
677                                     reportElement->getEnd()), Revenue, t);
678             cellText = indent(tcf->realFormat.format(val, false),
679                               lvi, tcf->getHAlign() ==
680                               TableColumnFormat::right);
681         }
682         else if ((*ci)->getName() == "utilization")
683         {
684             if (!t)
685             {
686                 double load = r->getEffectiveLoad
687                     (scenario, Interval(reportElement->getStart(),
688                                         reportElement->getEnd()));
689                 double val;
690                 if (load <= 0.0)
691                     val = 0.0;
692                 else
693                 {
694                     double freeLoad = r->getEffectiveFreeLoad
695                         (scenario, Interval(reportElement->getStart(),
696                                             reportElement->getEnd()));
697                     val = 100.0 / (1.0 + (freeLoad / load));
698                 }
699                 cellText = indent(QString().sprintf("%.1f%%", val), lvi,
700                                   tcf->getHAlign() == TableColumnFormat::right);
701             }
702         }
703         else
704             generateCustomAttribute(r, (*ci)->getName(), cellText, icon);
705
706         lvi->setText(column, cellText);
707         if (!icon.isNull())
708             lvi->setPixmap(column, icon);
709     }
710 }
711
712 void
713 TjReport::generateCustomAttribute(const CoreAttributes* ca, const QString name,
714                                   QString& cellText, QPixmap& icon) const
715 {
716     // Handle custom attributes
717     const CustomAttribute* custAttr =
718         ca->getCustomAttribute(name);
719     if (custAttr)
720     {
721         switch (custAttr->getType())
722         {
723             case CAT_Undefined:
724                 break;
725             case CAT_Text:
726             {
727                 QString text =
728                     dynamic_cast<const TextAttribute*>(custAttr)->
729                     getText();
730                 if (text.length() > 25 || isRichText(text))
731                     icon = KGlobal::iconLoader()->
732                         loadIcon("document", KIcon::Small);
733                 else
734                     cellText = text;
735                 break;
736             }
737             case CAT_Reference:
738                 cellText =
739                     dynamic_cast<const
740                     ReferenceAttribute*>(custAttr)->getLabel();
741                 icon = KGlobal::iconLoader()->
742                     loadIcon("html", KIcon::Small);
743                 break;
744         }
745     }
746 }
747
748 void
749 TjReport::prepareChart()
750 {
751     /* The object position mapping table changes most likely with every
752      * re-generation. So we delete it and create a new one. */
753     delete objPosTable;
754     objPosTable = new TjObjPosTable;
755     TjObjPosTableEntry* selectedObject = 0;
756
757     for (std::map<const QString, KListViewItem*, ltQString>::iterator
758          lvit = ca2lviDict.begin(); lvit != ca2lviDict.end(); ++lvit)
759     {
760         KListViewItem* lvi = (*lvit).second;
761         if (!lvi || !lvi->isVisible())
762             continue;
763
764         // Find out if the list entry is visible at all.
765         const QListViewItem* p;
766         bool isVisible = true;
767         for (p = lvi->parent(); p; p = p->parent())
768             if (!p->isOpen())
769             {
770                 isVisible = false;
771                 break;
772             }
773         // If no, we ignore it.
774         if (!isVisible)
775             continue;
776
777         // Reconstruct the CoreAttributes pointers.
778         QStringList tokens = QStringList::split(":", (*lvit).first);
779         CoreAttributes* ca1 = 0;
780         CoreAttributes* ca2 = 0;
781         const Project* project = report->getProject();
782         if (tokens[0] == "t")
783         {
784             if (tokens[2].isEmpty())
785                 ca1 = project->getTask(tokens[1]);
786             else
787             {
788                 ca1 = project->getResource(tokens[1]);
789                 ca2 = project->getTask(tokens[2]);
790                 assert(ca2 != 0);
791             }
792         }
793         else
794         {
795             if (tokens[2].isEmpty())
796                 ca1 = project->getResource(tokens[1]);
797             else
798             {
799                 ca1 = project->getTask(tokens[1]);
800                 ca2 = project->getResource(tokens[2]);
801                 assert(ca2 != 0);
802             }
803         }
804         assert(ca1 != 0);
805         TjObjPosTableEntry* tableEntry =
806             objPosTable->addEntry(ca1, ca2, lvi->itemPos(), lvi->height(),
807                                   lvi->isAlternate());
808
809         if (lvi == listView->selectedItem())
810             selectedObject = tableEntry;
811     }
812
813     // Calculate some commenly used values;
814     headerHeight = listView->header()->height();
815     QListViewItem* lvi;
816     itemHeight = 0;
817     listHeight = 0;
818     for (lvi = listView->firstChild(); lvi; lvi = lvi->itemBelow())
819         if (lvi->isVisible())
820         {
821             if (lvi->height() > itemHeight)
822                 itemHeight = lvi->height();
823             listHeight = lvi->itemPos() + itemHeight - 1;
824         }
825
826     // Resize header canvas to new size.
827     ganttHeaderView->setFixedHeight(headerHeight);
828
829     ganttChart->setProjectAndReportData(getReportElement());
830     QValueList<int> sizes = splitter->sizes();
831     if (scaleMode == TjGanttChart::fitSize)
832     {
833         /* In fitSize mode we show 1/3 table and 2/3 gantt chart. Otherwise we
834          * just keep the current size of the splitter. */
835         if (showGantt)
836         {
837             sizes[0] = static_cast<int>(width() / 3.0);
838             sizes[1] = static_cast<int>(width() * 2.0/3.0);
839         }
840         else
841         {
842             sizes[0] = width();
843             sizes[1] = 0;
844         }
845         splitter->setSizes(sizes);
846     }
847
848     ganttChart->setSizes(objPosTable, headerHeight, listHeight,
849                          sizes[1] == 0 ? static_cast<int>(width() * 2.0/3.0) :
850                          sizes[1],
851                          itemHeight);
852     ganttChart->setSelection(selectedObject);
853     QPaintDeviceMetrics metrics(ganttChartView);
854     ganttChart->setDPI(metrics.logicalDpiX(), metrics.logicalDpiY());
855     setGanttChartColors();
856     ganttChart->setHeaderHeight(headerHeight);
857     ganttChart->generate(scaleMode);
858     updateZoomSelector();
859
860     canvasFrame->setMaximumWidth(ganttChart->getWidth());
861 }
862
863 void
864 TjReport::generateListHeader(const QString& firstHeader, QtReportElement* tab)
865 {
866     // The first column is always the Task/Resource column
867     listView->addColumn(firstHeader + "\n");
868     // The second column is the sort index. It is always hidden.
869     listView->addColumn("sortIndex");
870     listView->setColumnWidthMode(1, QListView::Manual);
871     listView->hideColumn(1);
872     listView->setSortOrder(Qt::Ascending);
873     listView->setSortColumn(1);
874
875     lvCol2tci.clear();
876
877     showGantt = false;
878     int col = 2;
879     for (QPtrListIterator<TableColumnInfo>
880          ci = tab->getColumnsIterator(); *ci; ++ci, ++col)
881     {
882         /* The name and indices columns are automatically added as first
883          * columns, so we will just ignore them if the user has requested them
884          * as well. Calendar columns get special treatment as well. */
885         if ((*ci)->getName() == "chart")
886         {
887             showGantt = true;
888             col--;
889             continue;
890         }
891
892         if (indexColumns.find((*ci)->getName()) != indexColumns.end() ||
893             specialColumns.find((*ci)->getName()) != specialColumns.end())
894         {
895             col--;
896             continue;
897         }
898
899         /* Store a reference to the column info in the lvCol2tci map. */
900         lvCol2tci.insert(lvCol2tci.end(), ci);
901
902         const TableColumnFormat* tcf =
903             tab->getColumnFormat((*ci)->getName());
904         QString title = tcf->getTitle();
905         if (!(*ci)->getTitle().isEmpty())
906             title = (*ci)->getTitle();
907         listView->addColumn(title + "\n");
908         listView->setColumnAlignment(col, tcf->getHAlign());
909     }
910 }
911
912 void
913 TjReport::collapsReportItem(QListViewItem*)
914 {
915     if (loadingProject)
916         return;
917
918     regenerateChart();
919
920     syncVSlidersGantt2List(ganttChartView->contentsX(), listView->contentsY());
921 }
922
923 void
924 TjReport::expandReportItem(QListViewItem*)
925 {
926     if (loadingProject)
927         return;
928
929     regenerateChart();
930     syncVSlidersGantt2List(ganttChartView->contentsX(), listView->contentsY());
931 }
932
933 void
934 TjReport::listClicked(QListViewItem* lvi, const QPoint&, int column)
935 {
936     // The first column is always the name and the second column is the hidden
937     // sort index. Both are not in the TCI table. All clickable columns have
938     // an icon.
939     if (!lvi || column <= 1 || !lvi->pixmap(column))
940         return;
941
942     CoreAttributes* ca = lvi2caDict[QString().sprintf("%p", lvi)];
943     const TableColumnInfo* tci = lvCol2tci[column - 2];
944
945     if (ca->getType() == CA_Task &&
946         tci->getName() == "note" &&
947         !(dynamic_cast<Task*>(ca))->getNote().isEmpty())
948     {
949         Task* t = dynamic_cast<Task*>(ca);
950         // Open a new window that displays the note attached to the task.
951         RichTextDisplay* richTextDisplay =
952             new RichTextDisplay(topLevelWidget());
953         richTextDisplay->setCaption(i18n("Note for Task %1 (%2) - TaskJuggler")
954                                     .arg(t->getName()).arg(t->getId()));
955         richTextDisplay->textDisplay->setTextFormat(Qt::RichText);
956
957         richTextDisplay->textDisplay->setText(t->getNote());
958         richTextDisplay->show();
959     }
960     else if (ca->getType() == CA_Task &&
961              tci->getName() == "statusnote" &&
962              !(dynamic_cast<Task*>(ca))->getStatusNote(scenario).isEmpty())
963     {
964         Task* t = dynamic_cast<Task*>(ca);
965         // Open a new window that displays the note attached to the task.
966         RichTextDisplay* richTextDisplay =
967             new RichTextDisplay(topLevelWidget());
968         richTextDisplay->setCaption
969             (i18n("Status Note for Task %1 (%2) - TaskJuggler")
970              .arg(t->getName()).arg(t->getId()));
971         richTextDisplay->textDisplay->setTextFormat(Qt::RichText);
972
973         richTextDisplay->textDisplay->setText(t->getStatusNote(scenario));
974         richTextDisplay->show();
975     }
976     else if (ca->getCustomAttribute(tci->getName()))
977     {
978         switch (ca->getCustomAttribute(tci->getName())->getType())
979         {
980             case CAT_Undefined:
981                 break;
982             case CAT_Text:
983             {
984                 const TextAttribute* textAttr =
985                     dynamic_cast<const TextAttribute*>
986                     (ca->getCustomAttribute(tci->getName()));
987                 RichTextDisplay* richTextDisplay =
988                     new RichTextDisplay(topLevelWidget());
989                 richTextDisplay->setCaption
990                     (i18n("%1 for %2 %3 (%4) - TaskJuggler")
991                      .arg(tci->getName())
992                      .arg(ca->getType() == CA_Task ? i18n("Task") :
993                           i18n("Resource"))
994                      .arg(ca->getName())
995                      .arg(ca->getId()));
996                 richTextDisplay->textDisplay->setTextFormat(Qt::RichText);
997
998                 richTextDisplay->textDisplay->setText(textAttr->getText());
999                 richTextDisplay->show();
1000                 break;
1001             }
1002             case CAT_Reference:
1003             {
1004                 const ReferenceAttribute* refAttr =
1005                     dynamic_cast<const ReferenceAttribute*>
1006                     (ca->getCustomAttribute(tci->getName()));
1007                 KRun::runURL(KURL(refAttr->getURL()), "text/html");
1008                 break;
1009             }
1010         }
1011     }
1012 }
1013
1014 void
1015 TjReport::listHeaderClicked(int)
1016 {
1017     regenerateChart();
1018 }
1019
1020 void
1021 TjReport::doPopupMenu(QListViewItem* lvi, const QPoint& pos, int)
1022 {
1023     if (!lvi)
1024         return;
1025
1026     CoreAttributes* ca = lvi2caDict[QString().sprintf("%p", lvi)];
1027     QPopupMenu menu;
1028     if (ca->getType() == CA_Task)
1029     {
1030         Task* t = dynamic_cast<Task*>(ca);
1031
1032         menu.insertItem(i18n("&Edit Task"), 1);
1033         menu.insertItem(i18n("Show Task &Details"), 2);
1034         //menu.insertItem(i18n("&Zoom to fit Task"), 3);
1035         switch (menu.exec(pos))
1036         {
1037             case 1:
1038                 emit signalEditCoreAttributes(ca);
1039                 break;
1040             case 2:
1041                 showTaskDetails(t);
1042                 break;
1043             case 3:
1044                 break;
1045             default:
1046                 break;
1047         }
1048     }
1049     else
1050     {
1051         Resource* r = dynamic_cast<Resource*>(ca);
1052
1053         menu.insertItem(i18n("&Edit Resource"), 1);
1054         menu.insertItem(i18n("Show Resource &Details"), 2);
1055         switch (menu.exec(pos))
1056         {
1057             case 1:
1058                 emit signalEditCoreAttributes(ca);
1059                 break;
1060             case 2:
1061                 showResourceDetails(r);
1062                 break;
1063             default:
1064                 break;
1065         }
1066     }
1067 }
1068
1069 void
1070 TjReport::showTaskDetails(const Task* task)
1071 {
1072     RichTextDisplay* richTextDisplay = new RichTextDisplay(topLevelWidget());
1073     richTextDisplay->setCaption
1074         (i18n("Details of Task %1 (%2) - TaskJuggler")
1075          .arg(task->getName()).arg(task->getId()));
1076     richTextDisplay->textDisplay->setTextFormat(Qt::RichText);
1077
1078     QString text;
1079     text = i18n("<b>Task:</b> %1 (%2)<br/>")
1080         .arg(task->getName())
1081         .arg(task->getId());
1082
1083     if (!task->getNote().isEmpty())
1084     {
1085         if (!text.isEmpty())
1086             text += "<hr/>";
1087         text += i18n("<b>Note:</b> %1<br/>").arg(task->getNote());
1088     }
1089
1090     if (task->isMilestone())
1091     {
1092         if (!text.isEmpty())
1093             text += "<hr/>";
1094         text += i18n("<b>Date:</b> %1<br/>")
1095             .arg(time2tjp(task->getStart(scenario)));
1096     }
1097     else
1098     {
1099         if (!text.isEmpty())
1100             text += "<hr/>";
1101         text += i18n("<b>Start:</b> %1<br/>"
1102                      "<b>End:</b> %2<br/>"
1103                      "<b>Status:</b> %3<br/>")
1104             .arg(time2tjp(task->getStart(scenario)))
1105             .arg(time2tjp(task->getEnd(scenario) + 1))
1106             .arg(task->getStatusText(scenario));
1107
1108         if (task->getEffort(scenario) > 0.0)
1109         {
1110             const ReportElement* reportElement = getReportElement();
1111             double completion = task->getCompletionDegree(scenario) / 100.0;
1112             text += i18n("<hr/><b>Effort:</b> %1<br/>")
1113                     .arg(reportElement->scaledLoad
1114                          (task->getEffort(scenario), report->getNumberFormat(),
1115                           true, false));
1116             if (completion < 0.0)
1117             {
1118                 text += i18n("<b>Completion degree:</b> in progress<br/>");
1119             }
1120             else if (completion == 0.0)
1121             {
1122                 text += i18n("<b>Completion degree:</b> 0%<br/>");
1123             }
1124             else if (completion >= 1.0)
1125             {
1126                 text += i18n("<b>Completion degree:</b> 100%<br/>");
1127             }
1128             else
1129             {
1130                 text += i18n("<b>Completion degree:</b> %1%<br/>"
1131                              "<b>Done effort:</b> %2<br/>"
1132                              "<b>Remaining effort:</b> %3<br/>")
1133                     .arg(task->getCompletionDegree(scenario))
1134                     .arg(reportElement->scaledLoad
1135                          (task->getEffort(scenario) * completion,
1136                           report->getNumberFormat(), true, false))
1137                     .arg(reportElement->scaledLoad
1138                          (task->getEffort(scenario) * (1.0 - completion),
1139                           report->getNumberFormat(), true, false));
1140             }
1141         }
1142
1143         if (!task->getStatusNote(scenario).isEmpty())
1144             text += i18n("<b>Note:</b> %1<br/>")
1145                 .arg(task->getStatusNote(scenario));
1146     }
1147
1148     QString predecessors;
1149     for (TaskListIterator tli(task->getPreviousIterator()); *tli; ++tli)
1150         predecessors += "<li>" + (*tli)->getName() + " (" + (*tli)->getId() +
1151             ")</li>";
1152
1153     QString successors;
1154     for (TaskListIterator tli(task->getFollowersIterator()); *tli; ++tli)
1155         successors += "<li>" + (*tli)->getName() + " (" + (*tli)->getId() +
1156             ")<li/>";
1157
1158     if (!predecessors.isEmpty() || !successors.isEmpty())
1159     {
1160         text += "<hr/>";
1161         if (!predecessors.isEmpty())
1162             text += i18n("<b>Predecessors:</b><ul>%1</ul><br/>")
1163                 .arg(predecessors);
1164         if (!successors.isEmpty())
1165             text += i18n("<b>Successors:</b><ul>%1</ul><br/>").arg(successors);
1166     }
1167
1168     ResourceListIterator rli = task->getBookedResourcesIterator(scenario);
1169     if (*rli)
1170     {
1171         text += "<hr/><b>Allocated resources:</b><ul>";
1172         for (; *rli; ++rli)
1173             text += "<li>" + (*rli)->getName() + " (" + (*rli)->getId() +
1174                 ")</li>";
1175         text += "</ul>";
1176     }
1177
1178     text += generateRTCustomAttributes(task);
1179
1180     if (task->hasJournal())
1181     {
1182         text += "<hr/>";
1183         text += generateJournal(task->getJournalIterator());
1184     }
1185
1186     richTextDisplay->textDisplay->setText(text);
1187     richTextDisplay->show();
1188 }
1189
1190 void
1191 TjReport::showResourceDetails(Resource* resource)
1192 {
1193     RichTextDisplay* richTextDisplay = new RichTextDisplay(topLevelWidget());
1194     richTextDisplay->setCaption
1195         (i18n("Details of Resource %1 (%2) - TaskJuggler")
1196          .arg(resource->getName()).arg(resource->getFullId()));
1197     richTextDisplay->textDisplay->setTextFormat(Qt::RichText);
1198
1199     QString text;
1200     const ReportElement* reportElement = this->getReportElement();
1201     Interval iv = Interval(reportElement->getStart(),
1202                            reportElement->getEnd());
1203     double load = resource->getEffectiveLoad(scenario, iv);
1204     double freeLoad = resource->getEffectiveFreeLoad(scenario, iv);
1205
1206     text = i18n("<b>Resource:</b> %1 (%2)<br/>")
1207         .arg(resource->getName())
1208         .arg(resource->getId());
1209
1210     text += i18n("<hr><b>Effort:</b> %1 <b>Free Load:</b> %2 "
1211                 "<b>Utilization:</b> %3%")
1212         .arg(scaledLoad(load, numberFormat, true))
1213         .arg(scaledLoad(freeLoad, numberFormat, true))
1214         .arg((int) (load / (load + freeLoad) * 100.0));
1215
1216     if (resource->hasJournal())
1217     {
1218         if (!text.isEmpty())
1219             text += "<hr/>";
1220         text += generateJournal(resource->getJournalIterator());
1221     }
1222
1223     text += generateRTCustomAttributes(resource);
1224
1225     if (resource->hasJournal())
1226     {
1227         text += "<hr/>";
1228         text += generateJournal(resource->getJournalIterator());
1229     }
1230
1231     richTextDisplay->textDisplay->setText(text);
1232     richTextDisplay->show();
1233 }
1234
1235 QString
1236 TjReport::generateRTCustomAttributes(const CoreAttributes* ca) const
1237 {
1238     QDict<CustomAttribute> caDict = ca->getCustomAttributeDict();
1239
1240     QString text = "<hr/>";
1241     if (caDict.isEmpty())
1242         return text;
1243
1244     for (QDictIterator<CustomAttribute> cadi(caDict); cadi.current(); ++cadi)
1245     {
1246         text += "<b>" + cadi.currentKey() + ":</b> ";
1247         CustomAttribute* custAttr = cadi.current();
1248         switch (cadi.current()->getType())
1249         {
1250             case CAT_Reference:
1251             {
1252                 QString label = dynamic_cast<const
1253                     ReferenceAttribute*>(custAttr)->getLabel();
1254                 QString url = dynamic_cast<const
1255                     ReferenceAttribute*>(custAttr)->getURL();
1256                 text += "<a href=\"" + url + "\">" +
1257                     (label.isEmpty() ? url : label) + "</a>";
1258                 break;
1259             }
1260             case CAT_Text:
1261                 text += dynamic_cast<const TextAttribute*>(custAttr)->
1262                     getText();
1263                 break;
1264             case CAT_Undefined:
1265                 break;
1266         }
1267         text += "<br/>";
1268     }
1269
1270     return text;
1271 }
1272
1273 void
1274 TjReport::syncVSlidersGantt2List(int x, int y)
1275 {
1276     ganttHeaderView->setContentsPos(x, ganttHeaderView->contentsY());
1277     if (y != listView->contentsY())
1278     {
1279         // To prevent endless loops we need to disconnect the contentsMoving
1280         // signal temoraryly.
1281         disconnect(listView, SIGNAL(contentsMoving(int, int)),
1282                    this, SLOT(syncVSlidersList2Gantt(int, int)));
1283         listView->setContentsPos(listView->contentsX(), y);
1284         connect(listView, SIGNAL(contentsMoving(int, int)),
1285                 this, SLOT(syncVSlidersList2Gantt(int, int)));
1286     }
1287 }
1288
1289 void
1290 TjReport::syncVSlidersList2Gantt(int, int y)
1291 {
1292     if (y != ganttChartView->contentsY())
1293     {
1294         // To prevent endless loops we need to disconnect the contentsMoving
1295         // signal temoraryly.
1296         disconnect(ganttChartView, SIGNAL(contentsMoving(int, int)),
1297                    this, SLOT(syncVSlidersGantt2List(int, int)));
1298         ganttChartView->setContentsPos(ganttChartView->contentsX(), y);
1299         connect(ganttChartView, SIGNAL(contentsMoving(int, int)),
1300                 this, SLOT(syncVSlidersGantt2List(int, int)));
1301     }
1302 }
1303
1304 void
1305 TjReport::handleMouseEvent(const QMouseEvent* ev)
1306 {
1307     QPoint pos;
1308     QListViewItem* lvi = getChartItemBelowCursor(pos);
1309     if (!lvi)
1310         return;
1311
1312     if (ev->button() == Qt::LeftButton)
1313         listView->setSelected(lvi, true);
1314     else if (ev->button() == Qt::RightButton)
1315         doPopupMenu(lvi, QCursor::pos(), 0);
1316 }
1317
1318 void
1319 TjReport::updateStatusBar()
1320 {
1321     QPoint pos;
1322     QListViewItem* lvi = getChartItemBelowCursor(pos);
1323     if (!lvi)
1324     {
1325         emit signalChangeStatusBar("");
1326         return;
1327     }
1328
1329     CoreAttributes* ca = lvi2caDict[QString().sprintf("%p", lvi)];
1330     CoreAttributes* parent = lvi2ParentCaDict[QString().sprintf("%p", lvi)];
1331
1332     emit signalChangeStatusBar(this->generateStatusBarText(pos, ca, parent));
1333 }
1334
1335 void
1336 TjReport::reportSearchTriggered(const QString&)
1337 {
1338     triggerChartRegeneration(200);
1339 }
1340
1341 void
1342 TjReport::setReportStart(const QDate& d)
1343 {
1344     ReportElement* reportElement = this->getReportElement();
1345     time_t start = qdate2time(d);
1346
1347     bool clipped = false;
1348     if (start < date2time("2000-01-01"))
1349     {
1350         start = date2time("2000-01-01");
1351         clipped = true;
1352     }
1353     if (start > date2time("2030-01-01"))
1354     {
1355         start = date2time("2030-01-01");
1356         clipped = true;
1357     }
1358     if (start + (24 * 60 * 60) > reportElement->getEnd())
1359     {
1360         start = reportElement->getEnd() - (24 * 60 * 60);
1361         clipped = true;
1362     }
1363     if (clipped)
1364         reportController->reportStart->setDate(time2qdate(start));
1365
1366     reportElement->setStart(start);
1367     scaleMode = TjGanttChart::autoZoom;
1368     generateReport();
1369 }
1370
1371 void
1372 TjReport::setReportEnd(const QDate& d)
1373 {
1374     ReportElement* reportElement = this->getReportElement();
1375     time_t end = qdate2time(d);
1376
1377     bool clipped = false;
1378     if (end < date2time("2000-01-01"))
1379     {
1380         end = date2time("2000-01-01");
1381         clipped = true;
1382     }
1383     if (end > date2time("2030-01-01"))
1384     {
1385         end = date2time("2030-01-01");
1386         clipped = true;
1387     }
1388     if (end - (24 * 60 * 60) < reportElement->getStart())
1389     {
1390         end = reportElement->getStart() + (24 * 60 * 60);
1391         clipped = true;
1392     }
1393     if (clipped)
1394         reportController->reportEnd->setDate(time2qdate(end));
1395
1396     reportElement->setEnd(end);
1397     scaleMode = TjGanttChart::autoZoom;
1398     generateReport();
1399 }
1400
1401 QListViewItem*
1402 TjReport::getChartItemBelowCursor(QPoint& pos)
1403 {
1404     if (loadingProject || !isVisible() || !ganttChartView->isVisible())
1405         return 0;
1406
1407     /* Since it is easier to map the global cursor position to the
1408      * ganttChartView coordinates than using the event position we'll got for
1409      * the easy way. */
1410     pos = ganttChartView->mapFromGlobal(QCursor::pos());
1411     // Make sure the cursor is really above the ganttChartView.
1412     if (pos.x() < 0 || pos.y() < 0 ||
1413         pos.x() > ganttChartView->width() ||
1414         pos.y() > ganttChartView->height())
1415         return 0;
1416
1417     return listView->itemAt(QPoint(50, pos.y()));
1418 }
1419
1420 void
1421 TjReport::zoomTo(const QString& label)
1422 {
1423     if (!isVisible())
1424         return;
1425
1426     time_t x = ganttChart->x2time(ganttChartView->contentsX());
1427     int y = ganttChartView->contentsY();
1428
1429     if (!ganttChart->zoomTo(label))
1430         return;
1431
1432     canvasFrame->setMaximumWidth(ganttChart->getWidth());
1433
1434     ganttHeaderView->repaint();
1435     ganttChartView->repaint();
1436     update();
1437
1438     ganttHeaderView->setContentsPos(ganttChart->time2x(x), 0);
1439     ganttChartView->setContentsPos(ganttChart->time2x(x), y);
1440 }
1441
1442 void
1443 TjReport::zoomIn()
1444 {
1445     if (!isVisible())
1446         return;
1447
1448     time_t x = ganttChart->x2time(ganttChartView->contentsX());
1449     int y = ganttChartView->contentsY();
1450
1451     if (!ganttChart->zoomIn())
1452         return;
1453     canvasFrame->setMaximumWidth(ganttChart->getWidth());
1454
1455     ganttHeaderView->repaint();
1456     ganttChartView->repaint();
1457     update();
1458
1459     ganttHeaderView->setContentsPos(ganttChart->time2x(x), 0);
1460     ganttChartView->setContentsPos(ganttChart->time2x(x), y);
1461
1462     updateZoomSelector();
1463 }
1464
1465 void
1466 TjReport::zoomOut()
1467 {
1468     if (!isVisible())
1469         return;
1470
1471     time_t x = ganttChart->x2time(ganttChartView->contentsX());
1472     int y = ganttChartView->contentsY();
1473
1474     if (!ganttChart->zoomOut())
1475         return;
1476     canvasFrame->setMaximumWidth(ganttChart->getWidth());
1477
1478     ganttHeaderView->repaint();
1479     ganttChartView->repaint();
1480     update();
1481
1482     ganttHeaderView->setContentsPos(ganttChart->time2x(x), 0);
1483     ganttChartView->setContentsPos(ganttChart->time2x(x), y);
1484
1485     updateZoomSelector();
1486 }
1487
1488 void
1489 TjReport::show()
1490 {
1491     QWidget::show();
1492
1493     if (statusBarUpdateTimer)
1494         statusBarUpdateTimer->start(500, false);
1495
1496     updateZoomSelector();
1497 }
1498
1499 void
1500 TjReport::hide()
1501 {
1502     if (statusBarUpdateTimer)
1503         statusBarUpdateTimer->stop();
1504
1505     QWidget::hide();
1506 }
1507
1508 QString
1509 TjReport::indent(const QString& input, const QListViewItem* lvi, bool right)
1510 {
1511     // First let's find out how deep we are down the tree;
1512     int level = treeLevel(lvi);
1513
1514     if (right)
1515     {
1516         QString spaces = QString().fill(' ', 2 * (maxDepth - level));
1517         return input + spaces;
1518     }
1519     else
1520     {
1521         QString spaces = QString().fill(' ', 2 * level);
1522         return spaces + input;
1523     }
1524 }
1525
1526 int
1527 TjReport::treeLevel(const QListViewItem* lvi) const
1528 {
1529     assert(lvi != 0);
1530
1531     int level = 0;
1532     while (lvi->parent())
1533     {
1534         level++;
1535         lvi = lvi->parent();
1536         if (level > 30)
1537             kdFatal() << "Tree level explosion";
1538     }
1539     return level;
1540 }
1541
1542 QString
1543 TjReport::generateJournal(Journal::Iterator jit) const
1544 {
1545     QString text;
1546
1547     for ( ; *jit; ++jit)
1548         text += "<b><i>" + time2user((*jit)->getDate(),
1549                                      report->getTimeFormat()) +
1550             "</i></b><br/>" + (*jit)->getText() + "<br/>";
1551
1552     return text;
1553 }
1554
1555 void
1556 TjReport::setGanttChartColors()
1557 {
1558     ganttChart->setColor("headerBackgroundCol", colorGroup().background());
1559     ganttChart->setColor("headerLineCol", Qt::black);
1560     ganttChart->setColor("headerShadowCol", colorGroup().mid());
1561     ganttChart->setColor("chartBackgroundCol", listView->colorGroup().base());
1562     ganttChart->setColor("chartAltBackgroundCol",
1563                          listView->alternateBackground());
1564     ganttChart->setColor("chartTimeOffCol",
1565                          listView->alternateBackground().dark(110));
1566     ganttChart->setColor("chartLineCol",
1567                          listView->alternateBackground().dark(130));
1568     ganttChart->setColor("hightlightCol", colorGroup().highlight());
1569 }
1570
1571 void
1572 TjReport::updateZoomSelector()
1573 {
1574     manager->updateZoomSelector(ganttChart->getZoomStepLabels(),
1575                                 ganttChart->getCurrentZoomStep());
1576 }
1577
1578 #include "TjReport.moc"