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>
44 #include "ExpressionTree.h"
46 #include "TableColumnFormat.h"
47 #include "TextAttribute.h"
48 #include "ReferenceAttribute.h"
49 #include "QtTaskReport.h"
50 #include "QtResourceReport.h"
51 #include "RichTextDisplay.h"
52 #include "TjPrintReport.h"
53 #include "TjGanttChart.h"
54 #include "TjObjPosTable.h"
55 #include "KPrinterWrapper.h"
56 #include "UsageLimits.h"
57 #include "ReportManager.h"
58 #include "ReportController.h"
59 #include "kdateedit.h"
61 TjReport::TjReport(QWidget* p, ReportManager* m, Report* rDef,
63 : TjUIReportBase(p, m, rDef, n)
65 loadingProject = false;
66 scaleMode = TjGanttChart::fitSize;
68 QVBoxLayout* vl = new QVBoxLayout(this, 0, 0);
69 reportFrame = new QWidget(this);
70 reportFrame->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
72 QHBoxLayout* hl = new QHBoxLayout(reportFrame, 0, 0);
73 splitter = new QSplitter(Horizontal, reportFrame);
75 listView = new KListView(splitter);
76 listView->setRootIsDecorated(true);
77 listView->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
78 listView->setAllColumnsShowFocus(true);
79 // The sorting does not work yet properly.
80 listView->header()->setClickEnabled(false);
81 listView->setItemMargin(2);
83 canvasFrame = new QWidget(splitter);
84 QVBoxLayout* vlChart = new QVBoxLayout(canvasFrame, 0, 0);
85 canvasFrame->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
87 ganttChart = new TjGanttChart(reportFrame);
90 ganttHeaderView = new QCanvasView(ganttChart->getHeaderCanvas(),
92 ganttHeaderView->setHScrollBarMode(QScrollView::AlwaysOff);
93 ganttHeaderView->setVScrollBarMode(QScrollView::AlwaysOff);
95 ganttChartView = new QCanvasView(ganttChart->getChartCanvas(),
97 ganttChartView->setVScrollBarMode(QScrollView::AlwaysOff);
99 reportController = new ReportController(this);
100 reportController->reportSearch->setListView(listView);
101 reportController->reportStart->setDate(time2qdate(rDef->getStart()));
102 reportController->reportEnd->setDate(time2qdate(rDef->getEnd()));
104 vl->addWidget(reportFrame);
105 vl->addWidget(reportController);
106 vlChart->addWidget(ganttHeaderView);
107 vlChart->addWidget(ganttChartView);
108 hl->addWidget(splitter);
110 statusBarUpdateTimer = delayTimer = 0;
112 connect(listView, SIGNAL(selectionChanged()),
113 this, SLOT(regenerateChart()));
114 connect(listView, SIGNAL(expanded(QListViewItem*)),
115 this, SLOT(expandReportItem(QListViewItem*)));
116 connect(listView, SIGNAL(collapsed(QListViewItem*)),
117 this, SLOT(collapsReportItem(QListViewItem*)));
118 connect(listView, SIGNAL(clicked(QListViewItem*, const QPoint&, int)),
119 this, SLOT(listClicked(QListViewItem*, const QPoint&, int)));
121 SIGNAL(rightButtonPressed(QListViewItem*, const QPoint&, int)),
122 this, SLOT(doPopupMenu(QListViewItem*, const QPoint&, int)));
123 connect(listView->header(), SIGNAL(clicked(int)),
124 this, SLOT(listHeaderClicked(int)));
125 connect(ganttChartView, SIGNAL(contentsMoving(int, int)),
126 this, SLOT(syncVSlidersGantt2List(int, int)));
127 connect(listView, SIGNAL(contentsMoving(int, int)),
128 this, SLOT(syncVSlidersList2Gantt(int, int)));
129 connect(reportController->reportSearch, SIGNAL(textChanged(const QString&)),
130 this, SLOT(reportSearchTriggered(const QString&)));
131 connect(reportController->reportStart, SIGNAL(dateChanged(const QDate&)),
132 this, SLOT(setReportStart(const QDate&)));
133 connect(reportController->reportEnd, SIGNAL(dateChanged(const QDate&)),
134 this, SLOT(setReportEnd(const QDate&)));
136 indexColumns.insert("index");
137 indexColumns.insert("hierarchindex");
138 indexColumns.insert("hierarchno");
139 indexColumns.insert("no");
140 indexColumns.insert("seqno");
141 indexColumns.insert("name");
143 specialColumns.insert("daily");
144 specialColumns.insert("weekly");
145 specialColumns.insert("monthly");
146 specialColumns.insert("quarterly");
147 specialColumns.insert("yearly");
150 TjReport::~TjReport()
154 delete statusBarUpdateTimer;
160 KPrinter* printer = new KPrinter;
161 TjPrintReport* tjpr = 0;
163 printer->setFullPage(true);
164 printer->setResolution(300);
165 printer->setCreator(QString("TaskJuggler %1 - visit %2")
166 .arg(VERSION).arg(TJURL));
167 if (!printer->setup(this, i18n("Print %1").arg(report->getFileName())))
170 /* This is a hack to workaround the problem that the KPrinter settings
171 * not transferred to the QPrinter object when not printing to a file. */
172 ((KPrinterWrapper*) printer)->preparePrinting();
174 if ((tjpr = this->newPrintReport(printer)) == 0)
180 tjpr->getNumberOfPages(xPages, yPages);
181 if (!tjpr->beginPrinting())
184 // This block avoids a compile error due to gotos crossing 'first'.
187 for (int y = 0; y < yPages; ++y)
188 for (int x = 0; x < xPages; ++x)
194 tjpr->printReportPage(x, y);
207 listView->setFocus();
211 TjReport::event(QEvent* ev)
213 // Regenerate the chart in case of a palette change.
214 if (ev->type() == QEvent::ApplicationPaletteChange)
216 setGanttChartColors();
219 else if (ev->type() == QEvent::MouseButtonPress)
220 handleMouseEvent(static_cast<QMouseEvent*>(ev));
222 return QWidget::event(ev);
226 TjReport::generateReport()
228 setLoadingProject(true);
230 setCursor(KCursor::waitCursor());
231 if (!this->generateList())
233 setLoadingProject(false);
234 setCursor(KCursor::arrowCursor());
237 setLoadingProject(false);
238 setCursor(KCursor::arrowCursor());
240 /* The first time we generate the report, the window has not been fully
241 * layouted yet. So we can't set the splitter to a good size and generate
242 * the gantt report immediately. We use a 200ms timer to delay the
243 * rendering. Hopefully by then the window has been layouted properly. */
244 triggerChartRegeneration(200);
246 delete statusBarUpdateTimer;
247 statusBarUpdateTimer = new QTimer(this);
248 connect(statusBarUpdateTimer, SIGNAL(timeout()),
249 this, SLOT(updateStatusBar()));
250 statusBarUpdateTimer->start(500, false);
256 TjReport::triggerChartRegeneration(int msDelay)
260 delayTimer = new QTimer(this);
261 connect(delayTimer, SIGNAL(timeout()),
262 this, SLOT(regenerateChart()));
264 delayTimer->start(msDelay, true);
268 TjReport::regenerateChart()
273 setCursor(KCursor::waitCursor());
277 // When we are here, we have rendered the widgets at least once. So we can
278 // turn off manual mode.
279 scaleMode = TjGanttChart::manual;
281 ganttChart->getHeaderCanvas()->update();
282 ganttChart->getChartCanvas()->update();
284 setCursor(KCursor::arrowCursor());
288 TjReport::generateTaskListLine(const QtReportElement* reportElement,
289 const Task* t, KListViewItem* lvi,
292 assert(reportElement != 0);
296 // Skip the first two columns. They contain the hardwired task name and the
297 // sort index column.
299 for (QPtrListIterator<TableColumnInfo>
300 ci = reportElement->getColumnsIterator(); *ci; ++ci, ++column)
302 /* The name and indices columns are automatically added as first
303 * columns, so we will just ignore them if the user has requested them
304 * as well. Calendar and chart columns get special treatment as well. */
305 if (indexColumns.find((*ci)->getName()) != indexColumns.end() ||
306 specialColumns.find((*ci)->getName()) != specialColumns.end() ||
307 (*ci)->getName() == "chart")
316 const TableColumnFormat* tcf =
317 reportElement->getColumnFormat((*ci)->getName());
319 if ((*ci)->getName() == "completed")
321 double calcedCompletionDegree =
322 t->getCalcedCompletionDegree(scenario);
323 double providedCompletionDegree =
324 t->getCompletionDegree(scenario);
326 if (calcedCompletionDegree < 0)
328 if (calcedCompletionDegree == providedCompletionDegree)
330 cellText = i18n("in progress");
334 cellText = QString(i18n("%1% (in progress)"))
335 .arg((int) providedCompletionDegree);
340 if (calcedCompletionDegree == providedCompletionDegree)
342 cellText = QString("%1%")
343 .arg((int) providedCompletionDegree);
347 cellText = QString("%1% (%2%)")
348 .arg((int) providedCompletionDegree)
349 .arg((int) calcedCompletionDegree);
353 else if ((*ci)->getName() == "completedeffort")
356 if (!r && t->isLeaf())
358 // Task line, no resource.
359 val = t->getCompletedLoad(scenario);
361 else if (t && t->isLeaf())
363 // Task line, nested into a resource
364 const Project* project = report->getProject();
365 time_t now = project->getNow();
366 if (now < project->getStart())
367 now = project->getStart();
368 if (now > project->getEnd())
369 now = project->getEnd();
370 Interval iv = Interval(project->getStart(), now);
371 val = t->getLoad(scenario, iv, r);
374 (reportElement->scaledLoad(val, tcf->realFormat),
375 lvi, tcf->getHAlign() == TableColumnFormat::right);
377 else if ((*ci)->getName() == "cost")
379 double val = t->getCredits
380 (scenario, Interval(reportElement->getStart(),
381 reportElement->getEnd()), Cost, r);
382 cellText = indent(tcf->realFormat.format(val, false),
383 lvi, tcf->getHAlign() ==
384 TableColumnFormat::right);
386 else if ((*ci)->getName() == "criticalness")
388 cellText = indent(QString().sprintf("%f",
389 t->getCriticalness(scenario)),
390 lvi, tcf->getHAlign() ==
391 TableColumnFormat::right);
393 else if ((*ci)->getName() == "depends")
395 for (TaskListIterator it(t->getPreviousIterator()); *it != 0; ++it)
397 if (!cellText.isEmpty())
399 cellText += (*it)->getId();
402 else if ((*ci)->getName() == "duration")
403 cellText = reportElement->scaledDuration
404 (t->getCalcDuration(scenario), tcf->realFormat);
405 else if ((*ci)->getName() == "effort")
408 val = t->getLoad(scenario, Interval(t->getStart(scenario),
409 t->getEnd(scenario)), r);
411 (reportElement->scaledLoad(val, tcf->realFormat),
412 lvi, tcf->getHAlign() == TableColumnFormat::right);
414 else if ((*ci)->getName() == "end")
415 cellText = time2user(t->getEnd(scenario) + 1,
416 reportElement->getTimeFormat());
417 else if ((*ci)->getName() == "endbuffer")
418 cellText.sprintf("%3.0f", t->getEndBuffer(scenario));
419 else if ((*ci)->getName() == "endbufferstart")
420 cellText = time2user(t->getEndBufferStart(scenario),
421 reportElement->getTimeFormat());
422 else if ((*ci)->getName() == "follows")
424 for (TaskListIterator it(t->getFollowersIterator()); *it != 0; ++it)
426 if (!cellText.isEmpty())
428 cellText += (*it)->getId();
431 else if ((*ci)->getName() == "id")
432 cellText = t->getId();
433 else if ((*ci)->getName() == "maxend")
434 cellText = time2user(t->getMaxEnd(scenario),
435 reportElement->getTimeFormat());
436 else if ((*ci)->getName() == "maxstart")
437 cellText = time2user(t->getMaxStart(scenario),
438 reportElement->getTimeFormat());
439 else if ((*ci)->getName() == "minend")
440 cellText = time2user(t->getMinEnd(scenario),
441 reportElement->getTimeFormat());
442 else if ((*ci)->getName() == "minstart")
443 cellText = time2user(t->getMinStart(scenario),
444 reportElement->getTimeFormat());
445 else if ((*ci)->getName() == "note" && !t->getNote().isEmpty())
447 if (t->getNote().length() > 25 || isRichText(t->getNote()))
448 icon = KGlobal::iconLoader()->
449 loadIcon("document", KIcon::Small);
451 cellText = t->getNote();
453 else if ((*ci)->getName() == "pathcriticalness")
454 cellText = indent(QString().sprintf
455 ("%f", t->getPathCriticalness(scenario)),
456 lvi, tcf->getHAlign() ==
457 TableColumnFormat::right);
458 else if ((*ci)->getName() == "priority")
459 cellText = indent(QString().sprintf("%d", t->getPriority()),
460 lvi, tcf->getHAlign() ==
461 TableColumnFormat::right);
462 else if ((*ci)->getName() == "projectid")
463 cellText = t->getProjectId() + " (" +
464 reportElement->getReport()->getProject()->getIdIndex
465 (t->getProjectId()) + ")";
466 else if ((*ci)->getName() == "profit")
468 double val = t->getCredits
469 (scenario, Interval(reportElement->getStart(),
470 reportElement->getEnd()), Revenue, r) -
472 (scenario, Interval(reportElement->getStart(),
473 reportElement->getEnd()), Cost, r);
474 cellText = indent(tcf->realFormat.format(val, false),
475 lvi, tcf->getHAlign() ==
476 TableColumnFormat::right);
478 else if ((*ci)->getName() == "remainingeffort")
481 if (!r && t->isLeaf())
483 // Task line, no resource.
484 val = t->getRemainingLoad(scenario);
486 else if (t && t->isLeaf())
488 // Task line, nested into a resource
489 const Project* project = report->getProject();
490 time_t now = project->getNow();
491 if (now < project->getStart())
492 now = project->getStart();
493 if (now > project->getEnd())
494 now = project->getEnd();
495 Interval iv = Interval(now, project->getEnd());
496 val = t->getLoad(scenario, iv, r);
499 (reportElement->scaledLoad(val, tcf->realFormat),
500 lvi, tcf->getHAlign() == TableColumnFormat::right);
502 else if ((*ci)->getName() == "resources")
504 for (ResourceListIterator rli
505 (t->getBookedResourcesIterator(scenario)); *rli != 0; ++rli)
507 if (!cellText.isEmpty())
510 cellText += (*rli)->getName();
513 else if ((*ci)->getName() == "responsible")
515 if (t->getResponsible())
516 cellText = t->getResponsible()->getName();
518 else if ((*ci)->getName() == "revenue")
520 double val = t->getCredits
521 (scenario, Interval(reportElement->getStart(),
522 reportElement->getEnd()), Revenue, r);
523 cellText = indent(tcf->realFormat.format(val, false),
524 lvi, tcf->getHAlign() ==
525 TableColumnFormat::right);
527 else if ((*ci)->getName() == "scheduling")
528 cellText = t->getSchedulingText();
529 else if ((*ci)->getName() == "start")
530 cellText = time2user(t->getStart(scenario),
531 reportElement->getTimeFormat());
532 else if ((*ci)->getName() == "startbuffer")
533 cellText.sprintf("%3.0f", t->getStartBuffer(scenario));
534 else if ((*ci)->getName() == "startbufferend")
535 cellText = time2user(t->getStartBufferEnd(scenario),
536 reportElement->getTimeFormat());
537 else if ((*ci)->getName() == "status")
539 cellText = t->getStatusText(scenario);
541 else if ((*ci)->getName() == "statusnote")
543 if (t->getStatusNote(scenario).length() > 25 ||
544 isRichText(t->getStatusNote(scenario)))
545 icon = KGlobal::iconLoader()->
546 loadIcon("document", KIcon::Small);
548 cellText = t->getStatusNote(scenario);
551 generateCustomAttribute(t, (*ci)->getName(), cellText, icon);
553 lvi->setText(column, cellText);
555 lvi->setPixmap(column, icon);
560 TjReport::generateResourceListLine(const QtReportElement* reportElement,
561 Resource* r, KListViewItem* lvi,
564 assert(reportElement != 0);
568 // Skip the first colum. It contains the hardwired resource name.
570 for (QPtrListIterator<TableColumnInfo>
571 ci = reportElement->getColumnsIterator(); *ci; ++ci, ++column)
573 /* The name and indices columns are automatically added as first
574 * columns, so we will just ignore them if the user has requested them
575 * as well. Calendar and chart columns get special treatment as well. */
576 if (indexColumns.find((*ci)->getName()) != indexColumns.end() ||
577 specialColumns.find((*ci)->getName()) != specialColumns.end() ||
578 (*ci)->getName() == "chart")
586 const TableColumnFormat* tcf =
587 reportElement->getColumnFormat((*ci)->getName());
589 if ((*ci)->getName() == "cost")
591 double val = r->getCredits
592 (scenario, Interval(reportElement->getStart(),
593 reportElement->getEnd()), Cost, t);
594 cellText = indent(tcf->realFormat.format(val, false),
595 lvi, tcf->getHAlign() ==
596 TableColumnFormat::right);
598 else if ((*ci)->getName() == "efficiency")
600 cellText = QString().sprintf("%.1lf", r->getEfficiency());
602 else if ((*ci)->getName() == "effort")
606 val = r->getEffectiveLoad
607 (scenario, Interval(t->getStart(scenario),
608 t->getEnd(scenario)),
611 val = r->getEffectiveLoad
612 (scenario, Interval(reportElement->getStart(),
613 reportElement->getEnd()));
616 (reportElement->scaledLoad(val, tcf->realFormat), lvi,
617 tcf->getHAlign() == TableColumnFormat::right);
619 else if ((*ci)->getName() == "freeload")
624 val = r->getEffectiveFreeLoad
625 (scenario, Interval(reportElement->getStart(),
626 reportElement->getEnd()));
628 (reportElement->scaledLoad(val, tcf->realFormat), lvi,
629 tcf->getHAlign() == TableColumnFormat::right);
632 else if ((*ci)->getName() == "id")
634 cellText = r->getFullId();
636 else if ((*ci)->getName() == "maxeffort")
638 const UsageLimits* limits = r->getLimits();
640 cellText = i18n("no Limits");
643 int sg = report->getProject()->getScheduleGranularity();
644 if (limits->getDailyMax() > 0)
645 cellText = i18n("D: %1h").arg(limits->getDailyMax() *
647 if (limits->getWeeklyMax() > 0)
649 if (!cellText.isEmpty())
651 cellText += i18n("W: %1h").arg(limits->getWeeklyMax() *
654 if (limits->getMonthlyMax() > 0)
656 if (!cellText.isEmpty())
658 cellText += i18n("M: %1d").arg(limits->getMonthlyMax() *
659 sg / (60 * 60 * 24));
663 else if ((*ci)->getName() == "projectids")
664 cellText = r->getProjectIDs
665 (scenario, Interval(reportElement->getStart(),
666 reportElement->getEnd()));
667 else if ((*ci)->getName() == "rate")
669 cellText = indent(tcf->realFormat.format(r->getRate(), false),
670 lvi, tcf->getHAlign() ==
671 TableColumnFormat::right);
673 else if ((*ci)->getName() == "revenue")
675 double val = r->getCredits
676 (scenario, Interval(reportElement->getStart(),
677 reportElement->getEnd()), Revenue, t);
678 cellText = indent(tcf->realFormat.format(val, false),
679 lvi, tcf->getHAlign() ==
680 TableColumnFormat::right);
682 else if ((*ci)->getName() == "utilization")
686 double load = r->getEffectiveLoad
687 (scenario, Interval(reportElement->getStart(),
688 reportElement->getEnd()));
694 double freeLoad = r->getEffectiveFreeLoad
695 (scenario, Interval(reportElement->getStart(),
696 reportElement->getEnd()));
697 val = 100.0 / (1.0 + (freeLoad / load));
699 cellText = indent(QString().sprintf("%.1f%%", val), lvi,
700 tcf->getHAlign() == TableColumnFormat::right);
704 generateCustomAttribute(r, (*ci)->getName(), cellText, icon);
706 lvi->setText(column, cellText);
708 lvi->setPixmap(column, icon);
713 TjReport::generateCustomAttribute(const CoreAttributes* ca, const QString name,
714 QString& cellText, QPixmap& icon) const
716 // Handle custom attributes
717 const CustomAttribute* custAttr =
718 ca->getCustomAttribute(name);
721 switch (custAttr->getType())
728 dynamic_cast<const TextAttribute*>(custAttr)->
730 if (text.length() > 25 || isRichText(text))
731 icon = KGlobal::iconLoader()->
732 loadIcon("document", KIcon::Small);
740 ReferenceAttribute*>(custAttr)->getLabel();
741 icon = KGlobal::iconLoader()->
742 loadIcon("html", KIcon::Small);
749 TjReport::prepareChart()
751 /* The object position mapping table changes most likely with every
752 * re-generation. So we delete it and create a new one. */
754 objPosTable = new TjObjPosTable;
755 TjObjPosTableEntry* selectedObject = 0;
757 for (std::map<const QString, KListViewItem*, ltQString>::iterator
758 lvit = ca2lviDict.begin(); lvit != ca2lviDict.end(); ++lvit)
760 KListViewItem* lvi = (*lvit).second;
761 if (!lvi || !lvi->isVisible())
764 // Find out if the list entry is visible at all.
765 const QListViewItem* p;
766 bool isVisible = true;
767 for (p = lvi->parent(); p; p = p->parent())
773 // If no, we ignore it.
777 // Reconstruct the CoreAttributes pointers.
778 QStringList tokens = QStringList::split(":", (*lvit).first);
779 CoreAttributes* ca1 = 0;
780 CoreAttributes* ca2 = 0;
781 const Project* project = report->getProject();
782 if (tokens[0] == "t")
784 if (tokens[2].isEmpty())
785 ca1 = project->getTask(tokens[1]);
788 ca1 = project->getResource(tokens[1]);
789 ca2 = project->getTask(tokens[2]);
795 if (tokens[2].isEmpty())
796 ca1 = project->getResource(tokens[1]);
799 ca1 = project->getTask(tokens[1]);
800 ca2 = project->getResource(tokens[2]);
805 TjObjPosTableEntry* tableEntry =
806 objPosTable->addEntry(ca1, ca2, lvi->itemPos(), lvi->height(),
809 if (lvi == listView->selectedItem())
810 selectedObject = tableEntry;
813 // Calculate some commenly used values;
814 headerHeight = listView->header()->height();
818 for (lvi = listView->firstChild(); lvi; lvi = lvi->itemBelow())
819 if (lvi->isVisible())
821 if (lvi->height() > itemHeight)
822 itemHeight = lvi->height();
823 listHeight = lvi->itemPos() + itemHeight - 1;
826 // Resize header canvas to new size.
827 ganttHeaderView->setFixedHeight(headerHeight);
829 ganttChart->setProjectAndReportData(getReportElement());
830 QValueList<int> sizes = splitter->sizes();
831 if (scaleMode == TjGanttChart::fitSize)
833 /* In fitSize mode we show 1/3 table and 2/3 gantt chart. Otherwise we
834 * just keep the current size of the splitter. */
837 sizes[0] = static_cast<int>(width() / 3.0);
838 sizes[1] = static_cast<int>(width() * 2.0/3.0);
845 splitter->setSizes(sizes);
848 ganttChart->setSizes(objPosTable, headerHeight, listHeight,
849 sizes[1] == 0 ? static_cast<int>(width() * 2.0/3.0) :
852 ganttChart->setSelection(selectedObject);
853 QPaintDeviceMetrics metrics(ganttChartView);
854 ganttChart->setDPI(metrics.logicalDpiX(), metrics.logicalDpiY());
855 setGanttChartColors();
856 ganttChart->setHeaderHeight(headerHeight);
857 ganttChart->generate(scaleMode);
858 updateZoomSelector();
860 canvasFrame->setMaximumWidth(ganttChart->getWidth());
864 TjReport::generateListHeader(const QString& firstHeader, QtReportElement* tab)
866 // The first column is always the Task/Resource column
867 listView->addColumn(firstHeader + "\n");
868 // The second column is the sort index. It is always hidden.
869 listView->addColumn("sortIndex");
870 listView->setColumnWidthMode(1, QListView::Manual);
871 listView->hideColumn(1);
872 listView->setSortOrder(Qt::Ascending);
873 listView->setSortColumn(1);
879 for (QPtrListIterator<TableColumnInfo>
880 ci = tab->getColumnsIterator(); *ci; ++ci, ++col)
882 /* The name and indices columns are automatically added as first
883 * columns, so we will just ignore them if the user has requested them
884 * as well. Calendar columns get special treatment as well. */
885 if ((*ci)->getName() == "chart")
892 if (indexColumns.find((*ci)->getName()) != indexColumns.end() ||
893 specialColumns.find((*ci)->getName()) != specialColumns.end())
899 /* Store a reference to the column info in the lvCol2tci map. */
900 lvCol2tci.insert(lvCol2tci.end(), ci);
902 const TableColumnFormat* tcf =
903 tab->getColumnFormat((*ci)->getName());
904 QString title = tcf->getTitle();
905 if (!(*ci)->getTitle().isEmpty())
906 title = (*ci)->getTitle();
907 listView->addColumn(title + "\n");
908 listView->setColumnAlignment(col, tcf->getHAlign());
913 TjReport::collapsReportItem(QListViewItem*)
920 syncVSlidersGantt2List(ganttChartView->contentsX(), listView->contentsY());
924 TjReport::expandReportItem(QListViewItem*)
930 syncVSlidersGantt2List(ganttChartView->contentsX(), listView->contentsY());
934 TjReport::listClicked(QListViewItem* lvi, const QPoint&, int column)
936 // The first column is always the name and the second column is the hidden
937 // sort index. Both are not in the TCI table. All clickable columns have
939 if (!lvi || column <= 1 || !lvi->pixmap(column))
942 CoreAttributes* ca = lvi2caDict[QString().sprintf("%p", lvi)];
943 const TableColumnInfo* tci = lvCol2tci[column - 2];
945 if (ca->getType() == CA_Task &&
946 tci->getName() == "note" &&
947 !(dynamic_cast<Task*>(ca))->getNote().isEmpty())
949 Task* t = dynamic_cast<Task*>(ca);
950 // Open a new window that displays the note attached to the task.
951 RichTextDisplay* richTextDisplay =
952 new RichTextDisplay(topLevelWidget());
953 richTextDisplay->setCaption(i18n("Note for Task %1 (%2) - TaskJuggler")
954 .arg(t->getName()).arg(t->getId()));
955 richTextDisplay->textDisplay->setTextFormat(Qt::RichText);
957 richTextDisplay->textDisplay->setText(t->getNote());
958 richTextDisplay->show();
960 else if (ca->getType() == CA_Task &&
961 tci->getName() == "statusnote" &&
962 !(dynamic_cast<Task*>(ca))->getStatusNote(scenario).isEmpty())
964 Task* t = dynamic_cast<Task*>(ca);
965 // Open a new window that displays the note attached to the task.
966 RichTextDisplay* richTextDisplay =
967 new RichTextDisplay(topLevelWidget());
968 richTextDisplay->setCaption
969 (i18n("Status Note for Task %1 (%2) - TaskJuggler")
970 .arg(t->getName()).arg(t->getId()));
971 richTextDisplay->textDisplay->setTextFormat(Qt::RichText);
973 richTextDisplay->textDisplay->setText(t->getStatusNote(scenario));
974 richTextDisplay->show();
976 else if (ca->getCustomAttribute(tci->getName()))
978 switch (ca->getCustomAttribute(tci->getName())->getType())
984 const TextAttribute* textAttr =
985 dynamic_cast<const TextAttribute*>
986 (ca->getCustomAttribute(tci->getName()));
987 RichTextDisplay* richTextDisplay =
988 new RichTextDisplay(topLevelWidget());
989 richTextDisplay->setCaption
990 (i18n("%1 for %2 %3 (%4) - TaskJuggler")
992 .arg(ca->getType() == CA_Task ? i18n("Task") :
996 richTextDisplay->textDisplay->setTextFormat(Qt::RichText);
998 richTextDisplay->textDisplay->setText(textAttr->getText());
999 richTextDisplay->show();
1004 const ReferenceAttribute* refAttr =
1005 dynamic_cast<const ReferenceAttribute*>
1006 (ca->getCustomAttribute(tci->getName()));
1007 KRun::runURL(KURL(refAttr->getURL()), "text/html");
1015 TjReport::listHeaderClicked(int)
1021 TjReport::doPopupMenu(QListViewItem* lvi, const QPoint& pos, int)
1026 CoreAttributes* ca = lvi2caDict[QString().sprintf("%p", lvi)];
1028 if (ca->getType() == CA_Task)
1030 Task* t = dynamic_cast<Task*>(ca);
1032 menu.insertItem(i18n("&Edit Task"), 1);
1033 menu.insertItem(i18n("Show Task &Details"), 2);
1034 //menu.insertItem(i18n("&Zoom to fit Task"), 3);
1035 switch (menu.exec(pos))
1038 emit signalEditCoreAttributes(ca);
1051 Resource* r = dynamic_cast<Resource*>(ca);
1053 menu.insertItem(i18n("&Edit Resource"), 1);
1054 menu.insertItem(i18n("Show Resource &Details"), 2);
1055 switch (menu.exec(pos))
1058 emit signalEditCoreAttributes(ca);
1061 showResourceDetails(r);
1070 TjReport::showTaskDetails(const Task* task)
1072 RichTextDisplay* richTextDisplay = new RichTextDisplay(topLevelWidget());
1073 richTextDisplay->setCaption
1074 (i18n("Details of Task %1 (%2) - TaskJuggler")
1075 .arg(task->getName()).arg(task->getId()));
1076 richTextDisplay->textDisplay->setTextFormat(Qt::RichText);
1079 text = i18n("<b>Task:</b> %1 (%2)<br/>")
1080 .arg(task->getName())
1081 .arg(task->getId());
1083 if (!task->getNote().isEmpty())
1085 if (!text.isEmpty())
1087 text += i18n("<b>Note:</b> %1<br/>").arg(task->getNote());
1090 if (task->isMilestone())
1092 if (!text.isEmpty())
1094 text += i18n("<b>Date:</b> %1<br/>")
1095 .arg(time2tjp(task->getStart(scenario)));
1099 if (!text.isEmpty())
1101 text += i18n("<b>Start:</b> %1<br/>"
1102 "<b>End:</b> %2<br/>"
1103 "<b>Status:</b> %3<br/>")
1104 .arg(time2tjp(task->getStart(scenario)))
1105 .arg(time2tjp(task->getEnd(scenario) + 1))
1106 .arg(task->getStatusText(scenario));
1108 if (task->getEffort(scenario) > 0.0)
1110 const ReportElement* reportElement = getReportElement();
1111 double completion = task->getCompletionDegree(scenario) / 100.0;
1112 text += i18n("<hr/><b>Effort:</b> %1<br/>")
1113 .arg(reportElement->scaledLoad
1114 (task->getEffort(scenario), report->getNumberFormat(),
1116 if (completion < 0.0)
1118 text += i18n("<b>Completion degree:</b> in progress<br/>");
1120 else if (completion == 0.0)
1122 text += i18n("<b>Completion degree:</b> 0%<br/>");
1124 else if (completion >= 1.0)
1126 text += i18n("<b>Completion degree:</b> 100%<br/>");
1130 text += i18n("<b>Completion degree:</b> %1%<br/>"
1131 "<b>Done effort:</b> %2<br/>"
1132 "<b>Remaining effort:</b> %3<br/>")
1133 .arg(task->getCompletionDegree(scenario))
1134 .arg(reportElement->scaledLoad
1135 (task->getEffort(scenario) * completion,
1136 report->getNumberFormat(), true, false))
1137 .arg(reportElement->scaledLoad
1138 (task->getEffort(scenario) * (1.0 - completion),
1139 report->getNumberFormat(), true, false));
1143 if (!task->getStatusNote(scenario).isEmpty())
1144 text += i18n("<b>Note:</b> %1<br/>")
1145 .arg(task->getStatusNote(scenario));
1148 QString predecessors;
1149 for (TaskListIterator tli(task->getPreviousIterator()); *tli; ++tli)
1150 predecessors += "<li>" + (*tli)->getName() + " (" + (*tli)->getId() +
1154 for (TaskListIterator tli(task->getFollowersIterator()); *tli; ++tli)
1155 successors += "<li>" + (*tli)->getName() + " (" + (*tli)->getId() +
1158 if (!predecessors.isEmpty() || !successors.isEmpty())
1161 if (!predecessors.isEmpty())
1162 text += i18n("<b>Predecessors:</b><ul>%1</ul><br/>")
1164 if (!successors.isEmpty())
1165 text += i18n("<b>Successors:</b><ul>%1</ul><br/>").arg(successors);
1168 ResourceListIterator rli = task->getBookedResourcesIterator(scenario);
1171 text += "<hr/><b>Allocated resources:</b><ul>";
1173 text += "<li>" + (*rli)->getName() + " (" + (*rli)->getId() +
1178 text += generateRTCustomAttributes(task);
1180 if (task->hasJournal())
1183 text += generateJournal(task->getJournalIterator());
1186 richTextDisplay->textDisplay->setText(text);
1187 richTextDisplay->show();
1191 TjReport::showResourceDetails(Resource* resource)
1193 RichTextDisplay* richTextDisplay = new RichTextDisplay(topLevelWidget());
1194 richTextDisplay->setCaption
1195 (i18n("Details of Resource %1 (%2) - TaskJuggler")
1196 .arg(resource->getName()).arg(resource->getFullId()));
1197 richTextDisplay->textDisplay->setTextFormat(Qt::RichText);
1200 const ReportElement* reportElement = this->getReportElement();
1201 Interval iv = Interval(reportElement->getStart(),
1202 reportElement->getEnd());
1203 double load = resource->getEffectiveLoad(scenario, iv);
1204 double freeLoad = resource->getEffectiveFreeLoad(scenario, iv);
1206 text = i18n("<b>Resource:</b> %1 (%2)<br/>")
1207 .arg(resource->getName())
1208 .arg(resource->getId());
1210 text += i18n("<hr><b>Effort:</b> %1 <b>Free Load:</b> %2 "
1211 "<b>Utilization:</b> %3%")
1212 .arg(scaledLoad(load, numberFormat, true))
1213 .arg(scaledLoad(freeLoad, numberFormat, true))
1214 .arg((int) (load / (load + freeLoad) * 100.0));
1216 if (resource->hasJournal())
1218 if (!text.isEmpty())
1220 text += generateJournal(resource->getJournalIterator());
1223 text += generateRTCustomAttributes(resource);
1225 if (resource->hasJournal())
1228 text += generateJournal(resource->getJournalIterator());
1231 richTextDisplay->textDisplay->setText(text);
1232 richTextDisplay->show();
1236 TjReport::generateRTCustomAttributes(const CoreAttributes* ca) const
1238 QDict<CustomAttribute> caDict = ca->getCustomAttributeDict();
1240 QString text = "<hr/>";
1241 if (caDict.isEmpty())
1244 for (QDictIterator<CustomAttribute> cadi(caDict); cadi.current(); ++cadi)
1246 text += "<b>" + cadi.currentKey() + ":</b> ";
1247 CustomAttribute* custAttr = cadi.current();
1248 switch (cadi.current()->getType())
1252 QString label = dynamic_cast<const
1253 ReferenceAttribute*>(custAttr)->getLabel();
1254 QString url = dynamic_cast<const
1255 ReferenceAttribute*>(custAttr)->getURL();
1256 text += "<a href=\"" + url + "\">" +
1257 (label.isEmpty() ? url : label) + "</a>";
1261 text += dynamic_cast<const TextAttribute*>(custAttr)->
1274 TjReport::syncVSlidersGantt2List(int x, int y)
1276 ganttHeaderView->setContentsPos(x, ganttHeaderView->contentsY());
1277 if (y != listView->contentsY())
1279 // To prevent endless loops we need to disconnect the contentsMoving
1280 // signal temoraryly.
1281 disconnect(listView, SIGNAL(contentsMoving(int, int)),
1282 this, SLOT(syncVSlidersList2Gantt(int, int)));
1283 listView->setContentsPos(listView->contentsX(), y);
1284 connect(listView, SIGNAL(contentsMoving(int, int)),
1285 this, SLOT(syncVSlidersList2Gantt(int, int)));
1290 TjReport::syncVSlidersList2Gantt(int, int y)
1292 if (y != ganttChartView->contentsY())
1294 // To prevent endless loops we need to disconnect the contentsMoving
1295 // signal temoraryly.
1296 disconnect(ganttChartView, SIGNAL(contentsMoving(int, int)),
1297 this, SLOT(syncVSlidersGantt2List(int, int)));
1298 ganttChartView->setContentsPos(ganttChartView->contentsX(), y);
1299 connect(ganttChartView, SIGNAL(contentsMoving(int, int)),
1300 this, SLOT(syncVSlidersGantt2List(int, int)));
1305 TjReport::handleMouseEvent(const QMouseEvent* ev)
1308 QListViewItem* lvi = getChartItemBelowCursor(pos);
1312 if (ev->button() == Qt::LeftButton)
1313 listView->setSelected(lvi, true);
1314 else if (ev->button() == Qt::RightButton)
1315 doPopupMenu(lvi, QCursor::pos(), 0);
1319 TjReport::updateStatusBar()
1322 QListViewItem* lvi = getChartItemBelowCursor(pos);
1325 emit signalChangeStatusBar("");
1329 CoreAttributes* ca = lvi2caDict[QString().sprintf("%p", lvi)];
1330 CoreAttributes* parent = lvi2ParentCaDict[QString().sprintf("%p", lvi)];
1332 emit signalChangeStatusBar(this->generateStatusBarText(pos, ca, parent));
1336 TjReport::reportSearchTriggered(const QString&)
1338 triggerChartRegeneration(200);
1342 TjReport::setReportStart(const QDate& d)
1344 ReportElement* reportElement = this->getReportElement();
1345 time_t start = qdate2time(d);
1347 bool clipped = false;
1348 if (start < date2time("2000-01-01"))
1350 start = date2time("2000-01-01");
1353 if (start > date2time("2030-01-01"))
1355 start = date2time("2030-01-01");
1358 if (start + (24 * 60 * 60) > reportElement->getEnd())
1360 start = reportElement->getEnd() - (24 * 60 * 60);
1364 reportController->reportStart->setDate(time2qdate(start));
1366 reportElement->setStart(start);
1367 scaleMode = TjGanttChart::autoZoom;
1372 TjReport::setReportEnd(const QDate& d)
1374 ReportElement* reportElement = this->getReportElement();
1375 time_t end = qdate2time(d);
1377 bool clipped = false;
1378 if (end < date2time("2000-01-01"))
1380 end = date2time("2000-01-01");
1383 if (end > date2time("2030-01-01"))
1385 end = date2time("2030-01-01");
1388 if (end - (24 * 60 * 60) < reportElement->getStart())
1390 end = reportElement->getStart() + (24 * 60 * 60);
1394 reportController->reportEnd->setDate(time2qdate(end));
1396 reportElement->setEnd(end);
1397 scaleMode = TjGanttChart::autoZoom;
1402 TjReport::getChartItemBelowCursor(QPoint& pos)
1404 if (loadingProject || !isVisible() || !ganttChartView->isVisible())
1407 /* Since it is easier to map the global cursor position to the
1408 * ganttChartView coordinates than using the event position we'll got for
1410 pos = ganttChartView->mapFromGlobal(QCursor::pos());
1411 // Make sure the cursor is really above the ganttChartView.
1412 if (pos.x() < 0 || pos.y() < 0 ||
1413 pos.x() > ganttChartView->width() ||
1414 pos.y() > ganttChartView->height())
1417 return listView->itemAt(QPoint(50, pos.y()));
1421 TjReport::zoomTo(const QString& label)
1426 time_t x = ganttChart->x2time(ganttChartView->contentsX());
1427 int y = ganttChartView->contentsY();
1429 if (!ganttChart->zoomTo(label))
1432 canvasFrame->setMaximumWidth(ganttChart->getWidth());
1434 ganttHeaderView->repaint();
1435 ganttChartView->repaint();
1438 ganttHeaderView->setContentsPos(ganttChart->time2x(x), 0);
1439 ganttChartView->setContentsPos(ganttChart->time2x(x), y);
1448 time_t x = ganttChart->x2time(ganttChartView->contentsX());
1449 int y = ganttChartView->contentsY();
1451 if (!ganttChart->zoomIn())
1453 canvasFrame->setMaximumWidth(ganttChart->getWidth());
1455 ganttHeaderView->repaint();
1456 ganttChartView->repaint();
1459 ganttHeaderView->setContentsPos(ganttChart->time2x(x), 0);
1460 ganttChartView->setContentsPos(ganttChart->time2x(x), y);
1462 updateZoomSelector();
1471 time_t x = ganttChart->x2time(ganttChartView->contentsX());
1472 int y = ganttChartView->contentsY();
1474 if (!ganttChart->zoomOut())
1476 canvasFrame->setMaximumWidth(ganttChart->getWidth());
1478 ganttHeaderView->repaint();
1479 ganttChartView->repaint();
1482 ganttHeaderView->setContentsPos(ganttChart->time2x(x), 0);
1483 ganttChartView->setContentsPos(ganttChart->time2x(x), y);
1485 updateZoomSelector();
1493 if (statusBarUpdateTimer)
1494 statusBarUpdateTimer->start(500, false);
1496 updateZoomSelector();
1502 if (statusBarUpdateTimer)
1503 statusBarUpdateTimer->stop();
1509 TjReport::indent(const QString& input, const QListViewItem* lvi, bool right)
1511 // First let's find out how deep we are down the tree;
1512 int level = treeLevel(lvi);
1516 QString spaces = QString().fill(' ', 2 * (maxDepth - level));
1517 return input + spaces;
1521 QString spaces = QString().fill(' ', 2 * level);
1522 return spaces + input;
1527 TjReport::treeLevel(const QListViewItem* lvi) const
1532 while (lvi->parent())
1535 lvi = lvi->parent();
1537 kdFatal() << "Tree level explosion";
1543 TjReport::generateJournal(Journal::Iterator jit) const
1547 for ( ; *jit; ++jit)
1548 text += "<b><i>" + time2user((*jit)->getDate(),
1549 report->getTimeFormat()) +
1550 "</i></b><br/>" + (*jit)->getText() + "<br/>";
1556 TjReport::setGanttChartColors()
1558 ganttChart->setColor("headerBackgroundCol", colorGroup().background());
1559 ganttChart->setColor("headerLineCol", Qt::black);
1560 ganttChart->setColor("headerShadowCol", colorGroup().mid());
1561 ganttChart->setColor("chartBackgroundCol", listView->colorGroup().base());
1562 ganttChart->setColor("chartAltBackgroundCol",
1563 listView->alternateBackground());
1564 ganttChart->setColor("chartTimeOffCol",
1565 listView->alternateBackground().dark(110));
1566 ganttChart->setColor("chartLineCol",
1567 listView->alternateBackground().dark(130));
1568 ganttChart->setColor("hightlightCol", colorGroup().highlight());
1572 TjReport::updateZoomSelector()
1574 manager->updateZoomSelector(ganttChart->getZoomStepLabels(),
1575 ganttChart->getCurrentZoomStep());
1578 #include "TjReport.moc"