File indexing completed on 2024-04-28 17:02:19

0001 /*
0002    This file is part of Massif Visualizer
0003 
0004    Copyright 2014 Milian Wolff <mail@milianw.de>
0005 
0006    This program is free software; you can redistribute it and/or
0007    modify it under the terms of the GNU General Public License as
0008    published by the Free Software Foundation; either version 2 of
0009    the License or (at your option) version 3 or any later version
0010    accepted by the membership of KDE e.V. (or its successor approved
0011    by the membership of KDE e.V.), which shall act as a proxy
0012    defined in Section 14 of version 3 of the license.
0013 
0014    This program is distributed in the hope that it will be useful,
0015    but WITHOUT ANY WARRANTY; without even the implied warranty of
0016    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0017    GNU General Public License for more details.
0018 
0019    You should have received a copy of the GNU General Public License
0020    along with this program.  If not, see <http://www.gnu.org/licenses/>.
0021 */
0022 
0023 #include "charttab.h"
0024 
0025 #include "KChartChart"
0026 #include "KChartGridAttributes"
0027 #include "KChartHeaderFooter"
0028 #include "KChartCartesianCoordinatePlane"
0029 #include "KChartPlotter"
0030 #include "KChartLegend"
0031 #include "KChartDataValueAttributes"
0032 #include "KChartBackgroundAttributes"
0033 #include <KChartFrameAttributes.h>
0034 
0035 #include "visualizer/totalcostmodel.h"
0036 #include "visualizer/detailedcostmodel.h"
0037 #include "visualizer/datatreemodel.h"
0038 #include "visualizer/filtereddatatreemodel.h"
0039 
0040 #include "massifdata/util.h"
0041 #include "massifdata/snapshotitem.h"
0042 #include "massifdata/treeleafitem.h"
0043 #include "massifdata/filedata.h"
0044 
0045 #include "massif-visualizer-settings.h"
0046 
0047 #include <QDebug>
0048 #include <QVBoxLayout>
0049 #include <QPrinter>
0050 #include <QPrintPreviewDialog>
0051 #include <QLabel>
0052 #include <QMenu>
0053 #include <QApplication>
0054 #include <QDesktopWidget>
0055 #include <QSvgGenerator>
0056 #include <QWidgetAction>
0057 #include <QFileDialog>
0058 
0059 #include <KColorScheme>
0060 #include <KLocalizedString>
0061 #include <KStandardAction>
0062 #include <KActionCollection>
0063 #include <KMessageBox>
0064 #include <KFormat>
0065 
0066 using namespace KChart;
0067 using namespace Massif;
0068 
0069 namespace {
0070 
0071 class TimeAxis : public CartesianAxis
0072 {
0073     Q_OBJECT
0074 public:
0075     explicit TimeAxis(AbstractCartesianDiagram* diagram = 0)
0076         : CartesianAxis(diagram)
0077     {}
0078 
0079     virtual const QString customizedLabel(const QString& label) const
0080     {
0081         // squeeze large numbers here
0082         // TODO: when the unit is 'b' also use prettyCost() here
0083         return QString::number(label.toDouble());
0084     }
0085 };
0086 
0087 class SizeAxis : public CartesianAxis
0088 {
0089     Q_OBJECT
0090 public:
0091     explicit SizeAxis(AbstractCartesianDiagram* diagram = 0)
0092         : CartesianAxis(diagram)
0093     {}
0094 
0095     virtual const QString customizedLabel(const QString& label) const
0096     {
0097         // TODO: change distance between labels to 1024 and simply use prettyCost() here
0098         KFormat format(QLocale::system());
0099         return format.formatByteSize(label.toDouble(), 1, KFormat::MetricBinaryDialect);
0100     }
0101 };
0102 
0103 void markPeak(Plotter* p, const QModelIndex& peak, quint64 cost, const KColorScheme& scheme)
0104 {
0105     QBrush brush = p->model()->data(peak, DatasetBrushRole).value<QBrush>();
0106 
0107     QColor outline = brush.color();
0108     QColor foreground = scheme.foreground().color();
0109     QBrush background = scheme.background();
0110 
0111     DataValueAttributes dataAttributes = p->dataValueAttributes(peak);
0112     dataAttributes.setDataLabel(prettyCost(cost));
0113     dataAttributes.setVisible(true);
0114     dataAttributes.setShowRepetitiveDataLabels(true);
0115     dataAttributes.setShowOverlappingDataLabels(false);
0116 
0117     FrameAttributes frameAttrs = dataAttributes.frameAttributes();
0118     QPen framePen(outline);
0119     framePen.setWidth(2);
0120     frameAttrs.setPen(framePen);
0121     frameAttrs.setVisible(true);
0122     dataAttributes.setFrameAttributes(frameAttrs);
0123 
0124     MarkerAttributes a = dataAttributes.markerAttributes();
0125     a.setMarkerSize(QSizeF(7, 7));
0126     a.setPen(outline);
0127     a.setMarkerStyle(KChart::MarkerAttributes::MarkerDiamond);
0128     a.setVisible(true);
0129     dataAttributes.setMarkerAttributes(a);
0130 
0131     TextAttributes txtAttrs = dataAttributes.textAttributes();
0132     txtAttrs.setPen(foreground);
0133     txtAttrs.setFontSize(Measure(12));
0134     dataAttributes.setTextAttributes(txtAttrs);
0135 
0136     BackgroundAttributes bkgAtt = dataAttributes.backgroundAttributes();
0137 
0138     bkgAtt.setBrush(background);
0139     bkgAtt.setVisible(true);
0140     dataAttributes.setBackgroundAttributes(bkgAtt);
0141 
0142     p->setDataValueAttributes(peak, dataAttributes);
0143 }
0144 
0145 }
0146 
0147 ChartTab::ChartTab(const FileData* data,
0148                    KXMLGUIClient* guiParent, QWidget* parent)
0149   : DocumentTabInterface(data, guiParent, parent)
0150     , m_chart(new Chart(this))
0151     , m_header(new QLabel(this))
0152     , m_totalDiagram(0)
0153     , m_totalCostModel(new TotalCostModel(m_chart))
0154     , m_detailedDiagram(0)
0155     , m_detailedCostModel(new DetailedCostModel(m_chart))
0156     , m_legend(new Legend(m_chart))
0157     , m_print(0)
0158     , m_saveAs(0)
0159     , m_toggleTotal(0)
0160     , m_toggleDetailed(0)
0161     , m_hideFunction(0)
0162     , m_hideOtherFunctions(0)
0163     , m_box(new QSpinBox(this))
0164     , m_settingSelection(false)
0165 {
0166     setXMLFile(QStringLiteral("charttabui.rc"), true);
0167     setupActions();
0168 
0169     setLayout(new QVBoxLayout(this));
0170 
0171     setupGui();
0172 }
0173 
0174 ChartTab::~ChartTab()
0175 {
0176 }
0177 
0178 void ChartTab::setupActions()
0179 {
0180     m_print = KStandardAction::print(this, SLOT(showPrintPreviewDialog()), actionCollection());
0181     actionCollection()->addAction(QStringLiteral("file_print"), m_print);
0182 
0183     m_saveAs = KStandardAction::saveAs(this, SLOT(saveCurrentDocument()), actionCollection());
0184     actionCollection()->addAction(QStringLiteral("file_save_as"), m_saveAs);
0185 
0186     m_toggleTotal = new QAction(QIcon::fromTheme(QStringLiteral("office-chart-area")), i18n("Toggle total cost graph"), actionCollection());
0187     m_toggleTotal->setCheckable(true);
0188     m_toggleTotal->setChecked(true);
0189     connect(m_toggleTotal, &QAction::toggled, this, &ChartTab::showTotalGraph);
0190     actionCollection()->addAction(QStringLiteral("toggle_total"), m_toggleTotal);
0191 
0192     m_toggleDetailed = new QAction(QIcon::fromTheme(QStringLiteral("office-chart-area-stacked")), i18n("Toggle detailed cost graph"), actionCollection());
0193     m_toggleDetailed->setCheckable(true);
0194     m_toggleDetailed->setChecked(true);
0195     connect(m_toggleDetailed, &QAction::toggled, this, &ChartTab::showDetailedGraph);
0196     actionCollection()->addAction(QStringLiteral("toggle_detailed"), m_toggleDetailed);
0197 
0198     QWidgetAction* stackNumAction = new QWidgetAction(actionCollection());
0199     actionCollection()->addAction(QStringLiteral("stackNum"), stackNumAction);
0200     stackNumAction->setText(i18n("Stacked diagrams"));
0201     QWidget *stackNumWidget = new QWidget;
0202     QHBoxLayout* stackNumLayout = new QHBoxLayout;
0203     stackNumLayout->addWidget(new QLabel(i18n("Stacked diagrams:")));
0204     m_box->setMinimum(0);
0205     m_box->setMaximum(50);
0206     m_box->setValue(10);
0207     connect(m_box, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &ChartTab::setStackNum);
0208     stackNumLayout->addWidget(m_box);
0209     stackNumWidget->setLayout(stackNumLayout);
0210     stackNumAction->setDefaultWidget(stackNumWidget);
0211 
0212     m_hideFunction = new QAction(i18n("hide function"), this);
0213     connect(m_hideFunction, &QAction::triggered,
0214             this, &ChartTab::slotHideFunction);
0215     m_hideOtherFunctions = new QAction(i18n("hide other functions"), this);
0216     connect(m_hideOtherFunctions, &QAction::triggered,
0217             this, &ChartTab::slotHideOtherFunctions);
0218 }
0219 
0220 void ChartTab::setupGui()
0221 {
0222     layout()->addWidget(m_header);
0223     layout()->addWidget(m_chart);
0224 
0225     // HACK: otherwise the legend becomes _really_ large and might even crash X...
0226     // to visualize the issue, try: m_chart->setMaximumSize(QSize(10000, 10000));
0227     m_chart->setMaximumSize(qApp->desktop()->size());
0228 
0229     // for axis labels to fit
0230     m_chart->setGlobalLeadingRight(10);
0231     m_chart->setGlobalLeadingLeft(10);
0232     m_chart->setGlobalLeadingTop(20);
0233     m_chart->setContextMenuPolicy(Qt::CustomContextMenu);
0234 
0235     updateLegendPosition();
0236     m_legend->setTitleText(QString());
0237     m_legend->setSortOrder(Qt::DescendingOrder);
0238 
0239     m_chart->addLegend(m_legend);
0240 
0241     //NOTE: this has to be set _after_ the legend was added to the chart...
0242     updateLegendFont();
0243     m_legend->setTextAlignment(Qt::AlignLeft);
0244     m_legend->hide();
0245 
0246     connect(m_chart, &Chart::customContextMenuRequested,
0247             this, &ChartTab::chartContextMenuRequested);
0248 
0249     //BEGIN KChart
0250     KColorScheme scheme(QPalette::Active, KColorScheme::Window);
0251     QPen foreground(scheme.foreground().color());
0252 
0253     //Begin Legend
0254     BackgroundAttributes bkgAtt = m_legend->backgroundAttributes();
0255     QColor background = scheme.background(KColorScheme::AlternateBackground).color();
0256     background.setAlpha(200);
0257     bkgAtt.setBrush(QBrush(background));
0258     bkgAtt.setVisible(true);
0259     m_legend->setBackgroundAttributes(bkgAtt);
0260     TextAttributes txtAttrs = m_legend->textAttributes();
0261     txtAttrs.setPen(foreground);
0262     m_legend->setTextAttributes(txtAttrs);
0263 
0264     m_header->setAlignment(Qt::AlignCenter);
0265     updateHeader();
0266 
0267     //BEGIN TotalDiagram
0268     m_totalDiagram = new Plotter(this);
0269     m_totalDiagram->setAntiAliasing(true);
0270 
0271     CartesianAxis* bottomAxis = new TimeAxis(m_totalDiagram);
0272     TextAttributes axisTextAttributes = bottomAxis->textAttributes();
0273     axisTextAttributes.setPen(foreground);
0274     axisTextAttributes.setFontSize(Measure(10));
0275     bottomAxis->setTextAttributes(axisTextAttributes);
0276     TextAttributes axisTitleTextAttributes = bottomAxis->titleTextAttributes();
0277     axisTitleTextAttributes.setPen(foreground);
0278     axisTitleTextAttributes.setFontSize(Measure(12));
0279     bottomAxis->setTitleTextAttributes(axisTitleTextAttributes);
0280     bottomAxis->setTitleText(i18n("time in %1", m_data->timeUnit()));
0281     bottomAxis->setPosition ( CartesianAxis::Bottom );
0282     m_totalDiagram->addAxis(bottomAxis);
0283 
0284     CartesianAxis* rightAxis = new SizeAxis(m_totalDiagram);
0285     rightAxis->setTextAttributes(axisTextAttributes);
0286     rightAxis->setTitleTextAttributes(axisTitleTextAttributes);
0287     rightAxis->setTitleText(i18n("memory heap size"));
0288     rightAxis->setPosition ( CartesianAxis::Right );
0289     m_totalDiagram->addAxis(rightAxis);
0290 
0291     m_totalCostModel->setSource(m_data);
0292     m_totalDiagram->setModel(m_totalCostModel);
0293 
0294     CartesianCoordinatePlane* coordinatePlane = dynamic_cast<CartesianCoordinatePlane*>(m_chart->coordinatePlane());
0295     Q_ASSERT(coordinatePlane);
0296     coordinatePlane->addDiagram(m_totalDiagram);
0297 
0298     GridAttributes gridAttributes = coordinatePlane->gridAttributes(Qt::Horizontal);
0299     gridAttributes.setAdjustBoundsToGrid(false, false);
0300     coordinatePlane->setGridAttributes(Qt::Horizontal, gridAttributes);
0301 
0302     m_legend->addDiagram(m_totalDiagram);
0303 
0304     m_detailedDiagram = new Plotter;
0305     m_detailedDiagram->setAntiAliasing(true);
0306     m_detailedDiagram->setType(KChart::Plotter::Stacked);
0307 
0308     m_detailedCostModel->setSource(m_data);
0309     m_detailedDiagram->setModel(m_detailedCostModel);
0310 
0311     updatePeaks();
0312 
0313     m_chart->coordinatePlane()->addDiagram(m_detailedDiagram);
0314 
0315     m_legend->addDiagram(m_detailedDiagram);
0316     m_legend->show();
0317 
0318     m_box->setValue(m_detailedCostModel->maximumDatasetCount());
0319 
0320     connect(m_totalDiagram, &Plotter::clicked,
0321             this, &ChartTab::totalItemClicked);
0322     connect(m_detailedDiagram, &Plotter::clicked,
0323             this, &ChartTab::detailedItemClicked);
0324 }
0325 
0326 void ChartTab::settingsChanged()
0327 {
0328     updateHeader();
0329     updatePeaks();
0330     updateLegendPosition();
0331     updateLegendFont();
0332 }
0333 
0334 void ChartTab::setDetailedDiagramHidden(bool hidden)
0335 {
0336     m_detailedDiagram->setHidden(hidden);
0337 }
0338 
0339 void ChartTab::setDetailedDiagramVisible(bool visible)
0340 {
0341     m_detailedDiagram->setVisible(visible);
0342 }
0343 
0344 void ChartTab::setTotalDiagramHidden(bool hidden)
0345 {
0346     m_totalDiagram->setHidden(hidden);
0347 }
0348 
0349 void ChartTab::setTotalDiagramVisible(bool visible)
0350 {
0351     m_totalDiagram->setVisible(visible);
0352 }
0353 
0354 void ChartTab::updatePeaks()
0355 {
0356     KColorScheme scheme(QPalette::Active, KColorScheme::Window);
0357 
0358     if (m_data->peak()) {
0359         const QModelIndex peak = m_totalCostModel->peak();
0360         Q_ASSERT(peak.isValid());
0361         markPeak(m_totalDiagram, peak, m_data->peak()->cost(), scheme);
0362     }
0363     updateDetailedPeaks();
0364 }
0365 
0366 void ChartTab::updateLegendPosition()
0367 {
0368     KChartEnums::PositionValue pos;
0369     switch (Settings::self()->legendPosition()) {
0370         case Settings::EnumLegendPosition::North:
0371             pos = KChartEnums::PositionNorth;
0372             break;
0373         case Settings::EnumLegendPosition::South:
0374             pos = KChartEnums::PositionSouth;
0375             break;
0376         case Settings::EnumLegendPosition::East:
0377             pos = KChartEnums::PositionEast;
0378             break;
0379         case Settings::EnumLegendPosition::West:
0380             pos = KChartEnums::PositionWest;
0381             break;
0382         case Settings::EnumLegendPosition::Floating:
0383             pos = KChartEnums::PositionFloating;
0384             break;
0385         default:
0386             pos = KChartEnums::PositionFloating;
0387             qDebug() << "invalid legend position";
0388     }
0389     m_legend->setPosition(Position(pos));
0390 
0391     Qt::Alignment align;
0392     switch (Settings::self()->legendAlignment()) {
0393         case Settings::EnumLegendAlignment::Left:
0394             align = Qt::AlignLeft;
0395             break;
0396         case Settings::EnumLegendAlignment::Center:
0397             align = Qt::AlignHCenter | Qt::AlignVCenter;
0398             break;
0399         case Settings::EnumLegendAlignment::Right:
0400             align = Qt::AlignRight;
0401             break;
0402         case Settings::EnumLegendAlignment::Top:
0403             align = Qt::AlignTop;
0404             break;
0405         case Settings::EnumLegendAlignment::Bottom:
0406             align = Qt::AlignBottom;
0407             break;
0408         default:
0409             align = Qt::AlignHCenter | Qt::AlignVCenter;
0410             qDebug() << "invalid legend alignmemnt";
0411     }
0412 
0413     // do something reasonable since top,bottom have no effect
0414     // when used with north,south, same for left,right used with
0415     // east,west
0416     if ((((pos == KChartEnums::PositionNorth) || (pos == KChartEnums::PositionSouth))
0417          && ((align == Qt::AlignTop) || (align == Qt::AlignBottom)))
0418          || (((pos == KChartEnums::PositionEast) || (pos == KChartEnums::PositionWest))
0419          && ((align == Qt::AlignLeft) || (align == Qt::AlignRight)))) {
0420 
0421          align = Qt::AlignHCenter | Qt::AlignVCenter;
0422     }
0423 
0424     m_legend->setAlignment(align);
0425 }
0426 
0427 void ChartTab::updateLegendFont()
0428 {
0429     TextAttributes att = m_legend->textAttributes();
0430     att.setAutoShrink(true);
0431     att.setFontSize(Measure(Settings::self()->legendFontSize()));
0432     QFont font(QStringLiteral("monospace"));
0433     font.setStyleHint(QFont::TypeWriter);
0434     att.setFont(font);
0435     m_legend->setTextAttributes(att);
0436 }
0437 
0438 void ChartTab::updateDetailedPeaks()
0439 {
0440     KColorScheme scheme(QPalette::Active, KColorScheme::Window);
0441 
0442     const DetailedCostModel::Peaks& peaks = m_detailedCostModel->peaks();
0443     DetailedCostModel::Peaks::const_iterator it = peaks.constBegin();
0444     while (it != peaks.constEnd()) {
0445         const QModelIndex peak = it.key();
0446         Q_ASSERT(peak.isValid());
0447         markPeak(m_detailedDiagram, peak, it.value()->cost(), scheme);
0448         ++it;
0449     }
0450 }
0451 
0452 void ChartTab::updateHeader()
0453 {
0454     const QString app = m_data->cmd().split(QLatin1Char(' '), QString::SkipEmptyParts).first();
0455 
0456     m_header->setText(QString::fromLatin1("<b>%1</b><br /><i>%2</i>")
0457                         .arg(i18n("Memory consumption of %1", app))
0458                         .arg(i18n("Peak of %1 at snapshot #%2", prettyCost(m_data->peak()->cost()), m_data->peak()->number()))
0459     );
0460     m_header->setToolTip(i18n("Command: %1\nValgrind Options: %2", m_data->cmd(), m_data->description()));
0461 }
0462 
0463 void ChartTab::saveCurrentDocument()
0464 {
0465     const auto saveFilename = QFileDialog::getSaveFileName(this, i18n("Save Current Visualization"), QString(),
0466                                                            i18n("Images (*.png *.jpg *.tiff *.svg)"));
0467 
0468     if (!saveFilename.isEmpty()) {
0469 
0470         // TODO: implement a dialog to expose more options to the user.
0471         // for example we could expose dpi, size, and various format
0472         // dependent options such as compressions settings.
0473 
0474         // Vector graphic format
0475         if (QFileInfo(saveFilename).suffix().compare(QLatin1String("svg")) == 0) {
0476             QSvgGenerator generator;
0477             generator.setFileName(saveFilename);
0478             generator.setSize(m_chart->size());
0479             generator.setViewBox(m_chart->rect());
0480 
0481             QPainter painter;
0482             painter.begin(&generator);
0483             m_chart->paint(&painter, m_chart->rect());
0484             painter.end();
0485         }
0486 
0487         // Other format
0488         else if (!QPixmap::grabWidget(m_chart).save(saveFilename)) {
0489 
0490             KMessageBox::sorry(this, QString(
0491                 i18n("Error, failed to save the image to %1.")
0492                     ).arg(saveFilename));
0493         }
0494     }
0495 }
0496 
0497 void ChartTab::showPrintPreviewDialog()
0498 {
0499     QPrinter printer;
0500     QPrintPreviewDialog *ppd = new QPrintPreviewDialog(&printer, this);
0501     ppd->setAttribute(Qt::WA_DeleteOnClose);
0502     connect(ppd, &QPrintPreviewDialog::paintRequested, this, &ChartTab::printFile);
0503     ppd->setWindowTitle(i18n("Massif Chart Print Preview"));
0504     ppd->resize(800, 600);
0505     ppd->exec();
0506 }
0507 
0508 void ChartTab::printFile(QPrinter *printer)
0509 {
0510     QPainter painter;
0511     painter.begin(printer);
0512     m_chart->paint(&painter, printer->pageRect());
0513     painter.end();
0514 }
0515 
0516 void ChartTab::showDetailedGraph(bool show)
0517 {
0518     m_detailedDiagram->setHidden(!show);
0519     m_toggleDetailed->setChecked(show);
0520     m_chart->update();
0521 }
0522 
0523 void ChartTab::showTotalGraph(bool show)
0524 {
0525     m_totalDiagram->setHidden(!show);
0526     m_toggleTotal->setChecked(show);
0527     m_chart->update();
0528 }
0529 
0530 void ChartTab::slotHideFunction()
0531 {
0532     m_detailedCostModel->hideFunction(m_hideFunction->data().value<const TreeLeafItem*>());
0533 }
0534 
0535 void ChartTab::slotHideOtherFunctions()
0536 {
0537     m_detailedCostModel->hideOtherFunctions(m_hideOtherFunctions->data().value<const TreeLeafItem*>());
0538 }
0539 
0540 void ChartTab::chartContextMenuRequested(const QPoint& pos)
0541 {
0542     const QPoint dPos = m_detailedDiagram->mapFromGlobal(m_chart->mapToGlobal(pos));
0543 
0544     const QModelIndex idx = m_detailedDiagram->indexAt(dPos);
0545     if (!idx.isValid()) {
0546         return;
0547     }
0548     // hack: the ToolTip will only be queried by KChart and that one uses the
0549     // left index, but we want it to query the right one
0550     const QModelIndex _idx = m_detailedCostModel->index(idx.row() + 1, idx.column(), idx.parent());
0551     ModelItem item = m_detailedCostModel->itemForIndex(_idx);
0552 
0553     if (!item.first) {
0554         return;
0555     }
0556 
0557     QMenu menu;
0558 
0559     m_hideFunction->setData(QVariant::fromValue(item.first));
0560     menu.addAction(m_hideFunction);
0561 
0562     m_hideOtherFunctions->setData(QVariant::fromValue(item.first));
0563     menu.addAction(m_hideOtherFunctions);
0564 
0565     menu.addSeparator();
0566 
0567     emit contextMenuRequested(item, &menu);
0568 
0569     menu.exec(m_detailedDiagram->mapToGlobal(dPos));
0570 }
0571 
0572 void ChartTab::setStackNum(int num)
0573 {
0574     m_detailedCostModel->setMaximumDatasetCount(num);
0575     updatePeaks();
0576 }
0577 
0578 void ChartTab::detailedItemClicked(const QModelIndex& idx)
0579 {
0580     m_detailedCostModel->setSelection(idx);
0581     m_totalCostModel->setSelection(QModelIndex());
0582     m_chart->update();
0583 
0584     // hack: the ToolTip will only be queried by KChart and that one uses the
0585     // left index, but we want it to query the right one
0586     m_settingSelection = true;
0587     const QModelIndex _idx = m_detailedCostModel->index(idx.row() + 1, idx.column(), idx.parent());
0588     emit modelItemSelected(m_detailedCostModel->itemForIndex(_idx));
0589     m_settingSelection = false;
0590 }
0591 
0592 void ChartTab::totalItemClicked(const QModelIndex& idx)
0593 {
0594     const QModelIndex _idx = m_totalCostModel->index(idx.row() + 1, idx.column(), idx.parent());
0595     m_totalCostModel->setSelection(_idx);
0596     m_detailedCostModel->setSelection(QModelIndex());
0597     m_chart->update();
0598 
0599     m_settingSelection = true;
0600     emit modelItemSelected(m_totalCostModel->itemForIndex(_idx));
0601     m_settingSelection = false;
0602 }
0603 
0604 void ChartTab::selectModelItem(const ModelItem& item)
0605 {
0606     if (m_settingSelection) {
0607         return;
0608     }
0609 
0610     if (item.first) {
0611         m_detailedCostModel->setSelection(m_detailedCostModel->indexForItem(item));
0612         m_totalCostModel->setSelection(QModelIndex());
0613     } else {
0614         m_totalCostModel->setSelection(m_totalCostModel->indexForItem(item));
0615         m_detailedCostModel->setSelection(QModelIndex());
0616     }
0617 
0618     m_chart->update();
0619 }
0620 
0621 #include "charttab.moc"