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

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