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 }