File indexing completed on 2025-10-12 03:31:31

0001 /*
0002     File                 : DatapickerImageWidget.cpp
0003     Project              : LabPlot
0004     Description          : widget for datapicker properties
0005     --------------------------------------------------------------------
0006     SPDX-FileCopyrightText: 2015-2016 Ankit Wagadre <wagadre.ankit@gmail.com>
0007     SPDX-FileCopyrightText: 2015-2021 Alexander Semke <alexander.semke@web.de>
0008 
0009     SPDX-License-Identifier: GPL-2.0-or-later
0010 */
0011 
0012 #include "DatapickerImageWidget.h"
0013 #include "backend/core/Project.h"
0014 #include "backend/datapicker/DatapickerPoint.h"
0015 #include "backend/datapicker/ImageEditor.h"
0016 #include "commonfrontend/widgets/qxtspanslider.h"
0017 #include "kdefrontend/GuiTools.h"
0018 #include "kdefrontend/widgets/SymbolWidget.h"
0019 
0020 #include <KConfigGroup>
0021 #include <KLocalizedString>
0022 
0023 #include <QCompleter>
0024 #include <QDir>
0025 // see https://gitlab.kitware.com/cmake/cmake/-/issues/21609
0026 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
0027 #include <QFileSystemModel>
0028 #else
0029 #include <QDirModel>
0030 #endif
0031 #include <QGraphicsScene>
0032 #include <QPainter>
0033 #include <QStandardPaths>
0034 
0035 #include <cmath>
0036 
0037 HistogramView::HistogramView(QWidget* parent, int range)
0038     : QGraphicsView(parent)
0039     , m_scene(new QGraphicsScene())
0040     , m_range(range) {
0041     setTransform(QTransform());
0042     QRectF pageRect(0, 0, 1000, 100);
0043     m_scene->setSceneRect(pageRect);
0044     setScene(m_scene);
0045     setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
0046     setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
0047 
0048     m_lowerSlider = new QGraphicsRectItem(pageRect, nullptr);
0049     m_lowerSlider->setPen(QPen(Qt::black, 0.5));
0050     m_lowerSlider->setBrush(Qt::blue);
0051     m_lowerSlider->setOpacity(0.2);
0052     m_scene->addItem(m_lowerSlider);
0053 
0054     m_upperSlider = new QGraphicsRectItem(pageRect, nullptr);
0055     m_upperSlider->setPen(QPen(Qt::black, 0.5));
0056     m_upperSlider->setBrush(Qt::blue);
0057     m_upperSlider->setOpacity(0.2);
0058     m_scene->addItem(m_upperSlider);
0059 }
0060 
0061 void HistogramView::setScalePixmap(const QString& file) {
0062     // scene rect is 1000*100 where upper 1000*80 is for histogram graph
0063     // and lower 1000*20 is for histogram scale
0064     auto* pixmap = new QGraphicsPixmapItem(QPixmap(file).scaled(1000, 20, Qt::IgnoreAspectRatio), nullptr);
0065     pixmap->setZValue(-1);
0066     pixmap->setPos(0, 90);
0067     m_scene->addItem(pixmap);
0068 }
0069 
0070 void HistogramView::setSpan(int l, int h) {
0071     l = l * 1000 / m_range;
0072     h = h * 1000 / m_range;
0073     m_lowerSlider->setPos(QPointF(l - 1000, 0));
0074     m_upperSlider->setPos(QPointF(h, 0));
0075     invalidateScene(sceneRect(), QGraphicsScene::BackgroundLayer);
0076 }
0077 
0078 void HistogramView::resizeEvent(QResizeEvent* event) {
0079     fitInView(m_scene->sceneRect(), Qt::IgnoreAspectRatio);
0080     QGraphicsView::resizeEvent(event);
0081 }
0082 
0083 void HistogramView::drawBackground(QPainter* painter, const QRectF& rect) {
0084     if (!bins)
0085         return;
0086 
0087     painter->save();
0088     painter->setRenderHint(QPainter::Antialiasing, true);
0089     int max = 1;
0090     for (int i = 0; i <= m_range; i++)
0091         if (bins[i] > max)
0092             max = bins[i];
0093 
0094     // convert y-scale count to log scale so small counts are still visible
0095     // scene rect is 1000*100 where upper 1000*80 is for histogram graph
0096     // and lower 1000*20 is for histogram scale
0097     QPainterPath path(QPointF(0, (log(bins[0]) * 100 / log(max))));
0098     for (int i = 1; i <= m_range; i++) {
0099         int x = i * 1000 / m_range;
0100         int y = 80;
0101         if (bins[i] > 1)
0102             y = 80 - (log(bins[i]) * 80 / log(max));
0103 
0104         path.lineTo(QPointF(x, y));
0105     }
0106 
0107     painter->drawPath(path);
0108     invalidateScene(rect, QGraphicsScene::BackgroundLayer);
0109     painter->restore();
0110 }
0111 
0112 DatapickerImageWidget::DatapickerImageWidget(QWidget* parent)
0113     : BaseDock(parent)
0114     , m_image(nullptr) {
0115     ui.setupUi(this);
0116     setBaseWidgets(ui.leName, ui.teComment);
0117 
0118     //"General"-tab
0119     ui.leFileName->setClearButtonEnabled(true);
0120     ui.bOpen->setIcon(QIcon::fromTheme(QStringLiteral("document-open")));
0121 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
0122     ui.leFileName->setCompleter(new QCompleter(new QFileSystemModel, this));
0123 #else
0124     ui.leFileName->setCompleter(new QCompleter(new QDirModel, this));
0125 #endif
0126 
0127     //"Symbol"-tab
0128     symbolWidget = new SymbolWidget(ui.tSymbol);
0129     auto* gridLayout = dynamic_cast<QGridLayout*>(ui.tSymbol->layout());
0130     if (gridLayout)
0131         gridLayout->addWidget(symbolWidget, 0, 0, 1, 1);
0132 
0133     //"Edit Image"-tab
0134     auto* editTabLayout = static_cast<QGridLayout*>(ui.tEdit->layout());
0135     editTabLayout->setContentsMargins(2, 2, 2, 2);
0136     editTabLayout->setHorizontalSpacing(2);
0137     editTabLayout->setVerticalSpacing(4);
0138 
0139     ssHue = new SpanSlider(Qt::Horizontal, ui.tEdit);
0140     ssHue->setToolTip(i18n("Select the range for the hue.\nEverything outside of this range will be set to white."));
0141     ssHue->setRange(0, 360);
0142     editTabLayout->addWidget(ssHue, 3, 2);
0143 
0144     ssSaturation = new SpanSlider(Qt::Horizontal, ui.tEdit);
0145     ssSaturation->setToolTip(i18n("Select the range for the saturation.\nEverything outside of this range will be set to white."));
0146     ssSaturation->setRange(0, 100);
0147     editTabLayout->addWidget(ssSaturation, 5, 2);
0148 
0149     ssValue = new SpanSlider(Qt::Horizontal, ui.tEdit);
0150     ssValue->setToolTip(i18n("Select the range for the value, the degree of lightness of the color.\nEverything outside of this range will be set to white."));
0151     ssValue->setRange(0, 100);
0152     editTabLayout->addWidget(ssValue, 7, 2);
0153 
0154     ssIntensity = new SpanSlider(Qt::Horizontal, ui.tEdit);
0155     ssIntensity->setToolTip(i18n("Select the range for the intensity.\nEverything outside of this range will be set to white."));
0156     ssIntensity->setRange(0, 100);
0157     editTabLayout->addWidget(ssIntensity, 9, 2);
0158 
0159     ssForeground = new SpanSlider(Qt::Horizontal, ui.tEdit);
0160     ssForeground->setToolTip(
0161         i18n("Select the range for the colors that are not part of the background color.\nEverything outside of this range will be set to white."));
0162     ssForeground->setRange(0, 100);
0163     editTabLayout->addWidget(ssForeground, 11, 2);
0164 
0165     ui.cbGraphType->addItem(i18n("Cartesian (x, y)"), (int)DatapickerImage::GraphType::Linear);
0166     ui.cbGraphType->addItem(i18n("Polar (x, y°)"), (int)DatapickerImage::GraphType::PolarInDegree);
0167     ui.cbGraphType->addItem(i18n("Polar (x, y(rad))"), (int)DatapickerImage::GraphType::PolarInRadians);
0168     ui.cbGraphType->addItem(i18n("Logarithmic (ln(x), ln(y))"), (int)DatapickerImage::GraphType::LnXY);
0169     ui.cbGraphType->addItem(i18n("Logarithmic (ln(x), y)"), (int)DatapickerImage::GraphType::LnX);
0170     ui.cbGraphType->addItem(i18n("Logarithmic (x, ln(y))"), (int)DatapickerImage::GraphType::LnY);
0171     ui.cbGraphType->addItem(i18n("Logarithmic (log(x), log(y))"), (int)DatapickerImage::GraphType::Log10XY);
0172     ui.cbGraphType->addItem(i18n("Logarithmic (log(x), y)"), (int)DatapickerImage::GraphType::Log10X);
0173     ui.cbGraphType->addItem(i18n("Logarithmic (x, log(y))"), (int)DatapickerImage::GraphType::Log10Y);
0174     ui.cbGraphType->addItem(i18n("Ternary (x, y, z)"), (int)DatapickerImage::GraphType::Ternary);
0175 
0176     ui.lTernaryScale->setHidden(true);
0177     ui.sbTernaryScale->setHidden(true);
0178     ui.lPositionZ1->setHidden(true);
0179     ui.lPositionZ2->setHidden(true);
0180     ui.lPositionZ3->setHidden(true);
0181     ui.sbPositionZ1->setHidden(true);
0182     ui.sbPositionZ2->setHidden(true);
0183     ui.sbPositionZ3->setHidden(true);
0184 
0185     ui.cbPlotImageType->addItem(i18n("No Image"));
0186     ui.cbPlotImageType->addItem(i18n("Original Image"));
0187     ui.cbPlotImageType->addItem(i18n("Processed Image"));
0188 
0189     QString valueFile = QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("pics/colorchooser/colorchooser_value.xpm"));
0190     QString hueFile = QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("pics/colorchooser/colorchooser_hue.xpm"));
0191     QString saturationFile = QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("pics/colorchooser/colorchooser_saturation.xpm"));
0192 
0193     gvHue = new HistogramView(ui.tEdit, ImageEditor::colorAttributeMax(DatapickerImage::ColorAttributes::Hue));
0194     gvHue->setToolTip(i18n("Select the range for the hue.\nEverything outside of this range will be set to white."));
0195     editTabLayout->addWidget(gvHue, 2, 2);
0196     gvHue->setScalePixmap(hueFile);
0197 
0198     gvSaturation = new HistogramView(ui.tEdit, ImageEditor::colorAttributeMax(DatapickerImage::ColorAttributes::Saturation));
0199     gvSaturation->setToolTip(i18n("Select the range for the saturation.\nEverything outside of this range will be set to white."));
0200     editTabLayout->addWidget(gvSaturation, 4, 2);
0201     gvSaturation->setScalePixmap(saturationFile);
0202 
0203     gvValue = new HistogramView(ui.tEdit, ImageEditor::colorAttributeMax(DatapickerImage::ColorAttributes::Value));
0204     gvValue->setToolTip(i18n("Select the range for the value, the degree of lightness of the color.\nEverything outside of this range will be set to white."));
0205     editTabLayout->addWidget(gvValue, 6, 2);
0206     gvValue->setScalePixmap(valueFile);
0207 
0208     gvIntensity = new HistogramView(ui.tEdit, ImageEditor::colorAttributeMax(DatapickerImage::ColorAttributes::Intensity));
0209     gvIntensity->setToolTip(i18n("Select the range for the intensity.\nEverything outside of this range will be set to white."));
0210     editTabLayout->addWidget(gvIntensity, 8, 2);
0211     gvIntensity->setScalePixmap(valueFile);
0212 
0213     gvForeground = new HistogramView(ui.tEdit, ImageEditor::colorAttributeMax(DatapickerImage::ColorAttributes::Foreground));
0214     gvForeground->setToolTip(
0215         i18n("Select the range for the colors that are not part of the background color.\nEverything outside of this range will be set to white."));
0216     editTabLayout->addWidget(gvForeground, 10, 2);
0217     gvForeground->setScalePixmap(valueFile);
0218 
0219     DatapickerImageWidget::updateLocale();
0220 
0221     // SLOTS
0222     // general
0223     connect(ui.bOpen, &QPushButton::clicked, this, &DatapickerImageWidget::selectFile);
0224     connect(ui.leFileName, &QLineEdit::returnPressed, this, &DatapickerImageWidget::fileNameChanged);
0225     connect(ui.leFileName, &QLineEdit::textChanged, this, &DatapickerImageWidget::fileNameChanged);
0226     connect(ui.cbFileRelativePath, &QCheckBox::clicked, this, &DatapickerImageWidget::relativeChanged);
0227     connect(ui.cbFileEmbedd, &QCheckBox::clicked, this, &DatapickerImageWidget::embeddedChanged);
0228 
0229     // edit image
0230     connect(ui.cbPlotImageType, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &DatapickerImageWidget::plotImageTypeChanged);
0231     connect(ui.sbRotation, QOverload<double>::of(&NumberSpinBox::valueChanged), this, &DatapickerImageWidget::rotationChanged);
0232     connect(ssIntensity, &SpanSlider::spanChanged, this, &DatapickerImageWidget::intensitySpanChanged);
0233     connect(ssIntensity, &SpanSlider::spanChanged, gvIntensity, &HistogramView::setSpan);
0234     connect(ssForeground, &SpanSlider::spanChanged, this, &DatapickerImageWidget::foregroundSpanChanged);
0235     connect(ssForeground, &SpanSlider::spanChanged, gvForeground, &HistogramView::setSpan);
0236     connect(ssHue, &SpanSlider::spanChanged, this, &DatapickerImageWidget::hueSpanChanged);
0237     connect(ssHue, &SpanSlider::spanChanged, gvHue, &HistogramView::setSpan);
0238     connect(ssSaturation, &SpanSlider::spanChanged, this, &DatapickerImageWidget::saturationSpanChanged);
0239     connect(ssSaturation, &SpanSlider::spanChanged, gvSaturation, &HistogramView::setSpan);
0240     connect(ssValue, &SpanSlider::spanChanged, this, &DatapickerImageWidget::valueSpanChanged);
0241     connect(ssValue, &SpanSlider::spanChanged, gvValue, &HistogramView::setSpan);
0242     connect(ui.sbMinSegmentLength, QOverload<int>::of(&QSpinBox::valueChanged), this, &DatapickerImageWidget::minSegmentLengthChanged);
0243     connect(ui.sbPointSeparation, QOverload<int>::of(&QSpinBox::valueChanged), this, &DatapickerImageWidget::pointSeparationChanged);
0244 
0245     // axis point
0246     connect(ui.cbGraphType, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &DatapickerImageWidget::graphTypeChanged);
0247     connect(ui.sbTernaryScale, QOverload<double>::of(&NumberSpinBox::valueChanged), this, &DatapickerImageWidget::ternaryScaleChanged);
0248     connect(ui.sbPositionX1, QOverload<double>::of(&NumberSpinBox::valueChanged), this, &DatapickerImageWidget::logicalPositionChanged);
0249     connect(ui.sbPositionY1, QOverload<double>::of(&NumberSpinBox::valueChanged), this, &DatapickerImageWidget::logicalPositionChanged);
0250     connect(ui.sbPositionX2, QOverload<double>::of(&NumberSpinBox::valueChanged), this, &DatapickerImageWidget::logicalPositionChanged);
0251     connect(ui.sbPositionY2, QOverload<double>::of(&NumberSpinBox::valueChanged), this, &DatapickerImageWidget::logicalPositionChanged);
0252     connect(ui.sbPositionX3, QOverload<double>::of(&NumberSpinBox::valueChanged), this, &DatapickerImageWidget::logicalPositionChanged);
0253     connect(ui.sbPositionY3, QOverload<double>::of(&NumberSpinBox::valueChanged), this, &DatapickerImageWidget::logicalPositionChanged);
0254     connect(ui.sbPositionZ1, QOverload<double>::of(&NumberSpinBox::valueChanged), this, &DatapickerImageWidget::logicalPositionChanged);
0255     connect(ui.sbPositionZ2, QOverload<double>::of(&NumberSpinBox::valueChanged), this, &DatapickerImageWidget::logicalPositionChanged);
0256     connect(ui.sbPositionZ3, QOverload<double>::of(&NumberSpinBox::valueChanged), this, &DatapickerImageWidget::logicalPositionChanged);
0257 
0258     connect(ui.cbDatetime, &QCheckBox::clicked, this, &DatapickerImageWidget::dateTimeUsageChanged);
0259     connect(ui.dtePositionX1, &QDateTimeEdit::dateTimeChanged, this, &DatapickerImageWidget::logicalPositionChanged);
0260     connect(ui.dtePositionX2, &QDateTimeEdit::dateTimeChanged, this, &DatapickerImageWidget::logicalPositionChanged);
0261     connect(ui.dtePositionX3, &QDateTimeEdit::dateTimeChanged, this, &DatapickerImageWidget::logicalPositionChanged);
0262 
0263     connect(ui.chbSymbolVisible, &QCheckBox::clicked, this, &DatapickerImageWidget::pointsVisibilityChanged);
0264 }
0265 
0266 void DatapickerImageWidget::setImages(QList<DatapickerImage*> list) {
0267     CONDITIONAL_LOCK_RETURN;
0268     m_imagesList = list;
0269     m_image = list.first();
0270 
0271     // Set parents as aspects, because their name will be changed
0272     QList<AbstractAspect*> datapickers;
0273     for (const auto* l : list)
0274         datapickers.push_back(l->parentAspect());
0275     setAspects(datapickers);
0276 
0277     if (list.size() == 1) {
0278         ui.leName->setText(m_image->parentAspect()->name());
0279         ui.teComment->setText(m_image->parentAspect()->comment());
0280     }
0281 
0282     this->load();
0283 
0284     QList<Symbol*> symbols;
0285     for (auto* image : m_imagesList)
0286         symbols << image->symbol();
0287 
0288     symbolWidget->setSymbols(symbols);
0289 
0290     connect(m_image, &DatapickerImage::fileNameChanged, this, &DatapickerImageWidget::imageFileNameChanged);
0291     connect(m_image, &DatapickerImage::embeddedChanged, this, &DatapickerImageWidget::imageEmbeddedChanged);
0292     connect(m_image, &DatapickerImage::rotationAngleChanged, this, &DatapickerImageWidget::imageRotationAngleChanged);
0293     connect(m_image, &AbstractAspect::childAspectRemoved, this, &DatapickerImageWidget::updateSymbolWidgets);
0294     connect(m_image, &AbstractAspect::childAspectAdded, this, &DatapickerImageWidget::updateSymbolWidgets);
0295     connect(m_image, &DatapickerImage::axisPointsChanged, this, &DatapickerImageWidget::imageAxisPointsChanged);
0296     connect(m_image, &DatapickerImage::settingsChanged, this, &DatapickerImageWidget::imageEditorSettingsChanged);
0297     connect(m_image, &DatapickerImage::minSegmentLengthChanged, this, &DatapickerImageWidget::imageMinSegmentLengthChanged);
0298     connect(m_image, &DatapickerImage::pointVisibilityChanged, this, &DatapickerImageWidget::symbolVisibleChanged);
0299     connect(m_image, QOverload<int>::of(&DatapickerImage::referencePointSelected), this, &DatapickerImageWidget::imageReferencePointSelected);
0300     connect(m_image, &DatapickerImage::relativeFilePathChanged, this, &DatapickerImageWidget::imageRelativeChanged);
0301     if (m_image->project())
0302         connect(m_image->project(), &Project::saved, this, &DatapickerImageWidget::updateFileRelativePathCheckBoxEnable);
0303 
0304     handleWidgetActions();
0305     updateSymbolWidgets();
0306 }
0307 
0308 void DatapickerImageWidget::handleWidgetActions() {
0309     const QString fileName = m_image->fileName();
0310     const bool embedded = m_image->embedded();
0311     const bool valid = !m_image->originalPlotImage.isNull();
0312     const bool b = !fileName.isEmpty() || (embedded && valid);
0313     ui.leFileName->setEnabled(!embedded);
0314     updateFileRelativePathCheckBoxEnable();
0315     ui.tEdit->setEnabled(b);
0316     ui.cbFileEmbedd->setEnabled(valid);
0317     ui.cbGraphType->setEnabled(b);
0318     ui.cbDatetime->setEnabled(b);
0319     ui.sbRotation->setEnabled(b);
0320     ui.sbPositionX1->setEnabled(b);
0321     ui.sbPositionX2->setEnabled(b);
0322     ui.sbPositionX3->setEnabled(b);
0323     ui.sbPositionY1->setEnabled(b);
0324     ui.sbPositionY2->setEnabled(b);
0325     ui.sbPositionY3->setEnabled(b);
0326     ui.dtePositionX1->setEnabled(b);
0327     ui.dtePositionX2->setEnabled(b);
0328     ui.dtePositionX3->setEnabled(b);
0329     ui.sbMinSegmentLength->setEnabled(b);
0330     ui.sbPointSeparation->setEnabled(b);
0331 
0332     const bool invalid = (!fileName.isEmpty() && !QFile::exists(fileName) && !embedded);
0333     GuiTools::highlight(ui.leFileName, invalid);
0334 
0335     if (b) {
0336         // upload histogram to view
0337         gvIntensity->bins = m_image->intensityBins;
0338         gvForeground->bins = m_image->foregroundBins;
0339         gvHue->bins = m_image->hueBins;
0340         gvSaturation->bins = m_image->saturationBins;
0341         gvValue->bins = m_image->valueBins;
0342     }
0343 }
0344 
0345 void DatapickerImageWidget::updateXPositionWidgets(bool datetime) {
0346     ui.sbPositionX1->setVisible(!datetime);
0347     ui.sbPositionX2->setVisible(!datetime);
0348     ui.sbPositionX3->setVisible(!datetime);
0349     ui.dtePositionX1->setVisible(datetime);
0350     ui.dtePositionX2->setVisible(datetime);
0351     ui.dtePositionX3->setVisible(datetime);
0352 }
0353 
0354 void DatapickerImageWidget::updateLocale() {
0355     const auto locale = QLocale();
0356     ui.sbRotation->setLocale(locale);
0357     ui.sbPositionX1->setLocale(locale);
0358     ui.sbPositionX2->setLocale(locale);
0359     ui.sbPositionX3->setLocale(locale);
0360     ui.sbPositionY1->setLocale(locale);
0361     ui.sbPositionY2->setLocale(locale);
0362     ui.sbPositionY3->setLocale(locale);
0363     ui.dtePositionX1->setLocale(locale);
0364     ui.dtePositionX2->setLocale(locale);
0365     ui.dtePositionX3->setLocale(locale);
0366 }
0367 
0368 void DatapickerImageWidget::updateFileRelativePathCheckBoxEnable() {
0369     const auto* project = m_image->project();
0370     if (!project || project->fileName().isEmpty()) {
0371         ui.cbFileRelativePath->setEnabled(false);
0372         ui.cbFileRelativePath->setToolTip(i18n("Save project before using this option"));
0373     } else if (m_image->embedded()) {
0374         ui.cbFileRelativePath->setEnabled(false);
0375         ui.cbFileRelativePath->setToolTip(QStringLiteral(""));
0376     } else if (!m_image->fileName().isEmpty() && QFile::exists(m_image->fileName())) {
0377         ui.cbFileRelativePath->setEnabled(true);
0378         ui.cbFileRelativePath->setToolTip(QStringLiteral(""));
0379     } else {
0380         ui.cbFileRelativePath->setEnabled(false);
0381         ui.cbFileRelativePath->setToolTip(i18n("Invalid image"));
0382     }
0383 
0384     ui.cbFileRelativePath->setVisible(!m_image->embedded());
0385 }
0386 
0387 //**********************************************************
0388 //****** SLOTs for changes triggered in DatapickerImageWidget ********
0389 //**********************************************************
0390 //"General"-tab
0391 void DatapickerImageWidget::selectFile() {
0392     const QString& path = GuiTools::openImageFile(QLatin1String("DatapickerImageWidget"));
0393     if (path.isEmpty())
0394         return;
0395 
0396     ui.cbFileRelativePath->setChecked(false);
0397     ui.leFileName->setText(path);
0398 }
0399 
0400 void DatapickerImageWidget::embeddedChanged(bool embedded) {
0401     CONDITIONAL_LOCK_RETURN;
0402 
0403     for (auto* image : m_imagesList)
0404         image->setEmbedded(embedded);
0405 
0406     // embedded property was set, update the file name LineEdit after this
0407     if (embedded) {
0408         QFileInfo fi(m_image->fileName());
0409         ui.leFileName->setText(fi.fileName());
0410     } else
0411         ui.leFileName->setText(m_image->fileName());
0412 }
0413 
0414 void DatapickerImageWidget::relativeChanged(bool relative) {
0415     CONDITIONAL_LOCK_RETURN;
0416 
0417     for (auto* image : m_imagesList) {
0418         image->setRelativeFilePath(relative);
0419     }
0420 
0421     // Load new filename
0422     ui.leFileName->setText(m_image->fileName());
0423 }
0424 
0425 void DatapickerImageWidget::fileNameChanged() {
0426     CONDITIONAL_LOCK_RETURN;
0427 
0428     const QString fileName = ui.leFileName->text();
0429     for (auto* image : m_imagesList)
0430         image->setImage(fileName, image->embedded());
0431 }
0432 
0433 void DatapickerImageWidget::graphTypeChanged(int index) {
0434     auto points = m_image->axisPoints();
0435     points.type = static_cast<DatapickerImage::GraphType>(ui.cbGraphType->itemData(index).toInt());
0436 
0437     const bool ternary = (points.type == DatapickerImage::GraphType::Ternary);
0438     ui.lTernaryScale->setVisible(ternary);
0439     ui.sbTernaryScale->setVisible(ternary);
0440     ui.lPositionZ1->setVisible(ternary);
0441     ui.lPositionZ2->setVisible(ternary);
0442     ui.lPositionZ3->setVisible(ternary);
0443     ui.sbPositionZ1->setVisible(ternary);
0444     ui.sbPositionZ2->setVisible(ternary);
0445     ui.sbPositionZ3->setVisible(ternary);
0446 
0447     CONDITIONAL_RETURN_NO_LOCK;
0448 
0449     if (points.type == DatapickerImage::GraphType::LnXY || points.type == DatapickerImage::GraphType::LnX || points.type == DatapickerImage::GraphType::Log10XY
0450         || points.type == DatapickerImage::GraphType::Log10X) {
0451         if (points.logicalPos[0].x() == 0.0f)
0452             points.logicalPos[0].setX(0.01f);
0453         if (points.logicalPos[1].x() == 0.0f)
0454             points.logicalPos[1].setX(0.01f);
0455         if (points.logicalPos[2].x() == 0.0f)
0456             points.logicalPos[2].setX(0.01f);
0457     }
0458     if (points.type == DatapickerImage::GraphType::LnXY || points.type == DatapickerImage::GraphType::LnY || points.type == DatapickerImage::GraphType::Log10XY
0459         || points.type == DatapickerImage::GraphType::Log10Y) {
0460         if (points.logicalPos[0].y() == 0.0f)
0461             points.logicalPos[0].setY(0.01f);
0462         if (points.logicalPos[1].y() == 0.0f)
0463             points.logicalPos[1].setY(0.01f);
0464         if (points.logicalPos[2].y() == 0.0f)
0465             points.logicalPos[2].setY(0.01f);
0466     }
0467 
0468     for (auto* image : m_imagesList)
0469         image->setAxisPoints(points);
0470 }
0471 
0472 void DatapickerImageWidget::ternaryScaleChanged(double value) {
0473     CONDITIONAL_RETURN_NO_LOCK;
0474 
0475     DatapickerImage::ReferencePoints points = m_image->axisPoints();
0476     points.ternaryScale = value;
0477 
0478     for (auto* image : m_imagesList)
0479         image->setAxisPoints(points);
0480 }
0481 
0482 void DatapickerImageWidget::dateTimeUsageChanged(bool datetime) {
0483     updateXPositionWidgets(datetime);
0484 
0485     CONDITIONAL_LOCK_RETURN;
0486 
0487     auto points = m_image->axisPoints();
0488     points.datetime = datetime;
0489     for (auto* image : m_imagesList)
0490         image->setAxisPoints(points);
0491 }
0492 
0493 void DatapickerImageWidget::logicalPositionChanged() {
0494     CONDITIONAL_RETURN_NO_LOCK;
0495 
0496     auto points = m_image->axisPoints();
0497     if (points.datetime) {
0498         points.logicalPos[0].setX(ui.dtePositionX1->dateTime().toMSecsSinceEpoch());
0499         points.logicalPos[1].setX(ui.dtePositionX2->dateTime().toMSecsSinceEpoch());
0500         points.logicalPos[2].setX(ui.dtePositionX3->dateTime().toMSecsSinceEpoch());
0501     } else {
0502         points.logicalPos[0].setX(ui.sbPositionX1->value());
0503         points.logicalPos[1].setX(ui.sbPositionX2->value());
0504         points.logicalPos[2].setX(ui.sbPositionX3->value());
0505     }
0506 
0507     points.logicalPos[0].setY(ui.sbPositionY1->value());
0508     points.logicalPos[1].setY(ui.sbPositionY2->value());
0509     points.logicalPos[2].setY(ui.sbPositionY3->value());
0510 
0511     points.logicalPos[0].setZ(ui.sbPositionZ1->value());
0512     points.logicalPos[1].setZ(ui.sbPositionZ2->value());
0513     points.logicalPos[2].setZ(ui.sbPositionZ3->value());
0514 
0515     for (auto* image : m_imagesList)
0516         image->setAxisPoints(points);
0517 }
0518 
0519 void DatapickerImageWidget::pointsVisibilityChanged(bool state) {
0520     CONDITIONAL_LOCK_RETURN;
0521 
0522     for (auto* image : m_imagesList)
0523         image->setPointVisibility(state);
0524 }
0525 
0526 void DatapickerImageWidget::intensitySpanChanged(int lowerLimit, int upperLimit) {
0527     CONDITIONAL_LOCK_RETURN;
0528 
0529     auto settings = m_image->settings();
0530     settings.intensityThresholdHigh = upperLimit;
0531     settings.intensityThresholdLow = lowerLimit;
0532     for (auto* image : m_imagesList)
0533         image->setSettings(settings);
0534 }
0535 
0536 void DatapickerImageWidget::foregroundSpanChanged(int lowerLimit, int upperLimit) {
0537     CONDITIONAL_LOCK_RETURN;
0538 
0539     auto settings = m_image->settings();
0540     settings.foregroundThresholdHigh = upperLimit;
0541     settings.foregroundThresholdLow = lowerLimit;
0542     for (auto* image : m_imagesList)
0543         image->setSettings(settings);
0544 }
0545 
0546 void DatapickerImageWidget::hueSpanChanged(int lowerLimit, int upperLimit) {
0547     CONDITIONAL_LOCK_RETURN;
0548 
0549     auto settings = m_image->settings();
0550     settings.hueThresholdHigh = upperLimit;
0551     settings.hueThresholdLow = lowerLimit;
0552     for (auto* image : m_imagesList)
0553         image->setSettings(settings);
0554 }
0555 
0556 void DatapickerImageWidget::saturationSpanChanged(int lowerLimit, int upperLimit) {
0557     CONDITIONAL_LOCK_RETURN;
0558 
0559     auto settings = m_image->settings();
0560     settings.saturationThresholdHigh = upperLimit;
0561     settings.saturationThresholdLow = lowerLimit;
0562     for (auto* image : m_imagesList)
0563         image->setSettings(settings);
0564 }
0565 
0566 void DatapickerImageWidget::valueSpanChanged(int lowerLimit, int upperLimit) {
0567     CONDITIONAL_LOCK_RETURN;
0568 
0569     auto settings = m_image->settings();
0570     settings.valueThresholdHigh = upperLimit;
0571     settings.valueThresholdLow = lowerLimit;
0572     for (auto* image : m_imagesList)
0573         image->setSettings(settings);
0574 }
0575 
0576 void DatapickerImageWidget::plotImageTypeChanged(int index) {
0577     CONDITIONAL_LOCK_RETURN;
0578 
0579     for (auto* image : m_imagesList)
0580         image->setPlotImageType(DatapickerImage::PlotImageType(index));
0581 }
0582 
0583 void DatapickerImageWidget::rotationChanged(double value) {
0584     CONDITIONAL_RETURN_NO_LOCK;
0585 
0586     for (auto* image : m_imagesList)
0587         image->setRotationAngle(value);
0588 }
0589 
0590 void DatapickerImageWidget::minSegmentLengthChanged(int value) {
0591     CONDITIONAL_LOCK_RETURN;
0592 
0593     for (auto* image : m_imagesList)
0594         image->setminSegmentLength(value);
0595 }
0596 
0597 void DatapickerImageWidget::pointSeparationChanged(int value) {
0598     CONDITIONAL_LOCK_RETURN;
0599 
0600     for (auto* image : m_imagesList)
0601         image->setPointSeparation(value);
0602 }
0603 
0604 //*******************************************************************
0605 //******** SLOTs for changes triggered in DatapickerImage ***********
0606 //*******************************************************************
0607 void DatapickerImageWidget::imageFileNameChanged(const QString& name) {
0608     handleWidgetActions();
0609 
0610     CONDITIONAL_LOCK_RETURN;
0611 
0612     ui.leFileName->setText(name);
0613 }
0614 
0615 void DatapickerImageWidget::imageRotationAngleChanged(float angle) {
0616     CONDITIONAL_LOCK_RETURN;
0617     ui.sbRotation->setValue(angle);
0618 }
0619 
0620 void DatapickerImageWidget::imageAxisPointsChanged(const DatapickerImage::ReferencePoints& axisPoints) {
0621     CONDITIONAL_LOCK_RETURN;
0622     int index = ui.cbGraphType->findData((int)axisPoints.type);
0623     ui.cbGraphType->setCurrentIndex(index);
0624     ui.sbTernaryScale->setValue(axisPoints.ternaryScale);
0625     ui.sbPositionX1->setValue(axisPoints.logicalPos[0].x());
0626     ui.sbPositionY1->setValue(axisPoints.logicalPos[0].y());
0627     ui.sbPositionX2->setValue(axisPoints.logicalPos[1].x());
0628     ui.sbPositionY2->setValue(axisPoints.logicalPos[1].y());
0629     ui.sbPositionX3->setValue(axisPoints.logicalPos[2].x());
0630     ui.sbPositionY3->setValue(axisPoints.logicalPos[2].y());
0631     ui.sbPositionZ1->setValue(axisPoints.logicalPos[0].z());
0632     ui.sbPositionZ2->setValue(axisPoints.logicalPos[1].z());
0633     ui.sbPositionZ3->setValue(axisPoints.logicalPos[2].z());
0634 }
0635 
0636 void DatapickerImageWidget::imageEditorSettingsChanged(const DatapickerImage::EditorSettings& settings) {
0637     CONDITIONAL_LOCK_RETURN;
0638     ssIntensity->setSpan(settings.intensityThresholdLow, settings.intensityThresholdHigh);
0639     ssForeground->setSpan(settings.foregroundThresholdLow, settings.foregroundThresholdHigh);
0640     ssHue->setSpan(settings.hueThresholdLow, settings.hueThresholdHigh);
0641     ssSaturation->setSpan(settings.saturationThresholdLow, settings.saturationThresholdHigh);
0642     ssValue->setSpan(settings.valueThresholdLow, settings.valueThresholdHigh);
0643     gvIntensity->setSpan(settings.intensityThresholdLow, settings.intensityThresholdHigh);
0644     gvForeground->setSpan(settings.foregroundThresholdLow, settings.foregroundThresholdHigh);
0645     gvHue->setSpan(settings.hueThresholdLow, settings.hueThresholdHigh);
0646     gvSaturation->setSpan(settings.saturationThresholdLow, settings.saturationThresholdHigh);
0647     gvValue->setSpan(settings.valueThresholdLow, settings.valueThresholdHigh);
0648 }
0649 
0650 void DatapickerImageWidget::imageMinSegmentLengthChanged(const int value) {
0651     CONDITIONAL_LOCK_RETURN;
0652     ui.sbMinSegmentLength->setValue(value);
0653 }
0654 
0655 void DatapickerImageWidget::imageEmbeddedChanged(bool embedded) {
0656     handleWidgetActions();
0657 
0658     CONDITIONAL_LOCK_RETURN;
0659     ui.cbFileEmbedd->setChecked(embedded);
0660 }
0661 
0662 void DatapickerImageWidget::imageRelativeChanged(bool relative) {
0663     CONDITIONAL_LOCK_RETURN;
0664     ui.cbFileRelativePath->setChecked(relative);
0665 }
0666 
0667 void DatapickerImageWidget::updateSymbolWidgets() {
0668     int pointCount = m_image->childCount<DatapickerPoint>(AbstractAspect::ChildIndexFlag::IncludeHidden);
0669     if (pointCount)
0670         ui.tSymbol->setEnabled(true);
0671     else
0672         ui.tSymbol->setEnabled(false);
0673 }
0674 
0675 void DatapickerImageWidget::symbolVisibleChanged(bool on) {
0676     CONDITIONAL_LOCK_RETURN;
0677     ui.chbSymbolVisible->setChecked(on);
0678 }
0679 
0680 void DatapickerImageWidget::imageReferencePointSelected(int index) {
0681     ui.rbRefPoint1->setChecked(index == 0);
0682     ui.rbRefPoint2->setChecked(index == 1);
0683     ui.rbRefPoint3->setChecked(index == 2);
0684 }
0685 
0686 //**********************************************************
0687 //******************** SETTINGS ****************************
0688 //**********************************************************
0689 void DatapickerImageWidget::load() {
0690     if (!m_image)
0691         return;
0692 
0693     // No lock, because it is done already in the caller function
0694     ui.cbFileEmbedd->setChecked(m_image->embedded());
0695     embeddedChanged(m_image->embedded());
0696     ui.cbFileRelativePath->setChecked(m_image->isRelativeFilePath());
0697     updateFileRelativePathCheckBoxEnable();
0698     ui.leFileName->setText(m_image->fileName());
0699 
0700     // highlight the text field for the background image red if an image is used and cannot be found
0701     const QString& fileName = m_image->fileName();
0702     bool invalid = (!m_image->embedded() && !fileName.isEmpty() && !QFile::exists(fileName));
0703     GuiTools::highlight(ui.leFileName, invalid);
0704 
0705     imageReferencePointSelected(m_image->currentSelectedReferencePoint());
0706 
0707     ui.cbGraphType->setCurrentIndex(ui.cbGraphType->findData((int)m_image->axisPoints().type));
0708     ui.sbTernaryScale->setValue(m_image->axisPoints().ternaryScale);
0709     const bool datetime = m_image->axisPoints().datetime;
0710     ui.cbDatetime->setChecked(datetime);
0711     updateXPositionWidgets(datetime);
0712 
0713     const double x1 = m_image->axisPoints().logicalPos[0].x();
0714     const double x2 = m_image->axisPoints().logicalPos[1].x();
0715     const double x3 = m_image->axisPoints().logicalPos[2].x();
0716 
0717     ui.dtePositionX1->setMSecsSinceEpochUTC(x1);
0718     ui.dtePositionX2->setMSecsSinceEpochUTC(x2);
0719     ui.dtePositionX3->setMSecsSinceEpochUTC(x3);
0720 
0721     ui.sbPositionX1->setValue(x1);
0722     ui.sbPositionY1->setValue(m_image->axisPoints().logicalPos[0].y());
0723     ui.sbPositionX2->setValue(x2);
0724     ui.sbPositionY2->setValue(m_image->axisPoints().logicalPos[1].y());
0725     ui.sbPositionX3->setValue(x3);
0726     ui.sbPositionY3->setValue(m_image->axisPoints().logicalPos[2].y());
0727     ui.sbPositionZ1->setValue(m_image->axisPoints().logicalPos[0].z());
0728     ui.sbPositionZ2->setValue(m_image->axisPoints().logicalPos[1].z());
0729     ui.sbPositionZ3->setValue(m_image->axisPoints().logicalPos[2].z());
0730     ui.cbPlotImageType->setCurrentIndex((int)m_image->plotImageType());
0731     ssIntensity->setSpan(m_image->settings().intensityThresholdLow, m_image->settings().intensityThresholdHigh);
0732     ssForeground->setSpan(m_image->settings().foregroundThresholdLow, m_image->settings().foregroundThresholdHigh);
0733     ssHue->setSpan(m_image->settings().hueThresholdLow, m_image->settings().hueThresholdHigh);
0734     ssSaturation->setSpan(m_image->settings().saturationThresholdLow, m_image->settings().saturationThresholdHigh);
0735     ssValue->setSpan(m_image->settings().valueThresholdLow, m_image->settings().valueThresholdHigh);
0736     gvIntensity->setSpan(m_image->settings().intensityThresholdLow, m_image->settings().intensityThresholdHigh);
0737     gvForeground->setSpan(m_image->settings().foregroundThresholdLow, m_image->settings().foregroundThresholdHigh);
0738     gvHue->setSpan(m_image->settings().hueThresholdLow, m_image->settings().hueThresholdHigh);
0739     gvSaturation->setSpan(m_image->settings().saturationThresholdLow, m_image->settings().saturationThresholdHigh);
0740     gvValue->setSpan(m_image->settings().valueThresholdLow, m_image->settings().valueThresholdHigh);
0741     ui.sbPointSeparation->setValue(m_image->pointSeparation());
0742     ui.sbMinSegmentLength->setValue(m_image->minSegmentLength());
0743     ui.chbSymbolVisible->setChecked(m_image->pointVisibility());
0744 }