#include <QPainter>
#include <QValidator>
#include <QToolTip>
#include <QCursor>
#include <QMessageBox>
#include <QResizeEvent>

#include <gsl/gsl_math.h>

#include "QFitsCubeSpectrum.h"
#include "QFitsMainWindow.h"
#include "QFitsMainView.h"
#include "QFitsSingleBuffer.h"
#include "QFitsMultiBuffer.h"
#include "QFitsWidgetTable.h"
#include "QFitsView1D.h"
#include "QFitsWidget1D.h"
#include "QFitsMarkers.h"
#include "QFitsGlobal.h"
#include "qtdpuser.h"
#include "events.h"

//------------------------------------------------------------------------------
//         QFitsCubeSpectrum
//------------------------------------------------------------------------------
QFitsCubeSpectrum::QFitsCubeSpectrum(QFitsMainWindow *parent)
                            : QWidget(parent)
{
    cenx            = 0;
    ceny            = 0;
    method          = SinglePixel;
    statistics      = 0;
    r1              = 1;
    r2              = 2;
    ignoreInCube    = true;
    ignoreValue     = -1e10;

    QPalette pal(palette());
//    pal.setColor(QPalette::Background, Qt::Window);
    setPalette(pal);

    setWindowTitle("QFitsView - Spectrum Plot");
    plotter = new QFitsCubeSpectrumViewer(this);

    minlabel = new QLabel("Minimum: ", this);
    minlabel->adjustSize();
//    minlabel->setGeometry(0, 0, 70, 20);

    minEdit = new dpDoubleEdit("0", this);
    minEdit->adjustSize();
    minEdit->move(minlabel->width(), 0);
//    minEdit->setGeometry(70, 0, 50, 20);
//    minEdit->setValidator(new QDoubleValidator(minEdit));
    minEdit->setEnabled(false);
    connect(minEdit, SIGNAL(textChanged(const QString &)),
            this, SLOT(setMin(const QString &)));

    maxlabel = new QLabel(" Maximum: ", this);
    maxlabel->adjustSize();
//    maxlabel->move(minEdit->x() + minEdit->width(), 0);
//    maxlabel->setGeometry(120, 0, 70, 20);

    maxEdit = new dpDoubleEdit("100", this);
    maxEdit->adjustSize();
//    maxEdit->move(maxlabel->x() + maxlabel->width(), 0);
//    maxEdit->setGeometry(190, 0, 50, 20);
//    maxEdit->setValidator(new QDoubleValidator(maxEdit));
    maxEdit->setEnabled(false);
    connect(maxEdit, SIGNAL(textChanged(const QString &)),
            this, SLOT(setMax(const QString &)));

    rangeControl = new QFitsSpectrumRangeControl(this);
    connect(rangeControl, SIGNAL(channelRangeChanged()),
            this, SLOT(update()));

    autoScaleButton = new QCheckBox("Auto Scale ", this);
    autoScaleButton->adjustSize();
//    autoScaleButton->move(maxEdit->x() + maxEdit->width() + fontMetrics().horizontalAdvance('M'), 0);
//    autoScaleButton->setGeometry(250, 0, 100, 20);
    autoScaleButton->setChecked(true);
    connect(autoScaleButton, SIGNAL(toggled(bool)),
            minEdit, SLOT(setDisabled(bool)));
    connect(autoScaleButton, SIGNAL(toggled(bool)),
            maxEdit, SLOT(setDisabled(bool)));
    connect(autoScaleButton, SIGNAL(toggled(bool)),
            plotter, SLOT(setAutoScale(bool)));

    methodButton = new QComboBox(this);
//    methodButton->setGeometry(350, 0, 145, 20);
    methodButton->insertItem(0, "Single Pixel");
    methodButton->insertItem(1, "Circular");
    methodButton->insertItem(2, "Circular-Annular");
    methodButton->adjustSize();
//    methodButton->move(autoScaleButton->x() + autoScaleButton->width(), 0);
    connect(methodButton, SIGNAL(activated(int)),
            this, SLOT(setMethod(int)));

    r1label = new QLabel(" R1: ", this);
    r1label->adjustSize();
//    r1label->move(methodButton->x() + methodButton->width(), 0);
//    r1label->setGeometry(505, 0, 20, 20);
    r1label->setEnabled(false);

    r1edit = new QSpinBox(this);
    r1edit->setMinimum(0);
    r1edit->setMaximum(1000);
    r1edit->adjustSize();
    r1edit->resize(r1edit->width(), minEdit->height());
    r1edit->setSingleStep(1);
    r1edit->setValue(1);
//    r1edit->move(r1label->x() + r1label->width(), 0);
//    r1edit->setGeometry(525, 0, 50, 20);
    r1edit->setEnabled(false);
    connect(r1edit, SIGNAL(valueChanged(int)),
            this, SLOT(setR1(int)));

    r2label = new QLabel(" R2: ", this);
    r2label->adjustSize();
//    r2label->move(r1edit->x() + r1edit->width(), 0);
//    r2label->setGeometry(585, 0, 20, 20);
    r2label->setEnabled(false);

    r2edit = new QSpinBox(this);
    r2edit->setMinimum(0);
    r2edit->setMaximum(1000);
    r2edit->adjustSize();
    r2edit->resize(r2edit->width(), minEdit->height());
    r2edit->setSingleStep(1);
    r2edit->setValue(2);
//    r2edit->move(r2label->x() + r2label->width(), 0);
//    r2edit->setGeometry(605, 0, 50, 20);
    r2edit->setEnabled(false);
    connect(r2edit, SIGNAL(valueChanged(int)),
            this, SLOT(setR2(int)));

    useStandardStar = new QCheckBox("Divide Std", this);
    useStandardStar->adjustSize();
//    useStandardStar->move(r2edit->x() + r2edit->width(), 0);
//    useStandardStar->setGeometry(658, 0, 100, 20);
    useStandardStar->setChecked(false);
    useStandardStar->setEnabled(false);
    connect(useStandardStar, SIGNAL(clicked()),
            this, SLOT(useStandardStarClicked()));

    statisticsButton = new QComboBox(this);
    statisticsButton->insertItem(0, "Sum");
    statisticsButton->insertItem(1, "Average");
    statisticsButton->insertItem(2, "Median");
    statisticsButton->adjustSize();
//    statisticsButton->move(useStandardStar->x() + useStandardStar->width() + fontMetrics().horizontalAdvance('M'), 0);
    connect(statisticsButton, SIGNAL(activated(int)),
            this, SLOT(setStatistics(int)));

    QWidget *topWidget = new QWidget(this);
    QHBoxLayout *toplayout = new QHBoxLayout(topWidget);
    toplayout->setContentsMargins(0, 0, 0, 0);
//    toplayout->setSpacing(0);
    toplayout->addWidget(minlabel);
    toplayout->addWidget(minEdit);
    toplayout->addWidget(maxlabel);
    toplayout->addWidget(maxEdit);
    toplayout->addWidget(rangeControl);
    toplayout->addWidget(autoScaleButton);
    toplayout->addWidget(methodButton);
    toplayout->addWidget(r1label);
    toplayout->addWidget(r1edit);
    toplayout->addWidget(r2label);
    toplayout->addWidget(r2edit);
    toplayout->addWidget(useStandardStar);
    toplayout->addWidget(statisticsButton);
    topWidget->adjustSize();
    topWidget->setFixedSize(topWidget->width(), autoScaleButton->height());

    rangeControl->setFixedHeight(rangeControl->height());

    QVBoxLayout *vlayout = new QVBoxLayout(this);
    vlayout->setSpacing(0);
    vlayout->setContentsMargins(0, 0, 0, 0);
    vlayout->addWidget(topWidget);
    vlayout->addWidget(plotter);
    vlayout->addWidget(rangeControl);

    hideButton = new QFitsSimplestButton(QIcon(":/xicon.svg").pixmap(10, 10), this);
    hideButton->setGeometry(width() - 12, 0, 12, 12);
    hideButton->setToolTip("Close");
    connect(hideButton, SIGNAL(clicked()),
            this, SLOT(hide()));
}

void QFitsCubeSpectrum::setCentre(int x, int y) {
    cenx = x;
    ceny = y;
    update();
}

void QFitsCubeSpectrum::paintEvent(QPaintEvent *) {
    QReadLocker locker(&buffersLock);

    QFitsSingleBuffer *sb = fitsMainWindow->getActualSB();
    Fits *f = NULL;
    if (sb != NULL) {
        if ((sb->getDpData()->type == typeFits) &&
            (sb->getDpData()->fvalue != NULL))
        {
            f = sb->getDpData()->fvalue;
        }
        if (f == NULL) {
            if (sb->getDpData()->type == typeStrarr) {
                f = sb->getCubeDisplay1D();
            }
        }
    }

    if (f == NULL) {
        return;
    }

    if (f->Naxis(3) > 1) {
        if (!autoScaleButton->isChecked()) {
            plotter->setYRange(minEdit->text().toDouble(),
                               maxEdit->text().toDouble());
        }

        Fits *line = sb->getCubeDisplay1D();
        if (line == NULL) {
            line = new Fits();
        }
        switch (method) {
        case SinglePixel:
            line->spec3d(*f, cenx, ceny, 0, 0);
            break;
        case Circular:
            line->spec3d(*f, cenx, ceny, r1, 0, statistics);
            break;
        case CircularAnnular:
            line->spec3d(*f, cenx, ceny, r1, r2, statistics);
            break;
        default: break;
        }

        if (useStandardStar->isChecked()) {
            line->div(standardStar);
        }

        if (sb->getHasManualSpectrum()) {
            if (useStandardStar->isChecked()) {
                Fits manualSpectrumStandard;
                manualSpectrumStandard.copy(*(sb->getManualSpectrum()));
                manualSpectrumStandard.div(standardStar);
                plotter->setCData(manualSpectrumStandard);
            } else {
                plotter->setCData(*(sb->getManualSpectrum()));
            }
        } else {
            plotter->removeCData();
        }

        Fits *before = sb->getCubeDisplay1D();
        sb->setCubeDisplay1D(line);
        if (before == NULL) {
            // we are apparently starting up QFV and the min-max values
            // of the RangeControl couldn't be set before, so force this now
            changeWorkArray();
        }

        plotter->setData();
    } else {
        if (f->extensionType == EMPTY) {
            sb->setCubeDisplay1D(NULL);
            plotter->setData();
        }
        if (f->Naxis(1) == 1 && f->Naxis(2) == 1) {
            sb->setCubeDisplay1D(NULL);
            plotter->setData();
        }
    }

    if ((f->extensionType == BINTABLE) || (f->extensionType == TABLE)) {
        plotter->removeCData();
    }

    plotter->updateYRange(sb->getSpecChannelMinX(), sb->getSpecChannelMaxX());
}

void QFitsCubeSpectrum::resizeEvent(QResizeEvent *e) {
//    plotter->setGeometry(0, 20, e->size().width(), e->size().height() - 20-rangeControl->height());
//    rangeControl->setGeometry(0, e->size().height()-rangeControl->height(),
//                              e->size().width(), rangeControl->height());
    hideButton->setGeometry(e->size().width() - 12, 0, 12, 12);
}

void QFitsCubeSpectrum::enterEvent(QEvent *e) {
    plotter->setFocus();
}

void QFitsCubeSpectrum::enableControls(bool b) {
    autoScaleButton->setEnabled(b);
    useStandardStar->setEnabled(b);
    r1label->setEnabled(b);
    r2label->setEnabled(b);
    methodButton->setEnabled(b);
    statisticsButton->setEnabled(b);
    r1edit->setEnabled(b);
    r2edit->setEnabled(b);
    if (autoScaleButton->isEnabled() && autoScaleButton->isChecked()) {
        minEdit->setEnabled(false);
        maxEdit->setEnabled(false);
    } else {
        minEdit->setEnabled(b);
        maxEdit->setEnabled(b);
    }
    minlabel->setEnabled(b);
    maxlabel->setEnabled(b);
}

void QFitsCubeSpectrum::resetRangeControl() {
    rangeControl->setSpectrum(0, 0, 0, 0, false);
}

void QFitsCubeSpectrum::changeWorkArray() {
//@    QReadLocker locker(&buffersLock);
    dpuserType *dpt = NULL;
    QFitsSingleBuffer *sb = fitsMainWindow->getActualSB();

    if (sb != NULL) {
        if (sb->getLockedSingleBuffer() != NULL) {
            sb = sb->getLockedSingleBuffer();
        }

        int     nelements   = 0;
        double  crpix       = 0.,
                crval       = 0.,
                cdelt       = 0.;
        bool    logwave     = false;

        dpt = sb->getDpData();
        if (dpt->type == typeFits) {
            Fits *f = dpt->fvalue;
            if ((f != NULL) &&
                (f->extensionType != EMPTY))
            {
                if (f->Naxis(0) == 3) {
                    nelements   = f->Naxis(3);
                    crpix       = f->getCRPIX(3),
                    crval       = f->getCRVAL(3),
                    cdelt       = f->getCDELT(3);
                    logwave     = f->isAxisLog(3);
                } else if (f->Naxis(0) == 2) {
                    int dim = sb->getCubeSpecOrientation();
                    nelements   = f->Naxis(dim);
                    crpix       = f->getCRPIX(dim),
                    crval       = f->getCRVAL(dim),
                    cdelt       = f->getCDELT(dim);
                    logwave     = f->isAxisLog(dim);
                } else if ((f->extensionType == BINTABLE) || (f->extensionType == TABLE) || (f->Naxis(0) == 1)) {
                    Fits *display1D = sb->getCubeDisplay1D();
                    if (display1D != NULL) {
                        nelements   = f->Naxis(1);
                        crpix       = f->getCRPIX(1),
                        crval       = f->getCRVAL(1),
                        cdelt       = f->getCDELT(1);
                        logwave     = f->isAxisLog(3);
                    }
                }
            }

            rangeControl->setSpectrum(nelements, crpix, crval, cdelt, logwave);

            if (sb->showSpectrum()) {
                show();
            } else {
                hide();
            }
        }
    }
}

void QFitsCubeSpectrum::changeWorkArrayTable() {
    QFitsSingleBuffer *sb = fitsMainWindow->getActualSB();
    if (sb != NULL) {
        if (sb->getLockedSingleBuffer() != NULL) {
            sb = sb->getLockedSingleBuffer();
        }

        if (sb->showSpectrum()) {
            show();
        } else {
            if (!fitsMainWindow->getCurrentBuffer()->getHasMixedView()) {
                hide();
            }
        }

        Fits *f = sb->getCubeDisplay1D();
        if (f != NULL) {
            rangeControl->setSpectrum(f->Naxis(1), f->getCRPIX(1), f->getCRVAL(1), f->getCDELT(1), false);
        } else {
            rangeControl->setSpectrum(0., 0., 0., 0., false);
        }
    } else {
        rangeControl->setSpectrum(0., 0., 0., 0., false);
    }
    fitsMainWindow->spectrum->update();
}

void QFitsCubeSpectrum::saveAscii(QFitsSingleBuffer *sb) {
    QReadLocker locker(&buffersLock);
    if ((sb != NULL) &&
        (sb->getDpData()->type == typeFits) &&
        (sb->getDpData()->fvalue != NULL))
    {
        Fits line,
             *f = sb->getDpData()->fvalue;
        double start = 0., delta = 0.;
        FILE *fd;
        QString fname;

        if (f->Naxis(3) > 1) {
            fname = QFileDialog::getSaveFileName(this,
                                                 "QFitsView - Save as ASCII",
                                                 settings.lastSavePath);
            if (fname.isNull()) {
                return;
            }
            QFileInfo finfo(fname);
            settings.lastSavePath = finfo.absoluteDir().path();
            settings.lastSavePath.replace("\\", "/");

            fd = fopen(fname.toStdString().c_str(), "w+b");
            if (fd == NULL) {
                fname = "Could not open file " + fname + " for writing";
                QMessageBox::warning(this, "QFitsView", fname);
                return;
            }

            delta = f->getCDELT(3);
            if (delta == 0.0)  {
                delta = 1.0;
            }
            start = f->getCRVAL(3) - delta * (f->getCRPIX(3) - 1);
            if (start == 0.0) {
                start = 1.0;
            }
            if (!sb->getHasManualSpectrum()) {
                switch (method) {
                case SinglePixel:
                    line.spec3d(*f, cenx, ceny, 0, 0);
                    break;
                case Circular:
                    line.spec3d(*f, cenx, ceny, r1, 0, statistics);
                    break;
                case CircularAnnular:
                    line.spec3d(*f, cenx, ceny, r1, r2, statistics);
                    break;
                default:
                    break;
                }
            } else {
                line = *(sb->getManualSpectrum());
            }

            if (useStandardStar->isChecked()) {
                line.div(standardStar);
            }

            for (int i = 0; i < line.Nelements(); i++) {
                if (fprintf(fd, "%20.10f %20.10f\n", pixwave(i, f->getCRPIX(3), f->getCRVAL(3), f->getCDELT(3), f->isAxisLog(3)), line.ValueAt(i)) < 0) {
                    QMessageBox::warning(this, "QFitsView", "Could not write data");
                    fclose(fd);
                    return;
                }
            }
            fclose(fd);
        } else {
            QMessageBox::information(this, "QFitsView", "Need a FITS cube to extract spectrum");
        }
    }
}

void QFitsCubeSpectrum::saveFitsSpectrum(QFitsSingleBuffer *sb) {
    QReadLocker locker(&buffersLock);
    if ((sb != NULL) &&
        (sb->getDpData()->type == typeFits) &&
        (sb->getDpData()->fvalue != NULL))
    {
        Fits *f = sb->getDpData()->fvalue;
        if (f->Naxis(3) > 1) {
            Fits    line;
            QString fname = QFileDialog::getSaveFileName(this,
                                                         "QFitsView - Save spectrum as FITS",
                                                         settings.lastSavePath);
            if (fname.isNull()) {
                return;
            }
            QFileInfo finfo(fname);
            settings.lastSavePath = finfo.absoluteDir().path();
            settings.lastSavePath.replace("\\", "/");

            if (!sb->getHasManualSpectrum()) {
                switch (method) {
                case SinglePixel:
                    line.spec3d(*(f), cenx, ceny, 0, 0);
                    break;
                case Circular:
                    line.spec3d(*(f), cenx, ceny, r1, 0, statistics);
                    break;
                case CircularAnnular:
                    line.spec3d(*(f), cenx, ceny, r1, r2, statistics);
                    break;
                default:
                    break;
                }
            } else {
                line = *(sb->getManualSpectrum());
            }
/*
            if (f->getCDELT(3) != 0.0) {
                double  value = 0.;
                char    unit[256];
                if (f->GetFloatKey("CRVAL3", &value)) {
                    line.SetFloatKey("CRVAL1", value);
                }
                if (f->GetFloatKey("CRPIX3", &value)) {
                    line.SetFloatKey("CRPIX1", value);
                }
                if (f->GetFloatKey("CDELT3", &value)) {
                    line.SetFloatKey("CDELT1", value);
                }
                if (f->GetStringKey("CUNIT3", unit)) {
                    line.SetStringKey("CUNIT1", unit);
                }
                if (f->GetStringKey("CTYPE3", unit)) {
                    line.SetStringKey("CTYPE1", unit);
                }
            }
*/
            if (useStandardStar->isChecked()) {
                line.div(standardStar);
            }

            if (!line.WriteFITS(fname.toStdString().c_str())) {
                fname = "Could not open file " + fname + " for writing";
                QMessageBox::warning(this, "QFitsView", fname);
            }
        } else {
            QMessageBox::information(this, "QFitsView", "Need a FITS cube to extract spectrum");
        }
    }
}

void QFitsCubeSpectrum::newBufferSpectrum(QFitsSingleBuffer *sb) {
    if ((sb != NULL) &&
        (sb->getDpData()->type == typeFits) &&
        (sb->getDpData()->fvalue != NULL))
    {
        if (!(buffersLock.tryLockForRead())) {
          return;
        }
        Fits *f     = sb->getDpData()->fvalue;
        bool flip   = (f->getCDELT(3) < 0.);
        int  naxis3 = f->Naxis(3);
        buffersLock.unlock();

        QString buffername  = freeBufferName().c_str(),
                cmd         = buffername + " = 3dspec(" +
                              QString(fitsMainWindow->getCurrentBufferIndex().c_str()) +
                              sb->getExtensionIndicesString() +
                              ", " + QString::number(cenx) + ", " + QString::number(ceny);

        if (naxis3 > 1) {
            if (sb->getHasManualSpectrum()) {
                Fits dummy;
                dummy.copy(*(sb->getManualSpectrum()));
                if (useStandardStar->isChecked()) {
                    dummy.div(fitsMainWindow->spectrum->standardStar);
                    injectVariable(buffername, dummy);
                } else {
                    injectVariable(buffername, dummy);
                }
                if (flip) {
                    cmd = "flip " + buffername + ", 1; view " + buffername;
                } else {
                    cmd = "view " + buffername;
                }
                dpuser_widget->executeCommand(cmd);
            } else {
                switch (method) {
                    case SinglePixel:
                        break;
                    case Circular:
                        cmd += ", " + QString::number(r1);
                        break;
                    case CircularAnnular:
                        cmd += ", " + QString::number(r1);
                        cmd += ", " + QString::number(r2);
                        break;
                    default:
                        break;
                }
                switch (statistics) {
                    case 0:
                        cmd += ", /sum";
                        break;
                    case 1:
                        cmd += ", /average";
                        break;
                    case 2:
                        cmd += ", /median";
                        break;
                    default:
                        break;
                }
                cmd += ")";

                if (useStandardStar->isChecked()) {
                    cmd += " / " + QString(fitsMainWindow->getCurrentBufferIndex().c_str()) +
                           "_standardstar";
                }
                if (flip) {
                    cmd += "; flip " + buffername + ", 1";
                }
                dpuser_widget->executeCommand(cmd);
            }
        } else {
            QMessageBox::information(this, "QFitsView", "Need a FITS cube to extract spectrum");
        }
    }
}

void QFitsCubeSpectrum::setR1(int newr1) {
    r1 = newr1;

    if (method == Circular) {
        fitsMainWindow->getCurrentBuffer()->
                getCursorMarkers()->setupCircleMarkers(r1);
    } else if (method == CircularAnnular) {
        fitsMainWindow->getCurrentBuffer()->
                getCursorMarkers()->setupCircleAnnularMarkers(r1, r2edit->value());
    }
    update();
}

void QFitsCubeSpectrum::setR2(int newr2) {
    r2 = newr2;
    if (method == CircularAnnular) {
        fitsMainWindow->getCurrentBuffer()->
                getCursorMarkers()->setupCircleAnnularMarkers(r1edit->value(), r2);
    }
    update();
}

void QFitsCubeSpectrum::setMin(const QString &min) {
    plotter->setYRange(min.toDouble(), maxEdit->text().toDouble());
}

void QFitsCubeSpectrum::setMax(const QString &max) {
    plotter->setYRange(minEdit->text().toDouble(), max.toDouble());
}

void QFitsCubeSpectrum::closeEvent(QCloseEvent *e) {
    emit(closing());
    QWidget::closeEvent(e);
}

void QFitsCubeSpectrum::setIgnore(bool ignore, double value) {
    ignoreInCube = ignore;
    ignoreValue = value;
}

//int QFitsCubeSpectrum::getXmin() {
//    return rangeControl->getChannelMin();
//}

//int QFitsCubeSpectrum::getXmax() {
//    return rangeControl->getChannelMax();
//}

void QFitsCubeSpectrum::saveStandardStar(QFitsSingleBuffer *sb, QFitsMarkers *markers) {
    QReadLocker locker(&buffersLock);
    if ((sb != NULL) &&
        (sb->getDpData()->type == typeFits) &&
        (sb->getDpData()->fvalue != NULL))
    {
        Fits *f = sb->getDpData()->fvalue;
        useStandardStar->setEnabled(true);
        QString cmd = QString(fitsMainWindow->getCurrentBufferIndex().c_str()) +
                      "_standardstar = 3dspec(" +
                      QString(fitsMainWindow->getCurrentBufferIndex().c_str()) +
                      sb->getExtensionIndicesString() +
                      ", " +
                      QString::number(cenx) + ", " + QString::number(ceny);

        if (markers->getSizeSource() > 0) {
            double csum = 0.0;
            long x = 0, y = 0;
            for (int i = 0; i < markers->getSizeSource(); i++) {
                markers->getMarkerSource(i, &x, &y);
                if ((x >= 1) && (x <= sb->Naxis(1)) &&
                    (y >= 1) && (y <= sb->Naxis(2)))
                {
                    csum++;
                }
            }
            standardStar.copy(*(sb->getManualSpectrum()));
            if (csum > 0.0) {
                standardStar /= csum;
            }
        } else {
            switch (method) {
            case SinglePixel:
                standardStar.spec3d(*f, cenx, ceny, 0, 0);
                break;
            case Circular:
                standardStar.spec3d(*f, cenx, ceny, r1, 0, statistics);
                cmd += ", " + QString::number(r1);
                break;
            case CircularAnnular:
                standardStar.spec3d(*f, cenx, ceny, r1, r2, statistics);
                cmd += ", " + QString::number(r1) + ", " + QString::number(r2);
                break;
            default:
                break;
            }
            switch (statistics) {
                case 0:
                    cmd += ", /sum";
                    break;
                case 1:
                    cmd += ", /average";
                    break;
                case 2:
                    cmd += ", /median";
                    break;
                default:
                    break;
            }
        }
        cmd += ")";
        dpuser_widget->executeCommand(cmd);
    }
}

void QFitsCubeSpectrum::useStandardStarClicked() {
    update();
}

void QFitsCubeSpectrum::spectrumRangeChanged(double min, double max) {
    if (isVisible()) {
        rangeControl->setPhysicalRange(min, max);
    }
}

void QFitsCubeSpectrum::newSlice(int which) {
    fitsMainWindow->getCurrentBuffer()->setCubeCenter(which, QFV::Wavelength, true);
    update();
}

void QFitsCubeSpectrum::setMethod(int newmethod) {
    method = (dpSpectrumMode)newmethod;
    setupMarkers();
    update();
}

void QFitsCubeSpectrum::setStatistics(int newstatistics) {
    statistics = newstatistics;
    emit statisticsChanged();
    update();
}

void QFitsCubeSpectrum::setupMarkers() {
    QFitsMarkers *m = fitsMainWindow->getCurrentBuffer()->getCursorMarkers();
    if (method == SinglePixel) {
        r1edit->setEnabled(false);
        r2edit->setEnabled(false);
        r1label->setEnabled(false);
        r2label->setEnabled(false);
        // don't paint default point marker
//        m->setupPointMarker();
        m->deleteAllMarkers();
    } else if (method == Circular) {
        r1edit->setEnabled(true);
        r2edit->setEnabled(false);
        r1label->setEnabled(true);
        r2label->setEnabled(false);

        m->setupCircleMarkers(r1edit->value());
    } else if (method == CircularAnnular) {
        r1edit->setEnabled(true);
        r2edit->setEnabled(true);
        r1label->setEnabled(true);
        r2label->setEnabled(true);

        m->setupCircleAnnularMarkers(r1edit->value(), r2edit->value());
    }
}

//------------------------------------------------------------------------------
//         QFitsSpectrumRangeControl
//------------------------------------------------------------------------------
QFitsSpectrumRangeControl::QFitsSpectrumRangeControl(QWidget *parent)
                                            : QWidget(parent)
{
//    setFixedHeight(20);

    channelMin = new QSpinBox(this);
    channelMin->setMinimum(1);
    channelMin->setMaximum(1e100);
    channelMin->setSingleStep(1);
    channelMin->setToolTip("x-Axis minimum in channels");
    channelMin->setKeyboardTracking(false);
    connect(channelMin, SIGNAL(valueChanged(int)),
            this, SLOT(setChannelMin(int)));

    channelMax = new QSpinBox(this);
    channelMax->setMinimum(2);
    channelMax->setMaximum(1e100);
    channelMax->setSingleStep(1);
    channelMax->setToolTip("x-Axis maximum in channels");
    channelMax->setKeyboardTracking(false);
    connect(channelMax, SIGNAL(valueChanged(int)),
            this, SLOT(setChannelMax(int)));

    physicalMin = new QDoubleSpinBox(this);
    physicalMin->setMinimum(-9999.9);
    physicalMin->setMaximum(-9999.9);
    physicalMin->setDecimals(10);
    physicalMin->adjustSize();
    physicalMin->setToolTip("x-Axis minimum in wavelength unit");
    physicalMin->setKeyboardTracking(false);
    connect(physicalMin, SIGNAL(valueChanged(double)),
            this, SLOT(setPhysicalMin(double)));

    physicalMax = new QDoubleSpinBox(this);
    physicalMax->setMinimum(-9999.9);
    physicalMax->setMaximum(-9999.9);
    physicalMax->setDecimals(10);
    physicalMax->adjustSize();
    physicalMax->setToolTip("x-Axis maximum in wavelength unit");
    physicalMax->setKeyboardTracking(false);
    connect(physicalMax, SIGNAL(valueChanged(double)),
            this, SLOT(setPhysicalMax(double)));

    channelMin->resize(physicalMin->size());
    channelMax->resize(physicalMax->size());

    rangeScroller = new QScrollBar(Qt::Horizontal, this);
    rangeScroller->setMinimum(1);
    rangeScroller->setTracking(true);
    rangeScroller->setFocusPolicy(Qt::ClickFocus);
    connect(rangeScroller, SIGNAL(valueChanged(int)),
            this, SLOT(scrollerMoved(int)));

    enablePhysicalRange = false;
    updateAll           = true;
}

void QFitsSpectrumRangeControl::setSpectrum(int   t_nelements,
                                           double t_crpix,
                                           double t_crval,
                                           double t_cdelt,
                                           bool t_logwave)
{
    updateAll = false;

    nelements   = t_nelements;
    crpix       = t_crpix;
    crval       = t_crval;
    cdelt       = t_cdelt;
    logwave     = t_logwave;

    if (nelements == 0) {
        channelMin->setMinimum(0);
        channelMax->setMinimum(0);
        physicalMin->setMinimum(0.);
        physicalMax->setMinimum(0.);

        channelMin->setValue(0);
        channelMax->setValue(0);
        physicalMin->setValue(0);
        physicalMax->setValue(0);

        channelMin->setEnabled(false);
        channelMax->setEnabled(false);
        physicalMin->setEnabled(false);
        physicalMax->setEnabled(false);
    } else {
        channelMin->setEnabled(true);
        channelMax->setEnabled(true);
        if ((crpix-1.0 < 1e-8) && (crpix-1.0 > -1e-8) &&
            (crval-1.0 < 1e-8) && (crval-1.0 > -1e-8) &&
            (cdelt-1.0 < 1e-8) && (cdelt-1.0 > -1e-8))
        {
            physicalMin->setEnabled(false);
            physicalMax->setEnabled(false);
        } else {
            physicalMin->setEnabled(true);
            physicalMax->setEnabled(true);
        }

        double  min = 0.,
                max = 0.;
        if (cdelt >= 0) {
            channelMin->setMinimum(1);
            channelMin->setMaximum(nelements - 1);
            channelMax->setMinimum(2);
            channelMax->setMaximum(nelements);

//            min = crval + (1 - crpix) * cdelt;
//            max = crval + (nelements - crpix) * cdelt;
            min = pixwave(1, crpix, crval, cdelt, logwave);
            max = pixwave(nelements, crpix, crval, cdelt, logwave);
            physicalMin->setMinimum(min);
//            physicalMin->setMaximum(crval + ((nelements - 1) - crpix) * cdelt);
            physicalMin->setMaximum(pixwave(nelements - 1, crpix, crval, cdelt, logwave));
//            physicalMax->setMinimum(crval + (2 - crpix) * cdelt);
            physicalMax->setMinimum(pixwave(2, crpix, crval, cdelt, logwave));
            physicalMax->setMaximum(max);

            rangeScroller->setInvertedAppearance(false);
        } else {
            channelMin->setMinimum(2);
            channelMin->setMaximum(nelements);
            channelMax->setMinimum(1);
            channelMax->setMaximum(nelements - 1);

//            min = crval + (nelements - crpix) * cdelt;
//            max = crval + (1 - crpix) * cdelt;
            min = pixwave(nelements, crpix, crval, cdelt, logwave);
            max = pixwave(1, crpix, crval, cdelt, logwave);
            physicalMin->setMinimum(min);
//            physicalMin->setMaximum(crval + (2 - crpix) * cdelt);
            physicalMin->setMaximum(pixwave(2, crpix, crval, cdelt, logwave));
//            physicalMax->setMinimum(crval + ((nelements - 1) - crpix) * cdelt);
            physicalMax->setMinimum(pixwave(nelements - 1, crpix, crval, cdelt, logwave));
            physicalMax->setMaximum(max);

            rangeScroller->setInvertedAppearance(true);
        }

        physicalMin->setSingleStep(fabs(cdelt));
        physicalMax->setSingleStep(fabs(cdelt));

        QFitsWidget1D *oneD = dynamic_cast<QFitsWidget1D *>(parent());
        QFitsSingleBuffer *sb;
        if (oneD != NULL) {
            sb = oneD->getMyBuffer();
        } else {
            sb = fitsMainWindow->getActualSB();
        }

//        QFitsSingleBuffer *sb = fitsMainWindow->getActualSB();

        if ((sb != NULL) &&
            (sb->getDpData()->fvalue != NULL) &&
            (sb->getDpData()->fvalue->extensionType != EMPTY))
        {
            setPhysicalRange(sb->getSpecPhysMinX(),
                             sb->getSpecPhysMaxX());
        } else {
            // this is the case for e.g. GRAV files with string columns
            // and when selecting a row there
            setPhysicalRange(min, max);
        }
    }

//    channelMin->adjustSize();
//    channelMax->adjustSize();
    physicalMin->adjustSize();
    physicalMax->adjustSize();
    resizeControls(width(), height());

    updateScroller();

    updateAll = true;

    update();
}

void QFitsSpectrumRangeControl::setPhysicalRange(double min, double max) {
    QFitsWidget1D *oneD = dynamic_cast<QFitsWidget1D *>(parent());
    QFitsBaseBuffer *bb;
    if (oneD != NULL) {
        bb = oneD->getMyBuffer();
    } else {
        bb = fitsMainWindow->getActualSB();
        if (bb == NULL) {
            bb = dynamic_cast<QFitsMultiBuffer*>(fitsMainWindow->getCurrentBuffer());
        }
    }

    if (bb != NULL) {
        enablePhysicalRange = true;
        updateAll = false;

        double  physMin = min,
                physMax = max,
                tempr   = 0.;

        if (cdelt >= 0) {
            if (physMin < pixwave(1, crpix, crval, cdelt, logwave)) {
                physMin = pixwave(1, crpix, crval, cdelt, logwave);
            }
            if (physMax > pixwave(nelements, crpix, crval, cdelt, logwave)) {
                physMax = pixwave(nelements, crpix, crval, cdelt, logwave);
            }
            if (physMin > physMax) {
                SWAP(physMin, physMax);
            }
            if (fabs(physMin - physMax) < fabs(cdelt) / 10.) {
                physMax = physMin + cdelt;
            }
            if (physMax > pixwave(nelements, crpix, crval, cdelt, logwave)) {
                physMin = pixwave(nelements - 1, crpix, crval, cdelt, logwave);
                physMax = pixwave(nelements, crpix, crval, cdelt, logwave);
            }
        } else {
            if (physMin < pixwave(nelements, crpix, crval, cdelt, logwave)) {
                physMin = pixwave(nelements, crpix, crval, cdelt, logwave);
            }
            if (physMax > pixwave(1, crpix, crval, cdelt, logwave)) {
                physMax = pixwave(1, crpix, crval, cdelt, logwave);
            }
            if (physMin > physMax) {
                SWAP(physMin, physMax);
            }
            if (fabs(physMin - physMax) < fabs(cdelt) / 10.) {
                physMax = physMin - cdelt;
            }
            if (physMax > pixwave(1, crpix, crval, cdelt, logwave)) {
                physMax = pixwave(1, crpix, crval, cdelt, logwave);
                physMin = physMax +cdelt;
            }
        }

        bb->setSpecPhysMinX(physMin);
        bb->setSpecPhysMaxX(physMax);

        physicalMin->setValue(physMin);
        physicalMax->setValue(physMax);
        channelMin->setValue(bb->getSpecChannelMinX());
        channelMax->setValue(bb->getSpecChannelMaxX());

        updateScroller();
        updateAll = true;

        QFitsWidgetTable *wt = dynamic_cast<QFitsWidgetTable*>(bb->getState());
        if (wt != NULL) {
            // notify TableView about changed channelRange
            wt->setBruteRange(bb->getSpecChannelMinX(), bb->getSpecChannelMaxX());
        }

        emit channelRangeChanged();
    }
}

void QFitsSpectrumRangeControl::setChannelRange(int min, int max) {
    QFitsWidget1D *oneD = dynamic_cast<QFitsWidget1D *>(parent());
    QFitsBaseBuffer *bb;
    if (oneD != NULL) {
        bb = oneD->getMyBuffer();
    } else {
        bb = fitsMainWindow->getActualSB();
        if (bb == NULL) {
            bb = dynamic_cast<QFitsMultiBuffer*>(fitsMainWindow->getCurrentBuffer());
        }
    }

    if (bb != NULL) {
        updateAll = false;

        int chMin = min,
            chMax = max,
            tempr = 0;

        if (cdelt >= 0.) {
            if (chMin < 1) {
                chMin = 1;
            }
            if (chMax > nelements) {
                chMax = nelements;
            }
            if (chMin > chMax) {
                SWAP(chMin, chMax);
            }
            if (chMin == chMax) {
                chMax = chMin + 1;
            }
            if (chMax > nelements) {
                chMin = nelements - 1;
                chMax = nelements;
            }
        } else {
            if (chMax < 1) {
                chMax = 1;
            }
            if (chMin > nelements) {
                chMin = nelements;
            }
            if (chMax > chMin) {
                SWAP(chMax, chMin);
            }
            if (chMax == chMin) {
                chMin = chMax + 1;
            }
            if (chMin > nelements) {
                chMax = nelements - 1;
                chMin = nelements;
            }
        }

        bb->setSpecChannelMinX(chMin);
        bb->setSpecChannelMaxX(chMax);

        channelMin->setValue(chMin);
        channelMax->setValue(chMax);
        physicalMin->setValue(bb->getSpecPhysMinX());
        physicalMax->setValue(bb->getSpecPhysMaxX());

        updateScroller();
        updateAll = true;

        emit channelRangeChanged();
    }
}

void QFitsSpectrumRangeControl::updateScroller() {
    if (!rangeScroller->hasFocus()) {
        if ((channelMin->value() == 0)
            ||
            (!rangeScroller->invertedAppearance() &&
            (channelMin->value() == 1) &&
            (channelMax->value() == nelements))
            ||
            (rangeScroller->invertedAppearance() &&
            (channelMin->value() == nelements) &&
            (channelMax->value() == 1)))
        {
            rangeScroller->hide();
        } else {
            rangeScroller->setPageStep((channelMax->value() - channelMin->value()));
            if (!rangeScroller->invertedAppearance()) {
                rangeScroller->setMaximum(channelMax->maximum() - rangeScroller->pageStep());
                rangeScroller->setValue(channelMin->value());
            } else {
                rangeScroller->setMaximum(channelMin->maximum() - rangeScroller->pageStep());
                rangeScroller->setValue(channelMax->value());
            }
            rangeScroller->show();
            // todo: force widget to redraw, could be done e.g. by rangescroller->setFocus(), but this has side effects
        }
    }
}

void QFitsSpectrumRangeControl::resizeControls(int widgetWidth, int widgetHeight) {
    channelMin->setGeometry(0, 0,
                            channelMin->width(), widgetHeight);
    physicalMin->setGeometry(channelMin->width(), 0,
                             physicalMin->width(), widgetHeight);

    channelMax->setGeometry(widgetWidth-channelMax->width(), 0,
                            channelMax->width(), widgetHeight);
    physicalMax->setGeometry(channelMax->x()-physicalMax->width(), 0,
                             physicalMax->width(), widgetHeight);

    int corrLeft  = 2,
        corrRight = 4;
    rangeScroller->setGeometry(physicalMin->x()+physicalMin->width()+corrLeft, 0,
                               widgetWidth-channelMin->width()-channelMax->width()-physicalMin->width()-physicalMax->width()-corrRight, widgetHeight);
}

void QFitsSpectrumRangeControl::resizeEvent(QResizeEvent *e) {
    resizeControls(e->size().width(), e->size().height());
}

void QFitsSpectrumRangeControl::setChannelMin(int min) {
    if (updateAll) {
        setChannelRange(min, channelMax->value());
    }
}

void QFitsSpectrumRangeControl::setChannelMax(int max) {
    if (updateAll) {
        setChannelRange(channelMin->value(), max);
    }
}

void QFitsSpectrumRangeControl::setPhysicalMin(double min) {
    if (updateAll) {
        setPhysicalRange(min, physicalMax->value());
    }
}

void QFitsSpectrumRangeControl::setPhysicalMax(double max) {
    if (updateAll) {
        setPhysicalRange(physicalMin->value(), max);
    }
}

void QFitsSpectrumRangeControl::scrollerMoved(int newvalue) {
    if (updateAll) {
        int min = newvalue,
            max = newvalue + rangeScroller->pageStep();
        setChannelRange(min, max);
    }
}

//------------------------------------------------------------------------------
//         QFitsCubeSpectrumViewer
//------------------------------------------------------------------------------
QFitsCubeSpectrumViewer::QFitsCubeSpectrumViewer(QFitsCubeSpectrum *parent)
                      : QFitsView1D(parent), myParent(parent)
{
    messages += "Mouse-left: select range; "
                "Key-c: Display Center; Key-x: Continuum 1; Key-v: Continuum 2";
    messages += "drag to select linemap center; "
                "release in place selects channel map";
    messages += "drag to select linemap continuum 1; "
                "release in place removes continuum 1";
    messages += "drag to select linemap continuum 2; "
                "release in place removes continuum 2";
    messages += "Mark a buffer to select range, center or continuum (Left mouse click)";
    cdata           = NULL;
    pressedKey      = 0;
    pressedKeyLeft  = 0;
    pressedKeyRight = 0;
    cmin            = DBL_MAX;
    cmax            = -DBL_MAX;
}

QFitsCubeSpectrumViewer::~QFitsCubeSpectrumViewer() {
    free(cdata);
}

void QFitsCubeSpectrumViewer::paintEvent(QPaintEvent*) {
    if (ndata == 0) {
        // overpaint everything to blank
        QPainter p;
        p.begin(this);
        // get default grey background color
        int r = 0, g = 0, b = 0;
        QApplication::palette().window().color().getRgb(&r, &g, &b);
        if (r == 255 && g == 255 && b == 255) {
            // FIXME: hack for MacOS: Avoid white background
            r = g = b = 237;
        }
        p.setBrush(QColor(r, g, b));
        p.setPen(QColor(r, g, b));
        p.drawRect(rect());
        p.end();
        return;
    }

    QFitsSingleBuffer *sb = getMyBuffer();
    if (sb != NULL) {
        // get min/max-extend in Y depending if contiunuum is painted as well
        double  ymin    = sb->getSpecPhysMinY(),
                ymax    = sb->getSpecPhysMaxY();

//        // would be more correct, but doesn't look nice
//        // rather add additional right-handy-labels
//        if (cdata != NULL) {
//            if (cmin < ymin) {
//                ymin = cmin;
//            }
//            if (cmax > ymax) {
//                ymax = cmax;
//            }
//        }

        QPainter p;
        p.begin(this);
//        p.setPen(QColor(0, 0, 0));
        p.setPen(QWidget::palette().color(QWidget::foregroundRole()));

        // draw axes
        drawAxis(&p, ymin, ymax);

        // flip coordinate system in Y for subsequent painting
        QTransform origMatrix = p.worldTransform();
        p.setWorldTransform(m);
        p.setClipRect(0, -1, plotwidth, plotheight+1);

        // paint continuum spectrum (green filled)
        int j     = 0,
            alpha = 200;
        if (cdata != NULL) {
            QPolygon carray;
            j = calcPolyline(ndata, xdata, cdata, carray, cmin, cmax, true);
            p.setPen(QColor(0, 230, 0));
            p.setBrush(QColor(0, 230, 0));
            p.drawPolygon(carray);
        }

        if ((sb->getCubeMode() == DisplayCubeLinemap) ||
            (sb->getCubeCenter(QFV::Default) > 0))
        {
            QPolygon markarray;
            markarray.resize(4);

            int     c1  = 0,
                    c2  = 0;
            QColor  lightGray(200, 200, 200, alpha),
                    darkGray(150, 150, 150, alpha);
            switch (sb->getCubeMode()) {
                case DisplayCubeSingle:
                    // paint narrow (single) source rectangle (light grey filled)
//                    c1 = (int)(wavelengthToPixel(crval + (sb->getCubeCenter(QFV::Default) - crpix) * cdelt - cdelt / 2.) + 0.5);
//                    c2 = (int)(wavelengthToPixel(crval + (sb->getCubeCenter(QFV::Default) - crpix) * cdelt + cdelt / 2.) + 0.5);
                    c1 = wavelengthToPixel(pixwave(sb->getCubeCenter(QFV::Default) - 0.5, crpix, crval, cdelt, logwave));
                    c2 = wavelengthToPixel(pixwave(sb->getCubeCenter(QFV::Default) + 0.5, crpix, crval, cdelt, logwave));
                    markarray.setPoint(0, c1, 0);
                    markarray.setPoint(1, c1, plotheight);
                    markarray.setPoint(2, c2, plotheight);
                    markarray.setPoint(3, c2, 0);
                    p.setPen(lightGray);
                    p.setBrush(lightGray);
                    p.drawPolygon(markarray);
                    break;
                case DisplayCubeAverage:
                case DisplayCubeMedian:
                    // paint source rectangle filling the whole spectrum (light grey filled)
//                    c1 = (int)(wavelengthToPixel(crval + (1 - crpix) * cdelt - cdelt / 2.) + 0.5);
//                    c2 = (int)(wavelengthToPixel(crval + (sb->Naxis(sb->getCubeSpecOrientation()) - crpix) * cdelt + cdelt / 2.) + 0.5);
                    c1 = wavelengthToPixel(pixwave(0.5, crpix, crval, cdelt, logwave));
                    c2 = wavelengthToPixel(pixwave(sb->Naxis(sb->getCubeSpecOrientation()) + 0.5, crpix, crval, cdelt, logwave));
                    if (c1 > c2) {
                        int tmp = c1;
                        c1 = c2;
                        c2 = tmp;
                    }
                    markarray.setPoint(0, c1, 0);
                    markarray.setPoint(1, c1, plotheight);
                    markarray.setPoint(2, c2, plotheight);
                    markarray.setPoint(3, c2, 0);
                    p.setPen(lightGray);
                    p.setBrush(lightGray);
                    p.drawPolygon(markarray);
                    break;
                case DisplayCubeLinemap:
                    // paint source rectangle (light grey filled)
                    if ((sb != NULL) &&
                        (sb->getCubeCenter(QFV::Default) >= 1.))
                    {
                        // in TableView the linewidth is set to 0 when all rows are visible
                        // don't draw the rectangle in this case
//                        c1 = (int)(wavelengthToPixel(crval + (sb->getCubeCenter(QFV::Default) - sb->getLineWidth(QFV::Default) - crpix) * cdelt - cdelt / 2.) + 0.5);
//                        c2 = (int)(wavelengthToPixel(crval + (sb->getCubeCenter(QFV::Default) + sb->getLineWidth(QFV::Default) - crpix) * cdelt + cdelt / 2.) + 0.5);
                        c1 = wavelengthToPixel(pixwave(sb->getCubeCenter(QFV::Default) - sb->getLineWidth(QFV::Default) - 0.5, crpix, crval, cdelt, logwave));
                        c2 = wavelengthToPixel(pixwave(sb->getCubeCenter(QFV::Default) + sb->getLineWidth(QFV::Default) + 0.5, crpix, crval, cdelt, logwave));
                        if (c1 > c2) {
                            int tmp = c1;
                            c1 = c2;
                            c2 = tmp;
                        }
                        markarray.setPoint(0, c1, 0);
                        markarray.setPoint(1, c1, plotheight);
                        markarray.setPoint(2, c2, plotheight);
                        markarray.setPoint(3, c2, 0);
                        p.setPen(lightGray);
                        p.setBrush(lightGray);
                        p.drawPolygon(markarray);
                    }

                    // paint lower continum rectangle (dark grey filled)
                    if ((sb != NULL) &&
                        (sb->getLineCont1()))
                    {
//                        c1 = (int)(wavelengthToPixel(crval + (sb->getCubeCenter(QFV::Default) + sb->getLineCont1Center() - sb->getLineCont1Width() - crpix) * cdelt - cdelt / 2.) + 0.5);
//                        c2 = (int)(wavelengthToPixel(crval + (sb->getCubeCenter(QFV::Default) + sb->getLineCont1Center() + sb->getLineCont1Width() - crpix) * cdelt + cdelt / 2.) + 0.5);
                        c1 = wavelengthToPixel(pixwave(sb->getCubeCenter(QFV::Default) + sb->getLineCont1Center() - sb->getLineCont1Width() - 0.5, crpix, crval, cdelt, logwave));
                        c2 = wavelengthToPixel(pixwave(sb->getCubeCenter(QFV::Default) + sb->getLineCont1Center() + sb->getLineCont1Width() + 0.5, crpix, crval, cdelt, logwave));
                        if (c1 > c2) {
                            int tmp = c1;
                            c1 = c2;
                            c2 = tmp;
                        }
                        markarray.setPoint(0, c1, 0);
                        markarray.setPoint(1, c1, plotheight);
                        markarray.setPoint(2, c2, plotheight);
                        markarray.setPoint(3, c2, 0);
                        p.setPen(darkGray);
                        p.setBrush(darkGray);
                        p.drawPolygon(markarray);
                    }

                    // paint upper continum rectangle (dark grey filled)
                    if ((sb != NULL) &&
                        (sb->getLineCont2()))
                    {
//                        c1 = (int)(wavelengthToPixel(crval + (sb->getCubeCenter(QFV::Default) + sb->getLineCont2Center() - sb->getLineCont2Width() - crpix) * cdelt - cdelt / 2.) + 0.5);
//                        c2 = (int)(wavelengthToPixel(crval + (sb->getCubeCenter(QFV::Default) + sb->getLineCont2Center() + sb->getLineCont2Width() - crpix) * cdelt + cdelt / 2.) + 0.5);
                        c1 = wavelengthToPixel(pixwave(sb->getCubeCenter(QFV::Default) + sb->getLineCont2Center() - sb->getLineCont2Width() - 0.5, crpix, crval, cdelt, logwave));
                        c2 = wavelengthToPixel(pixwave(sb->getCubeCenter(QFV::Default) + sb->getLineCont2Center() + sb->getLineCont2Width() + 0.5, crpix, crval, cdelt, logwave));
                        if (c1 > c2) {
                            int tmp = c1;
                            c1 = c2;
                            c2 = tmp;
                        }
                        markarray.setPoint(0, c1, 0);
                        markarray.setPoint(1, c1, plotheight);
                        markarray.setPoint(2, c2, plotheight);
                        markarray.setPoint(3, c2, 0);
                        p.setPen(darkGray);
                        p.setBrush(darkGray);
                        p.drawPolygon(markarray);
                    }
                    break;
            default:
                break;
            }
        }

        // paint red rectangle when selecting source/continuum
        if (pressedKey != 0) {
            QPolygon markarray;
            markarray.resize(4);
            markarray.setPoint(0, pressedKeyLeft, 0);
            markarray.setPoint(1, pressedKeyLeft, plotheight);
            markarray.setPoint(2, pressedKeyRight, plotheight);
            markarray.setPoint(3, pressedKeyRight, 0);
            p.setPen(QColor(240, 0, 0));
            p.setBrush(QColor(240, 0, 0));
            p.drawPolygon(markarray);
        }

        // finally paint spectrum (black line)
        QPolygon array;
        j = calcPolyline(ndata, xdata, ydata, array, ymin, ymax);
//        p.setPen(QColor(0, 0, 0));
        p.setPen(QWidget::palette().color(QWidget::foregroundRole()));
        p.setBrush(Qt::NoBrush);
        p.drawPolyline(array.constData(), j);
        drawSelection(&p);

        // paint cursor position information
        p.setWorldTransform(origMatrix);
        p.setClipping(false);
        p.drawText(QRect(0, 0, width() - margin, fh), Qt::AlignRight|Qt::AlignBottom, posinfo);

        p.end();
    }
}

//void QFitsCubeSpectrumViewer::resizeEvent(QResizeEvent *e) {
//    QFitsView1D::resizeEvent(e);
//    fh = (int)((double)fh * 0.6);
//}

void QFitsCubeSpectrumViewer::mouseMoveEvent(QMouseEvent *e) {
    if (!hasFocus()) {
        setFocus();
    }

    calcPos(e->pos());

    QFitsSingleBuffer *sb = getMyBuffer();
    if ((sb != NULL) &&
        (pressedKey != 0))
    {
        pressedKeyRight = e->pos().x() - margin - fw;
    }

    update();
}

void QFitsCubeSpectrumViewer::enterEvent(QEvent *e) {
    QFitsSingleBuffer *sb = getMyBuffer();
    if (sb != NULL) {
        // Either QFitsSingleBuffer or QFitsMultiBuffer with marked QFitsSingleBuffer
        emit statusbartext(messages[0]);
    } else {
        // QFitsMultiBuffer, no QFitsSingleBuffer marked!
        emit statusbartext(messages[4]);
    }
}

void QFitsCubeSpectrumViewer::keyPressEvent(QKeyEvent *e) {
    QFitsSingleBuffer *sb = getMyBuffer();
    if ((sb != NULL) &&
        (!e->isAutoRepeat()) &&
        (dynamic_cast<QFitsWidgetTable*>(sb->getState()) == NULL))
    {
        if ((e->key() == Qt::Key_C) ||
            (e->key() == Qt::Key_X) ||
            (e->key() == Qt::Key_V))
        {
            pressedKey = e->key();
            pressedKeyLeft = pressedKeyRight =
                             mapFromGlobal(cursor().pos()).x() - margin - fw;

            if (e->key() == Qt::Key_C) {
                emit statusbartext(messages[1]);
//#ifdef HAS_VTK
            } else if ((sb != NULL) && (sb->getViewMode() != View3D)) {
//#else
//            } else if (sb != NULL) {
//#endif
                switch (e->key()) {
                    case Qt::Key_X:
                        emit statusbartext(messages[2]);
                        break;
                    case Qt::Key_V:
                        emit statusbartext(messages[3]);
                        break;
                    default:
                        fitsMainWindow->main_view->keyPressEvent(e);
                        break;
                }
            } else {
                fitsMainWindow->main_view->keyPressEvent(e);
            }
        } else {
            fitsMainWindow->main_view->keyPressEvent(e);
        }
    }
}

void QFitsCubeSpectrumViewer::keyReleaseEvent(QKeyEvent *e) {
    QReadLocker locker(&buffersLock);

    QFitsSingleBuffer *sb = getMyBuffer();
    if ((sb != NULL) &&
        (sb->getDpData()->type == typeFits) &&
        (dynamic_cast<QFitsWidgetTable*>(sb->getState()) == NULL))
    {
        Fits *f = sb->getDpData()->fvalue;
        if (f == NULL) {
            return;
        }
        if ((!e->isAutoRepeat()) && (pressedKey != 0)) {
            double  crval    = f->getCRVAL(3),
                    crpix    = f->getCRPIX(3),
                    cdelt    = f->getCDELT(3),
                    leftKey  = pixelToWavelength(pressedKeyLeft),
                    rightKey = pixelToWavelength(pressedKeyRight),
                    width    = fabs(leftKey - rightKey) / 2.0 / fabs(cdelt);
            sb->setDirty(true);

            switch (pressedKey) {
                case Qt::Key_C:
                    if (width > 0) {
                        sb->setCubeMode(DisplayCubeLinemap);
                        sb->setLineWidth(width, QFV::Default);
//                        sb->setCubeCenter(crpix + ((leftKey + rightKey) / 2.0 - crval) / cdelt,
//                                          QFV::Default);
                    } else {
                        sb->setCubeMode(DisplayCubeSingle);
                        sb->setLineWidth(1, QFV::Default);
//                        sb->setCubeCenter(crpix + ((leftKey + rightKey) / 2.0 - crval) / cdelt,
//                                          QFV::Default);
                    }
                    sb->setCubeCenter(wavepix((leftKey + rightKey) / 2., crpix, crval, cdelt, f->isAxisLog(3)), QFV::Default);
                    break;
                default:
                    break;
            }

//#ifdef HAS_VTK
            if (sb->getViewMode() != View3D)
//#endif
            {
                switch (pressedKey) {
                    case Qt::Key_X:
                        if (width > 0) {
                            sb->setLineCont1(true);
//                            sb->setLineCont1Center((int)(crpix + ((leftKey + rightKey) / 2.0 - crval) / cdelt - sb->getCubeCenter(QFV::Default)) + 0.5);
                            sb->setLineCont1Center(wavepix((leftKey + rightKey) / 2.0, crpix, crval, cdelt, f->isAxisLog(3)) - sb->getCubeCenter(QFV::Default));
                            sb->setLineCont1Width(width);
                        } else {
                            sb->setLineCont1(false);
                        }
                        break;
                    case Qt::Key_V:
                        if (width > 0) {
                            sb->setLineCont2(true);
//                            sb->setLineCont2Center((int)(crpix + ((leftKey + rightKey) / 2.0 - crval) / cdelt - sb->getCubeCenter(QFV::Default)) + 0.5);
                            sb->setLineCont2Center(wavepix((leftKey + rightKey) / 2.0, crpix, crval, cdelt, f->isAxisLog(3)) - sb->getCubeCenter(QFV::Default));
                            sb->setLineCont2Width(width);
                        } else {
                            sb->setLineCont2(false);
                        }
                        break;
                    default:
                        break;
                }
            }
            pressedKey = 0;
            doUpdate();
            emit statusbartext(messages[0]);
        }
    } else {
        pressedKey = 0;
    }
}

// only needed for View3D
void QFitsCubeSpectrumViewer::doUpdate() {
    update();

#ifdef HAS_VTK
    QFitsSingleBuffer *sb = getMyBuffer();
    if (sb != NULL) {
        if (sb->getViewMode() == View3D) {
            sb->new3Ddata2();
        } else {
            fitsMainWindow->updateScaling();
        }
    }
#else
    fitsMainWindow->updateScaling();
#endif
}

void QFitsCubeSpectrumViewer::setCData(const Fits &c) {
    cdata = (double *)realloc(cdata, c.Nelements() * sizeof(double));
    unsigned long i, j;

    if (c.getCDELT(1) > 0.0) {
        for (i = 0; i < c.Nelements(); i++) {
            cdata[i] = c.ValueAt(i);
        }
    } else {
        for (i = 0, j = ndata-1; i < c.Nelements(); i++, j--) {
            cdata[j] = c.ValueAt(i);
        }
    }
}

void QFitsCubeSpectrumViewer::removeCData() {
    free(cdata);
    cdata = NULL;
}

void QFitsCubeSpectrumViewer::updateYRange(int vMin, int vMax) {
    if (ydata == NULL || !myParent->getAutoScale()) {
        return;
    }

    if (cdelt < 0.) {
        vMin = ndata - vMin + 1;
        vMax = ndata - vMax + 1;
    }

    double  ymin = -1.0,
            ymax = 1.0;
    if ((vMin < 1) || (vMax > ndata) || (vMax < 1) || (vMin == vMax) || (ndata < 2)) {
        // do nothing anymore
    } else {
        if (vMax < vMin) {
            int tmp = vMin;
            vMin = vMax;
            vMax = tmp;
        }

        ymin = DBL_MAX;
        ymax = -DBL_MAX;
        cmin = DBL_MAX;
        cmax = -DBL_MAX;
        for (int i = vMin-1; i < vMax; i++) {
            if (gsl_finite(ydata[i])) {
                if (ydata[i] > ymax) {
                    ymax = ydata[i];
                }
                if (ydata[i] < ymin) {
                    ymin = ydata[i];
                }
            }
        }

        if (cdata != NULL) {
            for (int i = vMin-1; i < vMax; i++) {
                if (gsl_finite(cdata[i])) {
                    if (cdata[i] > cmax) {
                        cmax = cdata[i];
                    }
                    if (cdata[i] < cmin) {
                        cmin = cdata[i];
                    }
                }
            }
        }

        if ((ymin == ymax) || (ymin == DBL_MAX)) {
            ymin -= 1.0;
            ymax += 1.0;
            if (cdata != NULL) {
                ymin = cmin;
                ymax = cmax;
            }
        }
    }

    QFitsSingleBuffer *sb = getMyBuffer();
    if (sb != NULL) {
        sb->setSpecPhysRangeY(ymin, ymax);
    }
}

void QFitsCubeSpectrumViewer::setXRange(const double &xMin, const double &xMax) {
    double  tmp_xmin = xMin,
            tmp_xmax = xMax;

    if (xMin > xMax) {
        tmp_xmin = xMax;
        tmp_xmax = xMin;
    }
    if (tmp_xmax - tmp_xmin < cdelt)
        tmp_xmax = tmp_xmin + cdelt * 1.1;

    if ((ndata != 0) && myParent->getAutoScale()) {
        cmin = -1.;
        cmax = 1.;

        unsigned long i = 0;
        while ((xdata[i] < tmp_xmin) && (i < ndata - 1)) {
            i++;
        }

        while ((!gsl_finite(ydata[i])) && (i < ndata - 1)) {
            i++;
        }

        if ((cdata != NULL) && gsl_finite(cdata[i])) {
            cmin = cmax = cdata[i];
        }

        while ((xdata[i] <= tmp_xmax) && (i < ndata - 1)) {
            if (cdata != NULL) {
                if ((cdata[i] > cmax) && gsl_finite(cdata[i])) {
                    cmax = cdata[i];
                }
                if ((cdata[i] < cmin) && gsl_finite(cdata[i])) {
                    cmin = cdata[i];
                }
            }
            i++;
        }
    }

    QFitsSingleBuffer *sb = fitsMainWindow->getActualSB();
    if (sb != NULL) {
        QFitsWidgetTable *wt = dynamic_cast<QFitsWidgetTable*>(sb->getState());
        if (wt != NULL) {
            wt->setBruteRange(tmp_xmin, tmp_xmax);
        }
    }

    fitsMainWindow->spectrum->spectrumRangeChanged(tmp_xmin, tmp_xmax);

    update();
}
