File indexing completed on 2025-01-05 03:35:25

0001 /*
0002     File                 : BarPlotDock.cpp
0003     Project              : LabPlot
0004     Description          : Dock widget for the bar plot
0005     --------------------------------------------------------------------
0006     SPDX-FileCopyrightText: 2022-2024 Alexander Semke <alexander.semke@web.de>
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include "BarPlotDock.h"
0011 #include "backend/core/AbstractColumn.h"
0012 #include "backend/lib/macros.h"
0013 #include "commonfrontend/widgets/TreeViewComboBox.h"
0014 #include "kdefrontend/GuiTools.h"
0015 #include "kdefrontend/TemplateHandler.h"
0016 #include "kdefrontend/widgets/BackgroundWidget.h"
0017 #include "kdefrontend/widgets/LineWidget.h"
0018 #include "kdefrontend/widgets/ValueWidget.h"
0019 
0020 #include <QPushButton>
0021 
0022 #include <KConfig>
0023 #include <KLocalizedString>
0024 
0025 BarPlotDock::BarPlotDock(QWidget* parent)
0026     : BaseDock(parent) {
0027     ui.setupUi(this);
0028     setPlotRangeCombobox(ui.cbPlotRanges);
0029     setBaseWidgets(ui.leName, ui.teComment);
0030     setVisibilityWidgets(ui.chkVisible, ui.chkLegendVisible);
0031 
0032     // Tab "General"
0033 
0034     // x-data
0035     cbXColumn = new TreeViewComboBox(ui.tabGeneral);
0036     QSizePolicy sizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
0037     cbXColumn->setSizePolicy(sizePolicy);
0038     static_cast<QVBoxLayout*>(ui.frameXColumn->layout())->insertWidget(0, cbXColumn);
0039     ui.bRemoveXColumn->setIcon(QIcon::fromTheme(QStringLiteral("edit-clear")));
0040 
0041     // y-data
0042     m_buttonNew = new QPushButton();
0043     m_buttonNew->setIcon(QIcon::fromTheme(QStringLiteral("list-add")));
0044 
0045     m_gridLayout = new QGridLayout(ui.frameDataColumns);
0046     m_gridLayout->setContentsMargins(0, 0, 0, 0);
0047     m_gridLayout->setHorizontalSpacing(2);
0048     m_gridLayout->setVerticalSpacing(2);
0049     ui.frameDataColumns->setLayout(m_gridLayout);
0050 
0051     ui.cbType->addItem(i18n("Grouped"));
0052     ui.cbType->addItem(i18n("Stacked"));
0053     ui.cbType->addItem(i18n("Stacked 100%"));
0054 
0055     ui.cbOrientation->addItem(i18n("Horizontal"));
0056     ui.cbOrientation->addItem(i18n("Vertical"));
0057 
0058     // Tab "Bars"
0059     QString msg = i18n("Select the data column for which the properties should be shown and edited");
0060     ui.lNumber->setToolTip(msg);
0061     ui.cbNumber->setToolTip(msg);
0062 
0063     msg = i18n("Specify the factor in percent to control the width of the bar relative to its default value, applying to all bars");
0064     ui.lWidthFactor->setToolTip(msg);
0065     ui.sbWidthFactor->setToolTip(msg);
0066 
0067     // filling
0068     auto* gridLayout = static_cast<QGridLayout*>(ui.tabBars->layout());
0069     backgroundWidget = new BackgroundWidget(ui.tabBars);
0070     gridLayout->addWidget(backgroundWidget, 5, 0, 1, 3);
0071     auto* spacer = new QSpacerItem(72, 18, QSizePolicy::Minimum, QSizePolicy::Fixed);
0072     gridLayout->addItem(spacer, 6, 0, 1, 1);
0073 
0074     // border lines
0075     gridLayout->addWidget(ui.lBorder, 7, 0, 1, 1);
0076     lineWidget = new LineWidget(ui.tabBars);
0077     gridLayout->addWidget(lineWidget, 8, 0, 1, 3);
0078     spacer = new QSpacerItem(18, 10, QSizePolicy::Minimum, QSizePolicy::Expanding);
0079     gridLayout->addItem(spacer, 9, 0, 1, 1);
0080 
0081     // Tab "Values"
0082     auto* hboxLayout = new QHBoxLayout(ui.tabValues);
0083     valueWidget = new ValueWidget(ui.tabValues);
0084     hboxLayout->addWidget(valueWidget);
0085     hboxLayout->setContentsMargins(2, 2, 2, 2);
0086     hboxLayout->setSpacing(2);
0087 
0088     // adjust layouts in the tabs
0089     for (int i = 0; i < ui.tabWidget->count(); ++i) {
0090         auto* layout = dynamic_cast<QGridLayout*>(ui.tabWidget->widget(i)->layout());
0091         if (!layout)
0092             continue;
0093 
0094         layout->setContentsMargins(2, 2, 2, 2);
0095         layout->setHorizontalSpacing(2);
0096         layout->setVerticalSpacing(2);
0097     }
0098 
0099     // SLOTS
0100     // Tab "General"
0101     connect(cbXColumn, &TreeViewComboBox::currentModelIndexChanged, this, &BarPlotDock::xColumnChanged);
0102     connect(ui.bRemoveXColumn, &QPushButton::clicked, this, &BarPlotDock::removeXColumn);
0103     connect(m_buttonNew, &QPushButton::clicked, this, &BarPlotDock::addDataColumn);
0104     connect(ui.cbType, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &BarPlotDock::typeChanged);
0105     connect(ui.cbOrientation, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &BarPlotDock::orientationChanged);
0106 
0107     // Tab "Bars"
0108     connect(ui.cbNumber, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &BarPlotDock::currentBarChanged);
0109     connect(ui.sbWidthFactor, QOverload<int>::of(&QSpinBox::valueChanged), this, &BarPlotDock::widthFactorChanged);
0110 
0111     // template handler
0112     auto* frame = new QFrame(this);
0113     auto* layout = new QHBoxLayout(frame);
0114     layout->setContentsMargins(0, 11, 0, 11);
0115 
0116     auto* templateHandler = new TemplateHandler(this, QLatin1String("BarPlot"));
0117     layout->addWidget(templateHandler);
0118     connect(templateHandler, &TemplateHandler::loadConfigRequested, this, &BarPlotDock::loadConfigFromTemplate);
0119     connect(templateHandler, &TemplateHandler::saveConfigRequested, this, &BarPlotDock::saveConfigAsTemplate);
0120     connect(templateHandler, &TemplateHandler::info, this, &BarPlotDock::info);
0121 
0122     ui.verticalLayout->addWidget(frame);
0123 }
0124 
0125 void BarPlotDock::setBarPlots(QList<BarPlot*> list) {
0126     CONDITIONAL_LOCK_RETURN;
0127     m_barPlots = list;
0128     m_barPlot = list.first();
0129     setAspects(list);
0130 
0131     Q_ASSERT(m_barPlot);
0132     setModel();
0133 
0134     // backgrounds
0135     QList<Background*> backgrounds;
0136     QList<Line*> lines;
0137     QList<Value*> values;
0138     for (auto* plot : m_barPlots) {
0139         backgrounds << plot->backgroundAt(0);
0140         lines << plot->lineAt(0);
0141         values << plot->value();
0142     }
0143 
0144     backgroundWidget->setBackgrounds(backgrounds);
0145     lineWidget->setLines(lines);
0146     valueWidget->setValues(values);
0147 
0148     // show the properties of the first box plot
0149     ui.chkLegendVisible->setChecked(m_barPlot->legendVisible());
0150     ui.chkVisible->setChecked(m_barPlot->isVisible());
0151     load();
0152     cbXColumn->setColumn(m_barPlot->xColumn(), m_barPlot->xColumnPath());
0153     loadDataColumns();
0154 
0155     updatePlotRangeList();
0156 
0157     // set the current locale
0158     updateLocale();
0159 
0160     // SIGNALs/SLOTs
0161     // general
0162     connect(m_barPlot, &BarPlot::typeChanged, this, &BarPlotDock::plotTypeChanged);
0163     connect(m_barPlot, &BarPlot::xColumnChanged, this, &BarPlotDock::plotXColumnChanged);
0164     connect(m_barPlot, &BarPlot::dataColumnsChanged, this, &BarPlotDock::plotDataColumnsChanged);
0165 
0166     connect(m_barPlot, &BarPlot::widthFactorChanged, this, &BarPlotDock::plotWidthFactorChanged);
0167 }
0168 
0169 void BarPlotDock::setModel() {
0170     auto* model = aspectModel();
0171     model->enablePlottableColumnsOnly(true);
0172     model->enableShowPlotDesignation(true);
0173 
0174     QList<AspectType> list{AspectType::Column};
0175     model->setSelectableAspects(list);
0176 
0177     list = {AspectType::Folder,
0178             AspectType::Workbook,
0179             AspectType::Datapicker,
0180             AspectType::DatapickerCurve,
0181             AspectType::Spreadsheet,
0182             AspectType::LiveDataSource,
0183             AspectType::Column,
0184             AspectType::Worksheet,
0185             AspectType::CartesianPlot,
0186             AspectType::XYFitCurve,
0187             AspectType::XYSmoothCurve,
0188             AspectType::CantorWorksheet};
0189 
0190     cbXColumn->setTopLevelClasses(list);
0191     cbXColumn->setModel(model);
0192 }
0193 
0194 /*
0195  * updates the locale in the widgets. called when the application settins are changed.
0196  */
0197 void BarPlotDock::updateLocale() {
0198     lineWidget->updateLocale();
0199 }
0200 
0201 void BarPlotDock::loadDataColumns() {
0202     // add the combobox for the first column, is always present
0203     if (m_dataComboBoxes.count() == 0)
0204         addDataColumn();
0205 
0206     int count = m_barPlot->dataColumns().count();
0207     ui.cbNumber->clear();
0208 
0209     if (count != 0) {
0210         // box plot has already data columns, make sure we have the proper number of comboboxes
0211         int diff = count - m_dataComboBoxes.count();
0212         if (diff > 0) {
0213             for (int i = 0; i < diff; ++i)
0214                 addDataColumn();
0215         } else if (diff < 0) {
0216             for (int i = diff; i != 0; ++i)
0217                 removeDataColumn();
0218         }
0219 
0220         // show the columns in the comboboxes
0221         auto* model = aspectModel();
0222         for (int i = 0; i < count; ++i) {
0223             m_dataComboBoxes.at(i)->setModel(model); // the model might have changed in-between, reset the current model
0224             m_dataComboBoxes.at(i)->setAspect(m_barPlot->dataColumns().at(i));
0225         }
0226 
0227         // show columns names in the combobox for the selection of the bar to be modified
0228         for (int i = 0; i < count; ++i)
0229             if (m_barPlot->dataColumns().at(i))
0230                 ui.cbNumber->addItem(m_barPlot->dataColumns().at(i)->name());
0231     } else {
0232         // no data columns set in the box plot yet, we show the first combo box only
0233         m_dataComboBoxes.first()->setAspect(nullptr);
0234         for (int i = 0; i < m_dataComboBoxes.count(); ++i)
0235             removeDataColumn();
0236     }
0237 
0238     // disable data column widgets if we're modifying more than one box plot at the same time
0239     bool enabled = (m_barPlots.count() == 1);
0240     m_buttonNew->setVisible(enabled);
0241     for (auto* cb : m_dataComboBoxes)
0242         cb->setEnabled(enabled);
0243     for (auto* b : m_removeButtons)
0244         b->setVisible(enabled);
0245 
0246     // select the first column after all of them were added to the combobox
0247     ui.cbNumber->setCurrentIndex(0);
0248 }
0249 
0250 void BarPlotDock::setDataColumns() const {
0251     int newCount = m_dataComboBoxes.count();
0252     int oldCount = m_barPlot->dataColumns().count();
0253 
0254     if (newCount > oldCount)
0255         ui.cbNumber->addItem(QString::number(newCount));
0256     else {
0257         if (newCount != 0)
0258             ui.cbNumber->removeItem(ui.cbNumber->count() - 1);
0259     }
0260 
0261     QVector<const AbstractColumn*> columns;
0262 
0263     for (auto* cb : m_dataComboBoxes) {
0264         auto* aspect = cb->currentAspect();
0265         if (aspect && aspect->type() == AspectType::Column)
0266             columns << static_cast<AbstractColumn*>(aspect);
0267     }
0268 
0269     m_barPlot->setDataColumns(columns);
0270 }
0271 
0272 //**********************************************************
0273 //******* SLOTs for changes triggered in BarPlotDock *******
0274 //**********************************************************
0275 //"General"-tab
0276 void BarPlotDock::xColumnChanged(const QModelIndex& index) {
0277     auto aspect = static_cast<AbstractAspect*>(index.internalPointer());
0278     AbstractColumn* column(nullptr);
0279     if (aspect) {
0280         column = dynamic_cast<AbstractColumn*>(aspect);
0281         Q_ASSERT(column);
0282     }
0283 
0284     ui.bRemoveXColumn->setEnabled(column != nullptr);
0285 
0286     CONDITIONAL_LOCK_RETURN;
0287 
0288     for (auto* barPlot : m_barPlots)
0289         barPlot->setXColumn(column);
0290 }
0291 
0292 void BarPlotDock::removeXColumn() {
0293     cbXColumn->setAspect(nullptr);
0294     ui.bRemoveXColumn->setEnabled(false);
0295     for (auto* barPlot : m_barPlots)
0296         barPlot->setXColumn(nullptr);
0297 }
0298 
0299 void BarPlotDock::addDataColumn() {
0300     auto* cb = new TreeViewComboBox(this);
0301 
0302     static const QList<AspectType> list{AspectType::Folder,
0303                                         AspectType::Workbook,
0304                                         AspectType::Datapicker,
0305                                         AspectType::DatapickerCurve,
0306                                         AspectType::Spreadsheet,
0307                                         AspectType::LiveDataSource,
0308                                         AspectType::Column,
0309                                         AspectType::Worksheet,
0310                                         AspectType::CartesianPlot,
0311                                         AspectType::XYFitCurve,
0312                                         AspectType::XYSmoothCurve,
0313                                         AspectType::CantorWorksheet};
0314     cb->setTopLevelClasses(list);
0315     cb->setModel(aspectModel());
0316     connect(cb, &TreeViewComboBox::currentModelIndexChanged, this, &BarPlotDock::dataColumnChanged);
0317 
0318     int index = m_dataComboBoxes.size();
0319 
0320     if (index == 0) {
0321         QSizePolicy sizePolicy1(QSizePolicy::Expanding, QSizePolicy::Preferred);
0322         sizePolicy1.setHorizontalStretch(0);
0323         sizePolicy1.setVerticalStretch(0);
0324         sizePolicy1.setHeightForWidth(cb->sizePolicy().hasHeightForWidth());
0325         cb->setSizePolicy(sizePolicy1);
0326     } else {
0327         auto* button = new QPushButton();
0328         button->setIcon(QIcon::fromTheme(QStringLiteral("list-remove")));
0329         connect(button, &QPushButton::clicked, this, &BarPlotDock::removeDataColumn);
0330         m_gridLayout->addWidget(button, index, 1, 1, 1);
0331         m_removeButtons << button;
0332     }
0333 
0334     m_gridLayout->addWidget(cb, index, 0, 1, 1);
0335     m_gridLayout->addWidget(m_buttonNew, index + 1, 1, 1, 1);
0336 
0337     m_dataComboBoxes << cb;
0338     ui.lDataColumn->setText(i18n("Columns:"));
0339 }
0340 
0341 void BarPlotDock::removeDataColumn() {
0342     auto* sender = static_cast<QPushButton*>(QObject::sender());
0343     if (sender) {
0344         // remove button was clicked, determin which one and
0345         // delete it together with the corresponding combobox
0346         for (int i = 0; i < m_removeButtons.count(); ++i) {
0347             if (sender == m_removeButtons.at(i)) {
0348                 delete m_dataComboBoxes.takeAt(i + 1);
0349                 delete m_removeButtons.takeAt(i);
0350             }
0351         }
0352     } else {
0353         // no sender is available, the function is being called directly in loadDataColumns().
0354         // delete the last remove button together with the corresponding combobox
0355         int index = m_removeButtons.count() - 1;
0356         if (index >= 0) {
0357             delete m_dataComboBoxes.takeAt(index + 1);
0358             delete m_removeButtons.takeAt(index);
0359         }
0360     }
0361 
0362     // TODO
0363     if (!m_removeButtons.isEmpty()) {
0364         ui.lDataColumn->setText(i18n("Columns:"));
0365     } else {
0366         ui.lDataColumn->setText(i18n("Column:"));
0367     }
0368 
0369     if (!m_initializing)
0370         setDataColumns();
0371 }
0372 
0373 void BarPlotDock::dataColumnChanged(const QModelIndex&) {
0374     CONDITIONAL_LOCK_RETURN;
0375 
0376     setDataColumns();
0377 }
0378 
0379 void BarPlotDock::typeChanged(int index) {
0380     CONDITIONAL_LOCK_RETURN;
0381 
0382     auto type = static_cast<BarPlot::Type>(index);
0383     for (auto* barPlot : m_barPlots)
0384         barPlot->setType(type);
0385 }
0386 
0387 void BarPlotDock::orientationChanged(int index) {
0388     CONDITIONAL_LOCK_RETURN;
0389 
0390     auto orientation = BarPlot::Orientation(index);
0391     for (auto* barPlot : m_barPlots)
0392         barPlot->setOrientation(orientation);
0393 }
0394 
0395 //"Box"-tab
0396 /*!
0397  * called when the current bar number was changed, shows the bar properties for the selected bar.
0398  */
0399 void BarPlotDock::currentBarChanged(int index) {
0400     if (index == -1)
0401         return;
0402 
0403     CONDITIONAL_LOCK_RETURN;
0404 
0405     QList<Background*> backgrounds;
0406     QList<Line*> lines;
0407     for (auto* plot : m_barPlots) {
0408         auto* background = plot->backgroundAt(index);
0409         if (background)
0410             backgrounds << background;
0411 
0412         auto* line = plot->lineAt(index);
0413         if (line)
0414             lines << line;
0415     }
0416 
0417     backgroundWidget->setBackgrounds(backgrounds);
0418     lineWidget->setLines(lines);
0419 }
0420 
0421 void BarPlotDock::widthFactorChanged(int value) {
0422     CONDITIONAL_LOCK_RETURN;
0423 
0424     double factor = (double)value / 100.;
0425     for (auto* barPlot : m_barPlots)
0426         barPlot->setWidthFactor(factor);
0427 }
0428 
0429 //*************************************************************
0430 //******* SLOTs for changes triggered in BarPlot ********
0431 //*************************************************************
0432 // general
0433 void BarPlotDock::plotXColumnChanged(const AbstractColumn* column) {
0434     CONDITIONAL_LOCK_RETURN;
0435     cbXColumn->setColumn(column, m_barPlot->xColumnPath());
0436 }
0437 void BarPlotDock::plotDataColumnsChanged(const QVector<const AbstractColumn*>&) {
0438     CONDITIONAL_LOCK_RETURN;
0439     loadDataColumns();
0440 }
0441 void BarPlotDock::plotTypeChanged(BarPlot::Type type) {
0442     CONDITIONAL_LOCK_RETURN;
0443     ui.cbType->setCurrentIndex((int)type);
0444 }
0445 void BarPlotDock::plotOrientationChanged(BarPlot::Orientation orientation) {
0446     CONDITIONAL_LOCK_RETURN;
0447     ui.cbOrientation->setCurrentIndex((int)orientation);
0448 }
0449 
0450 // box
0451 void BarPlotDock::plotWidthFactorChanged(double factor) {
0452     CONDITIONAL_LOCK_RETURN;
0453     ui.sbWidthFactor->setValue(round(factor * 100));
0454 }
0455 
0456 //**********************************************************
0457 //******************** SETTINGS ****************************
0458 //**********************************************************
0459 void BarPlotDock::load() {
0460     // general
0461     ui.cbType->setCurrentIndex((int)m_barPlot->type());
0462     ui.cbOrientation->setCurrentIndex((int)m_barPlot->orientation());
0463 
0464     // box
0465     ui.sbWidthFactor->setValue(round(m_barPlot->widthFactor()) * 100);
0466 }
0467 
0468 void BarPlotDock::loadConfig(KConfig& config) {
0469     KConfigGroup group = config.group(QStringLiteral("BarPlot"));
0470 
0471     // general
0472     ui.cbType->setCurrentIndex(group.readEntry(QStringLiteral("Type"), (int)m_barPlot->type()));
0473     ui.cbOrientation->setCurrentIndex(group.readEntry(QStringLiteral("Orientation"), (int)m_barPlot->orientation()));
0474 
0475     // box
0476     ui.sbWidthFactor->setValue(round(group.readEntry(QStringLiteral("WidthFactor"), m_barPlot->widthFactor()) * 100));
0477     backgroundWidget->loadConfig(group);
0478     lineWidget->loadConfig(group);
0479 
0480     // values
0481     valueWidget->loadConfig(group);
0482 }
0483 
0484 void BarPlotDock::loadConfigFromTemplate(KConfig& config) {
0485     auto name = TemplateHandler::templateName(config);
0486     int size = m_barPlots.size();
0487     if (size > 1)
0488         m_barPlot->beginMacro(i18n("%1 bar plots: template \"%2\" loaded", size, name));
0489     else
0490         m_barPlot->beginMacro(i18n("%1: template \"%2\" loaded", m_barPlot->name(), name));
0491 
0492     this->loadConfig(config);
0493 
0494     m_barPlot->endMacro();
0495 }
0496 
0497 void BarPlotDock::saveConfigAsTemplate(KConfig& config) {
0498     KConfigGroup group = config.group(QStringLiteral("BarPlot"));
0499 
0500     // general
0501     group.writeEntry(QStringLiteral("Type"), ui.cbType->currentIndex());
0502     group.writeEntry(QStringLiteral("Orientation"), ui.cbOrientation->currentIndex());
0503 
0504     // box
0505     group.writeEntry(QStringLiteral("WidthFactor"), ui.sbWidthFactor->value() / 100.0);
0506     backgroundWidget->saveConfig(group);
0507     lineWidget->saveConfig(group);
0508 
0509     // values
0510     valueWidget->saveConfig(group);
0511 
0512     config.sync();
0513 }