File indexing completed on 2022-09-27 12:24:15

0001 /*
0002     SPDX-FileCopyrightText: 2005, 2006 Carsten Niehaus <cniehaus@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "spectrumwidget.h"
0008 
0009 #include "kalziumutils.h"
0010 
0011 #include <config-kalzium.h>
0012 
0013 #include <cmath>
0014 #include <element.h>
0015 
0016 #include "kalzium_debug.h"
0017 #include <QGlobalStatic>
0018 #include <QKeyEvent>
0019 #include <QPainter>
0020 #include <QPixmap>
0021 #include <QSizePolicy>
0022 
0023 #include <KUnitConversion/Converter>
0024 
0025 #if defined(HAVE_IEEEFP_H)
0026 #include <ieeefp.h>
0027 #endif
0028 
0029 SpectrumWidget::SpectrumWidget(QWidget *parent)
0030     : QWidget(parent)
0031 {
0032     m_startValue = 0;
0033     m_endValue = 0;
0034 
0035     m_LMBPointCurrent.setX(-1);
0036     m_LMBPointPress.setX(-1);
0037 
0038     m_realHeight = 200;
0039 
0040     m_gamma = 0.8;
0041     m_intensityMax = 255;
0042 
0043     setType(Prefs::spectrumType());
0044 
0045     setMinimumSize(400, 230);
0046     setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
0047     setAttribute(Qt::WA_OpaquePaintEvent, true);
0048     setContextMenuPolicy(Qt::PreventContextMenu);
0049 }
0050 
0051 void SpectrumWidget::paintEvent(QPaintEvent * /*e*/)
0052 {
0053     if (!m_spectrum) {
0054         return;
0055     }
0056 
0057     m_pixmap = QPixmap(width(), height());
0058     m_pixmap.fill();
0059 
0060     QPainter p;
0061     p.begin(&m_pixmap);
0062     p.fillRect(0, 0, width(), m_realHeight, Qt::black);
0063 
0064     paintBands(&p);
0065 
0066     drawTickmarks(&p);
0067 
0068     if (m_LMBPointPress.x() != -1 && m_LMBPointCurrent.x() != -1) {
0069         drawZoomLine(&p);
0070     }
0071 
0072     p.end();
0073 
0074     QPainter p2(this);
0075     p2.drawPixmap(0, 0, m_pixmap);
0076 }
0077 
0078 void SpectrumWidget::drawZoomLine(QPainter *p)
0079 {
0080     p->setPen(Qt::white);
0081     p->drawLine(m_LMBPointPress.x(), m_LMBPointPress.y(), m_LMBPointCurrent.x(), m_LMBPointPress.y());
0082     p->drawLine(m_LMBPointCurrent.x(), m_LMBPointPress.y() + 10, m_LMBPointCurrent.x(), m_LMBPointPress.y() - 10);
0083     p->drawLine(m_LMBPointPress.x(), m_LMBPointPress.y() + 10, m_LMBPointPress.x(), m_LMBPointPress.y() - 10);
0084 }
0085 
0086 void SpectrumWidget::paintBands(QPainter *p)
0087 {
0088     if (m_type == AbsorptionSpectrum) {
0089         for (double va = m_startValue; va <= m_endValue; va += 0.1) {
0090             int x = xPos(va);
0091             p->setPen(wavelengthToRGB(va));
0092             p->drawLine(x, 0, x, m_realHeight);
0093         }
0094 
0095         p->setPen(Qt::black);
0096     }
0097 
0098     int i = 0;
0099     int x = 0;
0100     double currentWavelength;
0101 
0102     foreach (Spectrum::peak *peak, m_spectrum->peaklist()) {
0103         currentWavelength = peak->wavelengthToUnit(Prefs::spectrumWavelengthUnit());
0104 
0105         if (currentWavelength < m_startValue || currentWavelength > m_endValue) {
0106             continue;
0107         }
0108 
0109         x = xPos(currentWavelength);
0110 
0111         switch (m_type) {
0112         case EmissionSpectrum:
0113             p->setPen(wavelengthToRGB(currentWavelength));
0114             p->drawLine(x, 0, x, m_realHeight - 1);
0115 
0116             p->setPen(Qt::black);
0117             p->drawLine(x, m_realHeight, x, m_realHeight);
0118             break;
0119 
0120         case AbsorptionSpectrum:
0121             p->setPen(Qt::black);
0122             p->drawLine(x, 0, x, m_realHeight - 1);
0123             break;
0124         }
0125 
0126         ++i;
0127     }
0128 }
0129 
0130 QColor SpectrumWidget::wavelengthToRGB(double wavelength)
0131 {
0132     double blue = 0.0, green = 0.0, red = 0.0, factor = 0.0;
0133 
0134     // wavelengthTo RGB function works with nanometers.
0135     wavelength = KUnitConversion::Value(wavelength, KUnitConversion::UnitId(Prefs::spectrumWavelengthUnit())).convertTo(KUnitConversion::Nanometer).number();
0136 
0137     int wavelength_ = (int)floor(wavelength);
0138 
0139     if (wavelength_ < 380 || wavelength_ > 780) {
0140         return {Qt::white};
0141     } else if (wavelength_ >= 380 && wavelength_ < 440) {
0142         red = -(wavelength - 440) / (440 - 380);
0143         green = 0.0;
0144         blue = 1.0;
0145     } else if (wavelength_ >= 440 && wavelength_ < 490) {
0146         red = 0.0;
0147         green = (wavelength - 440) / (490 - 440);
0148         blue = 1.0;
0149     } else if (wavelength_ >= 490 && wavelength_ < 510) {
0150         red = 0.0;
0151         green = 1.0;
0152         blue = -(wavelength - 510) / (510 - 490);
0153     } else if (wavelength_ >= 510 && wavelength_ < 580) {
0154         red = (wavelength - 510) / (580 - 510);
0155         green = 1.0;
0156         blue = 0.0;
0157     } else if (wavelength_ >= 580 && wavelength_ < 645) {
0158         red = 1.0;
0159         green = -(wavelength - 645) / (645 - 580);
0160         blue = 0.0;
0161     } else if (wavelength_ >= 645 && wavelength_ < 780) {
0162         red = 1.0;
0163         green = 0.0;
0164         blue = 0.0;
0165     }
0166 
0167     if (wavelength_ > 380 && wavelength_ <= 420) {
0168         factor = 0.3 + 0.7 * (wavelength - 380) / (420 - 380);
0169     } else if (wavelength_ > 420 && wavelength_ <= 701) {
0170         factor = 1.0;
0171     } else if (wavelength_ > 701 && wavelength_ <= 780) {
0172         factor = 0.3 + 0.7 * (780 - wavelength) / (780 - 700);
0173     } else {
0174         factor = 0.0;
0175     }
0176 
0177     return {Adjust(red, factor), Adjust(green, factor), Adjust(blue, factor)};
0178 }
0179 
0180 int SpectrumWidget::Adjust(double color, double /*factor*/)
0181 {
0182     if (color == 0.0) {
0183         return 0;
0184     } else {
0185         //         return qRound(m_intensityMax * pow(color*factor, m_gamma)); FIXME
0186         return m_intensityMax * color;
0187     }
0188 }
0189 
0190 void SpectrumWidget::drawTickmarks(QPainter *p)
0191 {
0192     // the size of the text on the tickmarks
0193     const int space = 20;
0194 
0195     // the distance between the tickmarks in pixel
0196     const int d = 10;
0197 
0198     // the total number of tickmarks to draw (small and long)
0199     const int numberOfTickmarks = (int)floor((double)(width() / d));
0200 
0201     double pos = 0.0;
0202 
0203     for (int i = 0; i < numberOfTickmarks; ++i) {
0204         if (i % 5 == 0) {
0205             // long tickmarks plus text
0206             p->drawLine(i * d, m_realHeight, i * d, m_realHeight + 10);
0207             if (i % 10 == 0 && i * d > space && i * d < width() - space) {
0208                 pos = (double)(i * d) / width();
0209                 p->fillRect(i * d - space, m_realHeight + 12, 2 * space, 15, Qt::white);
0210                 p->drawText(i * d - space, m_realHeight + 12, 2 * space, 15, Qt::AlignCenter, QString::number(KalziumUtils::strippedValue(Wavelength(pos))));
0211             }
0212         } else { // small tickmarks
0213             p->drawLine(i * d, m_realHeight, i * d, m_realHeight + 5);
0214         }
0215     }
0216 }
0217 
0218 void SpectrumWidget::keyPressEvent(QKeyEvent *e)
0219 {
0220     switch (e->key()) {
0221     case Qt::Key_Plus:
0222         slotZoomIn();
0223         break;
0224     case Qt::Key_Minus:
0225         slotZoomOut();
0226         break;
0227     }
0228 }
0229 
0230 void SpectrumWidget::slotZoomOut()
0231 {
0232     qCDebug(KALZIUM_LOG) << "SpectrumWidget::slotZoomOut() " << m_startValue << ":: " << m_endValue;
0233 
0234     double diff = m_endValue - m_startValue;
0235 
0236     double offset = diff * 0.05;
0237 
0238     m_endValue = m_endValue + offset;
0239     m_startValue = m_startValue - offset;
0240 
0241     // check for invalid values
0242     if (m_startValue < 0.0) {
0243         m_startValue = 0.0;
0244     }
0245 
0246     if (m_endValue > 10000.0) { // FIXME: Magic numbers...
0247         m_endValue = 40000.0;
0248     }
0249 
0250     setBorders(m_startValue, m_endValue);
0251 }
0252 
0253 void SpectrumWidget::setBorders(double left, double right)
0254 {
0255     qCDebug(KALZIUM_LOG) << "setBorders    " << left << ".." << right;
0256 
0257     m_startValue = left;
0258     m_endValue = right;
0259 
0260     // round the startValue down and the endValue up
0261     Q_EMIT bordersChanged(int(m_startValue + 0.5), int(m_endValue + 0.5));
0262 
0263     update();
0264 }
0265 
0266 void SpectrumWidget::slotZoomIn()
0267 {
0268     qCDebug(KALZIUM_LOG) << "SpectrumWidget::slotZoomIn() " << m_startValue << ":: " << m_endValue;
0269 
0270     double diff = m_endValue - m_startValue;
0271 
0272     double offset = diff * 0.05;
0273 
0274     m_endValue = m_endValue - offset;
0275     m_startValue = m_startValue + offset;
0276 
0277     setBorders(m_startValue, m_endValue);
0278 }
0279 
0280 void SpectrumWidget::mouseMoveEvent(QMouseEvent *e)
0281 {
0282     m_LMBPointCurrent = e->pos();
0283     update();
0284 }
0285 
0286 void SpectrumWidget::mousePressEvent(QMouseEvent *e)
0287 {
0288     if (e->button() == Qt::LeftButton) {
0289         m_LMBPointPress = e->pos();
0290     }
0291     if (e->button() == Qt::RightButton) {
0292         resetSpectrum();
0293     }
0294 
0295     findPeakFromMouseposition(Wavelength((double)e->pos().x() / width()));
0296 }
0297 
0298 void SpectrumWidget::findPeakFromMouseposition(double wavelength)
0299 {
0300     qCDebug(KALZIUM_LOG) << "SpectrumWidget::findPeakFromMouseposition()";
0301     Spectrum::peak *peak = nullptr;
0302 
0303     // find the difference in percent (1.0 is 100%, 0.1 is 10%)
0304     double dif = 0.0;
0305 
0306     bool foundWavelength = false;
0307 
0308     // find the highest intensity
0309     foreach (Spectrum::peak *currentPeak, m_spectrum->peaklist()) {
0310         double currentWavelength = currentPeak->wavelengthToUnit(Prefs::spectrumWavelengthUnit());
0311 
0312         double thisdif = currentWavelength / wavelength;
0313 
0314         if (thisdif < 0.9 || thisdif > 1.1) {
0315             continue;
0316         }
0317 
0318         if (thisdif > 1.0) { // convert for example 1.3 to 0.7
0319             thisdif = thisdif - 1;
0320             thisdif = 1 - thisdif;
0321         }
0322 
0323         if (thisdif > dif) {
0324             dif = thisdif;
0325             peak = currentPeak;
0326             foundWavelength = true;
0327         }
0328     }
0329 
0330     if (foundWavelength) {
0331         Q_EMIT peakSelected(peak);
0332     }
0333 }
0334 
0335 void SpectrumWidget::mouseReleaseEvent(QMouseEvent *e)
0336 {
0337     if (e->button() == Qt::LeftButton) {
0338         int left = (int)Wavelength((double)m_LMBPointPress.x() / width());
0339         int right = (int)Wavelength((double)e->pos().x() / width());
0340 
0341         if (left == right) {
0342             return;
0343         }
0344 
0345         if (left > right) {
0346             setBorders(right, left);
0347         } else {
0348             setBorders(left, right);
0349         }
0350     }
0351 
0352     m_LMBPointPress.setX(-1);
0353     m_LMBPointCurrent.setX(-1);
0354 }
0355 
0356 void SpectrumWidget::setSpectrum(Spectrum *spec)
0357 {
0358     m_spectrum = spec;
0359     resetSpectrum();
0360 }
0361 
0362 void SpectrumWidget::resetSpectrum()
0363 {
0364     // set the minimum and maximum peak to the min/max wavelength
0365     // plus/minus ten. This makes then always visible
0366     double minimumPeak = m_spectrum->minPeak(Prefs::spectrumWavelengthUnit()) - 20.0;
0367     double maximumPeak = m_spectrum->maxPeak(Prefs::spectrumWavelengthUnit()) + 20.0;
0368 
0369     setBorders(minimumPeak, maximumPeak);
0370 }