2 * The TaskJuggler Project Management Software
4 * Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007
5 * by Chris Schlaeger <cs@kde.org>
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.
18 #include <qsplitter.h>
22 #include <qdatetime.h>
24 #include <qpopupmenu.h>
25 #include <qpaintdevicemetrics.h>
27 #include <klistview.h>
30 #include <kiconloader.h>
34 #include <kglobalsettings.h>
35 #include <ktextbrowser.h>
37 #include <kmessagebox.h>
38 #include <klistviewsearchline.h>
45 #include "ExpressionTree.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"
62 TjReport::TjReport(QWidget* p, ReportManager* m, Report* rDef,
64 : TjUIReportBase(p, m, rDef, n)
66 loadingProject = false;
67 scaleMode = TjGanttChart::fitSize;
69 QVBoxLayout* vl = new QVBoxLayout(this, 0, 0);
70 reportFrame = new QWidget(this);
71 reportFrame->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
73 QHBoxLayout* hl = new QHBoxLayout(reportFrame, 0, 0);
74 splitter = new QSplitter(Horizontal, reportFrame);
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);
84 canvasFrame = new QWidget(splitter);
85 QVBoxLayout* vlChart = new QVBoxLayout(canvasFrame, 0, 0);
86 canvasFrame->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
88 ganttChart = new TjGanttChart(reportFrame);
91 ganttHeaderView = new QCanvasView(ganttChart->getHeaderCanvas(),
93 ganttHeaderView->setHScrollBarMode(QScrollView::AlwaysOff);
94 ganttHeaderView->setVScrollBarMode(QScrollView::AlwaysOff);
96 ganttChartView = new QCanvasView(ganttChart->getChartCanvas(),
98 ganttChartView->setVScrollBarMode(QScrollView::AlwaysOff);
100 reportController = new ReportController(this);
101 reportController->reportSearch->setListView(listView);
102 reportController->reportStart->setDate(time2qdate(rDef->getStart()));
103 reportController->reportEnd->setDate(time2qdate(rDef->getEnd()));
105 vl->addWidget(reportFrame);
106 vl->addWidget(reportController);
107 vlChart->addWidget(ganttHeaderView);
108 vlChart->addWidget(ganttChartView);
109 hl->addWidget(splitter);
111 statusBarUpdateTimer = delayTimer = 0;
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)));
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&)));
137 indexColumns.insert("index");
138 indexColumns.insert("hierarchindex");
139 indexColumns.insert("hierarchno");
140 indexColumns.insert("no");
141 indexColumns.insert("seqno");
142 indexColumns.insert("name");
144 specialColumns.insert("daily");
145 specialColumns.insert("weekly");
146 specialColumns.insert("monthly");
147 specialColumns.insert("quarterly");
148 specialColumns.insert("yearly");
151 TjReport::~TjReport()
155 delete statusBarUpdateTimer;
161 KPrinter* printer = new KPrinter;
162 TjPrintReport* tjpr = 0;
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())))
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();
175 if ((tjpr = this->newPrintReport(printer)) == 0)
181 tjpr->getNumberOfPages(xPages, yPages);
182 if (!tjpr->beginPrinting())
185 // This block avoids a compile error due to gotos crossing 'first'.
188 for (int y = 0; y < yPages; ++y)
189 for (int x = 0; x < xPages; ++x)
195 tjpr->printReportPage(x, y);
208 listView->setFocus();
212 TjReport::event(QEvent* ev)
214 // Regenerate the chart in case of a palette change.
215 if (ev->type() == QEvent::ApplicationPaletteChange)
217 setGanttChartColors();
220 else if (ev->type() == QEvent::MouseButtonPress)
221 handleMouseEvent(static_cast<QMouseEvent*>(ev));
223 return QWidget::event(ev);
227 TjReport::generateReport()
229 setLoadingProject(true);
231 setCursor(KCursor::waitCursor());
232 if (!this->generateList())
234 setLoadingProject(false);
235 setCursor(KCursor::arrowCursor());
238 setLoadingProject(false);
239 setCursor(KCursor::arrowCursor());
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);
247 delete statusBarUpdateTimer;
248 statusBarUpdateTimer = new QTimer(this);
249 connect(statusBarUpdateTimer, SIGNAL(timeout()),
250 this, SLOT(updateStatusBar()));
251 statusBarUpdateTimer->start(500, false);
257 TjReport::triggerChartRegeneration(int msDelay)
261 delayTimer = new QTimer(this);
262 connect(delayTimer, SIGNAL(timeout()),
263 this, SLOT(regenerateChart()));
265 delayTimer->start(msDelay, true);
269 TjReport::regenerateChart()
274 setCursor(KCursor::waitCursor());
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;
282 ganttChart->getHeaderCanvas()->update();
283 ganttChart->getChartCanvas()->update();
285 setCursor(KCursor::arrowCursor());
289 TjReport::generateTaskListLine(const QtReportElement* reportElement,
290 const Task* t, KListViewItem* lvi,
293 assert(reportElement != 0);
297 // Skip the first two columns. They contain the hardwired task name and the
298 // sort index column.
300 for (QPtrListIterator<TableColumnInfo>
301 ci = reportElement->getColumnsIterator(); *ci; ++ci, ++column)
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")
317 const TableColumnFormat* tcf =
318 reportElement->getColumnFormat((*ci)->getName());
320 if ((*ci)->getName() == "accounts")
323 cellText = t->getAccount()->getId();
325 else if ((*ci)->getName() == "completed")
327 double calcedCompletionDegree =
328 t->getCalcedCompletionDegree(scenario);
329 double providedCompletionDegree =
330 t->getCompletionDegree(scenario);
332 if (calcedCompletionDegree < 0)
334 if (calcedCompletionDegree == providedCompletionDegree)
336 cellText = i18n("in progress");
340 cellText = QString(i18n("%1% (in progress)"))
341 .arg((int) providedCompletionDegree);
346 if (calcedCompletionDegree == providedCompletionDegree)
348 cellText = QString("%1%")
349 .arg((int) providedCompletionDegree);
353 cellText = QString("%1% (%2%)")
354 .arg((int) providedCompletionDegree)
355 .arg((int) calcedCompletionDegree);
359 else if ((*ci)->getName() == "completedeffort")
362 if (!r && t->isLeaf())
364 // Task line, no resource.
365 val = t->getCompletedLoad(scenario);
367 else if (t && t->isLeaf())
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);
380 (reportElement->scaledLoad(val, tcf->realFormat),
381 lvi, tcf->getHAlign() == TableColumnFormat::right);
383 else if ((*ci)->getName() == "cost")
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);
392 else if ((*ci)->getName() == "criticalness")
394 cellText = indent(QString().sprintf("%f",
395 t->getCriticalness(scenario)),
396 lvi, tcf->getHAlign() ==
397 TableColumnFormat::right);
399 else if ((*ci)->getName() == "depends")
401 for (TaskListIterator it(t->getPreviousIterator()); *it != 0; ++it)
403 if (!cellText.isEmpty())
405 cellText += (*it)->getId();
408 else if ((*ci)->getName() == "duration")
409 cellText = reportElement->scaledDuration
410 (t->getCalcDuration(scenario), tcf->realFormat);
411 else if ((*ci)->getName() == "effort")
414 val = t->getLoad(scenario, Interval(t->getStart(scenario),
415 t->getEnd(scenario)), r);
417 (reportElement->scaledLoad(val, tcf->realFormat),
418 lvi, tcf->getHAlign() == TableColumnFormat::right);
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")
430 for (TaskListIterator it(t->getFollowersIterator()); *it != 0; ++it)
432 if (!cellText.isEmpty())
434 cellText += (*it)->getId();
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())
453 if (t->getNote().length() > 25 || isRichText(t->getNote()))
454 icon = KGlobal::iconLoader()->
455 loadIcon("document", KIcon::Small);
457 cellText = t->getNote();
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")
474 double val = t->getCredits
475 (scenario, Interval(reportElement->getStart(),
476 reportElement->getEnd()), Revenue, r) -
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);
484 else if ((*ci)->getName() == "remainingeffort")
487 if (!r && t->isLeaf())
489 // Task line, no resource.
490 val = t->getRemainingLoad(scenario);
492 else if (t && t->isLeaf())
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);
505 (reportElement->scaledLoad(val, tcf->realFormat),
506 lvi, tcf->getHAlign() == TableColumnFormat::right);
508 else if ((*ci)->getName() == "resources")
510 for (ResourceListIterator rli
511 (t->getBookedResourcesIterator(scenario)); *rli != 0; ++rli)
513 if (!cellText.isEmpty())
516 cellText += (*rli)->getName();
519 else if ((*ci)->getName() == "responsible")
521 if (t->getResponsible())
522 cellText = t->getResponsible()->getName();
524 else if ((*ci)->getName() == "revenue")
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);
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")
545 cellText = t->getStatusText(scenario);
547 else if ((*ci)->getName() == "statusnote")
549 if (t->getStatusNote(scenario).length() > 25 ||
550 isRichText(t->getStatusNote(scenario)))
551 icon = KGlobal::iconLoader()->
552 loadIcon("document", KIcon::Small);
554 cellText = t->getStatusNote(scenario);
557 generateCustomAttribute(t, (*ci)->getName(), cellText, icon);
559 lvi->setText(column, cellText);
561 lvi->setPixmap(column, icon);
566 TjReport::generateResourceListLine(const QtReportElement* reportElement,
567 Resource* r, KListViewItem* lvi,
570 assert(reportElement != 0);
574 // Skip the first colum. It contains the hardwired resource name.
576 for (QPtrListIterator<TableColumnInfo>
577 ci = reportElement->getColumnsIterator(); *ci; ++ci, ++column)
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")
592 const TableColumnFormat* tcf =
593 reportElement->getColumnFormat((*ci)->getName());
595 if ((*ci)->getName() == "cost")
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);
604 else if ((*ci)->getName() == "efficiency")
606 cellText = QString().sprintf("%.1lf", r->getEfficiency());
608 else if ((*ci)->getName() == "effort")
612 val = r->getEffectiveLoad
613 (scenario, Interval(t->getStart(scenario),
614 t->getEnd(scenario)),
617 val = r->getEffectiveLoad
618 (scenario, Interval(reportElement->getStart(),
619 reportElement->getEnd()));
622 (reportElement->scaledLoad(val, tcf->realFormat), lvi,
623 tcf->getHAlign() == TableColumnFormat::right);
625 else if ((*ci)->getName() == "freeload")
630 val = r->getEffectiveFreeLoad
631 (scenario, Interval(reportElement->getStart(),
632 reportElement->getEnd()));
634 (reportElement->scaledLoad(val, tcf->realFormat), lvi,
635 tcf->getHAlign() == TableColumnFormat::right);
638 else if ((*ci)->getName() == "id")
640 cellText = r->getFullId();
642 else if ((*ci)->getName() == "maxeffort")
644 const UsageLimits* limits = r->getLimits();
646 cellText = i18n("no Limits");
649 int sg = report->getProject()->getScheduleGranularity();
650 if (limits->getDailyMax() > 0)
651 cellText = i18n("D: %1h").arg(limits->getDailyMax() *
653 if (limits->getWeeklyMax() > 0)
655 if (!cellText.isEmpty())
657 cellText += i18n("W: %1h").arg(limits->getWeeklyMax() *
660 if (limits->getMonthlyMax() > 0)
662 if (!cellText.isEmpty())
664 cellText += i18n("M: %1d").arg(limits->getMonthlyMax() *
665 sg / (60 * 60 * 24));
669 else if ((*ci)->getName() == "projectids")
670 cellText = r->getProjectIDs
671 (scenario, Interval(reportElement->getStart(),
672 reportElement->getEnd()));
673 else if ((*ci)->getName() == "rate")
675 cellText = indent(tcf->realFormat.format(r->getRate(), false),
676 lvi, tcf->getHAlign() ==
677 TableColumnFormat::right);
679 else if ((*ci)->getName() == "revenue")
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);
688 else if ((*ci)->getName() == "utilization")
692 double load = r->getEffectiveLoad
693 (scenario, Interval(reportElement->getStart(),
694 reportElement->getEnd()));
700 double freeLoad = r->getEffectiveFreeLoad
701 (scenario, Interval(reportElement->getStart(),
702 reportElement->getEnd()));
703 val = 100.0 / (1.0 + (freeLoad / load));
705 cellText = indent(QString().sprintf("%.1f%%", val), lvi,
706 tcf->getHAlign() == TableColumnFormat::right);
710 generateCustomAttribute(r, (*ci)->getName(), cellText, icon);
712 lvi->setText(column, cellText);
714 lvi->setPixmap(column, icon);
719 TjReport::generateCustomAttribute(const CoreAttributes* ca, const QString name,
720 QString& cellText, QPixmap& icon) const
722 // Handle custom attributes
723 const CustomAttribute* custAttr =
724 ca->getCustomAttribute(name);
727 switch (custAttr->getType())
734 dynamic_cast<const TextAttribute*>(custAttr)->
736 if (text.length() > 25 || isRichText(text))
737 icon = KGlobal::iconLoader()->
738 loadIcon("document", KIcon::Small);
746 ReferenceAttribute*>(custAttr)->getLabel();
747 icon = KGlobal::iconLoader()->
748 loadIcon("html", KIcon::Small);
755 TjReport::prepareChart()
757 /* The object position mapping table changes most likely with every
758 * re-generation. So we delete it and create a new one. */
760 objPosTable = new TjObjPosTable;
761 TjObjPosTableEntry* selectedObject = 0;
763 for (std::map<const QString, KListViewItem*, ltQString>::iterator
764 lvit = ca2lviDict.begin(); lvit != ca2lviDict.end(); ++lvit)
766 KListViewItem* lvi = (*lvit).second;
767 if (!lvi || !lvi->isVisible())
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())
779 // If no, we ignore it.
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")
790 if (tokens[2].isEmpty())
791 ca1 = project->getTask(tokens[1]);
794 ca1 = project->getResource(tokens[1]);
795 ca2 = project->getTask(tokens[2]);
801 if (tokens[2].isEmpty())
802 ca1 = project->getResource(tokens[1]);
805 ca1 = project->getTask(tokens[1]);
806 ca2 = project->getResource(tokens[2]);
811 TjObjPosTableEntry* tableEntry =
812 objPosTable->addEntry(ca1, ca2, lvi->itemPos(), lvi->height(),
815 if (lvi == listView->selectedItem())
816 selectedObject = tableEntry;
819 // Calculate some commenly used values;
820 headerHeight = listView->header()->height();
824 for (lvi = listView->firstChild(); lvi; lvi = lvi->itemBelow())
825 if (lvi->isVisible())
827 if (lvi->height() > itemHeight)
828 itemHeight = lvi->height();
829 listHeight = lvi->itemPos() + itemHeight - 1;
832 // Resize header canvas to new size.
833 ganttHeaderView->setFixedHeight(headerHeight);
835 ganttChart->setProjectAndReportData(getReportElement());
836 QValueList<int> sizes = splitter->sizes();
837 if (scaleMode == TjGanttChart::fitSize)
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. */
843 sizes[0] = static_cast<int>(width() / 3.0);
844 sizes[1] = static_cast<int>(width() * 2.0/3.0);
851 splitter->setSizes(sizes);
854 ganttChart->setSizes(objPosTable, headerHeight, listHeight,
855 sizes[1] == 0 ? static_cast<int>(width() * 2.0/3.0) :
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();
866 canvasFrame->setMaximumWidth(ganttChart->getWidth());
870 TjReport::generateListHeader(const QString& firstHeader, QtReportElement* tab)
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);
885 for (QPtrListIterator<TableColumnInfo>
886 ci = tab->getColumnsIterator(); *ci; ++ci, ++col)
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")
898 if (indexColumns.find((*ci)->getName()) != indexColumns.end() ||
899 specialColumns.find((*ci)->getName()) != specialColumns.end())
905 /* Store a reference to the column info in the lvCol2tci map. */
906 lvCol2tci.insert(lvCol2tci.end(), ci);
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());
919 TjReport::collapsReportItem(QListViewItem*)
926 syncVSlidersGantt2List(ganttChartView->contentsX(), listView->contentsY());
930 TjReport::expandReportItem(QListViewItem*)
936 syncVSlidersGantt2List(ganttChartView->contentsX(), listView->contentsY());
940 TjReport::listClicked(QListViewItem* lvi, const QPoint&, int column)
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
945 if (!lvi || column <= 1 || !lvi->pixmap(column))
948 CoreAttributes* ca = lvi2caDict[QString().sprintf("%p", lvi)];
949 const TableColumnInfo* tci = lvCol2tci[column - 2];
951 if (ca->getType() == CA_Task &&
952 tci->getName() == "note" &&
953 !(dynamic_cast<Task*>(ca))->getNote().isEmpty())
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);
963 richTextDisplay->textDisplay->setText(t->getNote());
964 richTextDisplay->show();
966 else if (ca->getType() == CA_Task &&
967 tci->getName() == "statusnote" &&
968 !(dynamic_cast<Task*>(ca))->getStatusNote(scenario).isEmpty())
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);
979 richTextDisplay->textDisplay->setText(t->getStatusNote(scenario));
980 richTextDisplay->show();
982 else if (ca->getCustomAttribute(tci->getName()))
984 switch (ca->getCustomAttribute(tci->getName())->getType())
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")
998 .arg(ca->getType() == CA_Task ? i18n("Task") :
1002 richTextDisplay->textDisplay->setTextFormat(Qt::RichText);
1004 richTextDisplay->textDisplay->setText(textAttr->getText());
1005 richTextDisplay->show();
1010 const ReferenceAttribute* refAttr =
1011 dynamic_cast<const ReferenceAttribute*>
1012 (ca->getCustomAttribute(tci->getName()));
1013 KRun::runURL(KURL(refAttr->getURL()), "text/html");
1021 TjReport::listHeaderClicked(int)
1027 TjReport::doPopupMenu(QListViewItem* lvi, const QPoint& pos, int)
1032 CoreAttributes* ca = lvi2caDict[QString().sprintf("%p", lvi)];
1034 if (ca->getType() == CA_Task)
1036 Task* t = dynamic_cast<Task*>(ca);
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))
1044 emit signalEditCoreAttributes(ca);
1057 Resource* r = dynamic_cast<Resource*>(ca);
1059 menu.insertItem(i18n("&Edit Resource"), 1);
1060 menu.insertItem(i18n("Show Resource &Details"), 2);
1061 switch (menu.exec(pos))
1064 emit signalEditCoreAttributes(ca);
1067 showResourceDetails(r);
1076 TjReport::showTaskDetails(const Task* task)
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);
1085 text = i18n("<b>Task:</b> %1 (%2)<br/>")
1086 .arg(task->getName())
1087 .arg(task->getId());
1089 if (!task->getNote().isEmpty())
1091 if (!text.isEmpty())
1093 text += i18n("<b>Note:</b> %1<br/>").arg(task->getNote());
1096 if (task->isMilestone())
1098 if (!text.isEmpty())
1100 text += i18n("<b>Date:</b> %1<br/>")
1101 .arg(time2tjp(task->getStart(scenario)));
1105 if (!text.isEmpty())
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));
1114 if (task->getEffort(scenario) > 0.0)
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(),
1122 if (completion < 0.0)
1124 text += i18n("<b>Completion degree:</b> in progress<br/>");
1126 else if (completion == 0.0)
1128 text += i18n("<b>Completion degree:</b> 0%<br/>");
1130 else if (completion >= 1.0)
1132 text += i18n("<b>Completion degree:</b> 100%<br/>");
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));
1149 if (!task->getStatusNote(scenario).isEmpty())
1150 text += i18n("<b>Note:</b> %1<br/>")
1151 .arg(task->getStatusNote(scenario));
1154 QString predecessors;
1155 for (TaskListIterator tli(task->getPreviousIterator()); *tli; ++tli)
1156 predecessors += "<li>" + (*tli)->getName() + " (" + (*tli)->getId() +
1160 for (TaskListIterator tli(task->getFollowersIterator()); *tli; ++tli)
1161 successors += "<li>" + (*tli)->getName() + " (" + (*tli)->getId() +
1164 if (!predecessors.isEmpty() || !successors.isEmpty())
1167 if (!predecessors.isEmpty())
1168 text += i18n("<b>Predecessors:</b><ul>%1</ul><br/>")
1170 if (!successors.isEmpty())
1171 text += i18n("<b>Successors:</b><ul>%1</ul><br/>").arg(successors);
1174 ResourceListIterator rli = task->getBookedResourcesIterator(scenario);
1177 text += "<hr/><b>Allocated resources:</b><ul>";
1179 text += "<li>" + (*rli)->getName() + " (" + (*rli)->getId() +
1184 text += generateRTCustomAttributes(task);
1186 if (task->hasJournal())
1189 text += generateJournal(task->getJournalIterator());
1192 richTextDisplay->textDisplay->setText(text);
1193 richTextDisplay->show();
1197 TjReport::showResourceDetails(Resource* resource)
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);
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);
1212 text = i18n("<b>Resource:</b> %1 (%2)<br/>")
1213 .arg(resource->getName())
1214 .arg(resource->getId());
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));
1222 if (resource->hasJournal())
1224 if (!text.isEmpty())
1226 text += generateJournal(resource->getJournalIterator());
1229 text += generateRTCustomAttributes(resource);
1231 if (resource->hasJournal())
1234 text += generateJournal(resource->getJournalIterator());
1237 richTextDisplay->textDisplay->setText(text);
1238 richTextDisplay->show();
1242 TjReport::generateRTCustomAttributes(const CoreAttributes* ca) const
1244 QDict<CustomAttribute> caDict = ca->getCustomAttributeDict();
1246 QString text = "<hr/>";
1247 if (caDict.isEmpty())
1250 for (QDictIterator<CustomAttribute> cadi(caDict); cadi.current(); ++cadi)
1252 text += "<b>" + cadi.currentKey() + ":</b> ";
1253 CustomAttribute* custAttr = cadi.current();
1254 switch (cadi.current()->getType())
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>";
1267 text += dynamic_cast<const TextAttribute*>(custAttr)->
1280 TjReport::syncVSlidersGantt2List(int x, int y)
1282 ganttHeaderView->setContentsPos(x, ganttHeaderView->contentsY());
1283 if (y != listView->contentsY())
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)));
1296 TjReport::syncVSlidersList2Gantt(int, int y)
1298 if (y != ganttChartView->contentsY())
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)));
1311 TjReport::handleMouseEvent(const QMouseEvent* ev)
1314 QListViewItem* lvi = getChartItemBelowCursor(pos);
1318 if (ev->button() == Qt::LeftButton)
1319 listView->setSelected(lvi, true);
1320 else if (ev->button() == Qt::RightButton)
1321 doPopupMenu(lvi, QCursor::pos(), 0);
1325 TjReport::updateStatusBar()
1328 QListViewItem* lvi = getChartItemBelowCursor(pos);
1331 emit signalChangeStatusBar("");
1335 CoreAttributes* ca = lvi2caDict[QString().sprintf("%p", lvi)];
1336 CoreAttributes* parent = lvi2ParentCaDict[QString().sprintf("%p", lvi)];
1338 emit signalChangeStatusBar(this->generateStatusBarText(pos, ca, parent));
1342 TjReport::reportSearchTriggered(const QString&)
1344 triggerChartRegeneration(200);
1348 TjReport::setReportStart(const QDate& d)
1350 ReportElement* reportElement = this->getReportElement();
1351 time_t start = qdate2time(d);
1353 bool clipped = false;
1354 if (start < date2time("2000-01-01"))
1356 start = date2time("2000-01-01");
1359 if (start > date2time("2030-01-01"))
1361 start = date2time("2030-01-01");
1364 if (start + (24 * 60 * 60) > reportElement->getEnd())
1366 start = reportElement->getEnd() - (24 * 60 * 60);
1370 reportController->reportStart->setDate(time2qdate(start));
1372 reportElement->setStart(start);
1373 scaleMode = TjGanttChart::autoZoom;
1378 TjReport::setReportEnd(const QDate& d)
1380 ReportElement* reportElement = this->getReportElement();
1381 time_t end = qdate2time(d);
1383 bool clipped = false;
1384 if (end < date2time("2000-01-01"))
1386 end = date2time("2000-01-01");
1389 if (end > date2time("2030-01-01"))
1391 end = date2time("2030-01-01");
1394 if (end - (24 * 60 * 60) < reportElement->getStart())
1396 end = reportElement->getStart() + (24 * 60 * 60);
1400 reportController->reportEnd->setDate(time2qdate(end));
1402 reportElement->setEnd(end);
1403 scaleMode = TjGanttChart::autoZoom;
1408 TjReport::getChartItemBelowCursor(QPoint& pos)
1410 if (loadingProject || !isVisible() || !ganttChartView->isVisible())
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
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())
1423 return listView->itemAt(QPoint(50, pos.y()));
1427 TjReport::zoomTo(const QString& label)
1432 time_t x = ganttChart->x2time(ganttChartView->contentsX());
1433 int y = ganttChartView->contentsY();
1435 if (!ganttChart->zoomTo(label))
1438 canvasFrame->setMaximumWidth(ganttChart->getWidth());
1440 ganttHeaderView->repaint();
1441 ganttChartView->repaint();
1444 ganttHeaderView->setContentsPos(ganttChart->time2x(x), 0);
1445 ganttChartView->setContentsPos(ganttChart->time2x(x), y);
1454 time_t x = ganttChart->x2time(ganttChartView->contentsX());
1455 int y = ganttChartView->contentsY();
1457 if (!ganttChart->zoomIn())
1459 canvasFrame->setMaximumWidth(ganttChart->getWidth());
1461 ganttHeaderView->repaint();
1462 ganttChartView->repaint();
1465 ganttHeaderView->setContentsPos(ganttChart->time2x(x), 0);
1466 ganttChartView->setContentsPos(ganttChart->time2x(x), y);
1468 updateZoomSelector();
1477 time_t x = ganttChart->x2time(ganttChartView->contentsX());
1478 int y = ganttChartView->contentsY();
1480 if (!ganttChart->zoomOut())
1482 canvasFrame->setMaximumWidth(ganttChart->getWidth());
1484 ganttHeaderView->repaint();
1485 ganttChartView->repaint();
1488 ganttHeaderView->setContentsPos(ganttChart->time2x(x), 0);
1489 ganttChartView->setContentsPos(ganttChart->time2x(x), y);
1491 updateZoomSelector();
1499 if (statusBarUpdateTimer)
1500 statusBarUpdateTimer->start(500, false);
1502 updateZoomSelector();
1508 if (statusBarUpdateTimer)
1509 statusBarUpdateTimer->stop();
1515 TjReport::indent(const QString& input, const QListViewItem* lvi, bool right)
1517 // First let's find out how deep we are down the tree;
1518 int level = treeLevel(lvi);
1522 QString spaces = QString().fill(' ', 2 * (maxDepth - level));
1523 return input + spaces;
1527 QString spaces = QString().fill(' ', 2 * level);
1528 return spaces + input;
1533 TjReport::treeLevel(const QListViewItem* lvi) const
1538 while (lvi->parent())
1541 lvi = lvi->parent();
1543 kdFatal() << "Tree level explosion";
1549 TjReport::generateJournal(Journal::Iterator jit) const
1553 for ( ; *jit; ++jit)
1554 text += "<b><i>" + time2user((*jit)->getDate(),
1555 report->getTimeFormat()) +
1556 "</i></b><br/>" + (*jit)->getText() + "<br/>";
1562 TjReport::setGanttChartColors()
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());
1578 TjReport::updateZoomSelector()
1580 manager->updateZoomSelector(ganttChart->getZoomStepLabels(),
1581 ganttChart->getCurrentZoomStep());
1584 #include "TjReport.moc"