OSDN Git Service

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