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 }