
/***************************** LICENSE START ***********************************

 Copyright 2012 ECMWF and INPE. This software is distributed under the terms
 of the Apache License version 2.0. In applying this license, ECMWF does not
 waive the privileges and immunities granted to it by virtue of its status as
 an Intergovernmental Organization or submit itself to any jurisdiction.

 ***************************** LICENSE END *************************************/

#include "MvQColourGradLine.h"

#include <QAction>
#include <QDebug>
#include <QLabel>
#include <QMenu>
#include <QMouseEvent>
#include <QPainter>
#include <QToolButton>
#include <QLinearGradient>

#include <algorithm>

#include "MvQMethods.h"
#include "MvQPalette.h"
#include "MvQRequestPanelHelp.h"
#include "TextDialog.h"

#include "LineFactory.h"
#include "RequestPanel.h"

//#define DEBUG_MV_MVQCOLOURGRADLINE__

void ColorCell::render(QPainter* painter)
{
    QPolygonF p(5);
    p[0] = QPointF(xp, yp);
    p[1] = QPointF(xp + w / 2, yp + h - w);
    p[2] = QPointF(xp + w / 2, yp + h);
    p[3] = QPointF(xp - w / 2, yp + h);
    p[4] = QPointF(xp - w / 2, yp + h - w);

    painter->setRenderHint(QPainter::Antialiasing, true);
    painter->setBrush(QColor(240, 240, 240));
    painter->setPen(QColor(50, 50, 50));
    painter->drawConvexPolygon(p);

    painter->setRenderHint(QPainter::Antialiasing, false);
    painter->setPen(QColor(140, 140, 140));
    painter->drawLine(xp + w / 2, yp + h - w, xp - w / 2, yp + h - w);

    // Color rect
    QRect r(xp - w / 2, yp + h - w, w, w);
    r.adjust(2, 2, -2, -2);
    painter->setBrush(col);
    painter->fillRect(r, col);
}

void ColorCell::renderSelection(QPainter* painter)
{
    QPolygonF outer(QRectF(xp - w / 2 - 5, yp - 1, w + 10, h + 2));

    QPolygonF inner(5);
    inner[0] = QPointF(xp - 1, yp - 1);
    inner[1] = QPointF(xp + w / 2 + 1, yp + h - w);
    inner[2] = QPointF(xp + w / 2 + 1, yp + h + 1);
    inner[3] = QPointF(xp - w / 2 - 1, yp + h + 1);
    inner[4] = QPointF(xp - w / 2 - 1, yp + h - w);

    painter->setRenderHint(QPainter::Antialiasing, true);

    // background
    painter->setPen(Qt::NoPen);
    painter->setBrush(QColor(242, 243, 255));
    painter->drawPolygon(outer.subtracted(inner));

    // outline
    painter->setPen(QPen(QColor(20, 20, 20), 1));
    painter->setBrush(Qt::NoBrush);
    painter->drawPolygon(inner);
}
//==============================================
//
// MvQColourGradWidget
//
//==============================================

MvQColourGradWidget::MvQColourGradWidget(QWidget* parent) :
    QWidget(parent),
    edited_(false),
    currentCell_(-1),
    editorGapX_(2),
    editorGapY_(4),
    gradHeight_(30)
{
    blackPen_ = QPen(Qt::black, 2., Qt::SolidLine);
    whitePen_ = QPen(Qt::white, 2., Qt::SolidLine);

    setContextMenuPolicy(Qt::ActionsContextMenu);

    insertBeforeAc_ = new QAction(this);
    insertBeforeAc_->setText(tr("Insert before cell"));
    insertBeforeAc_->setData("insertBefore");
    insertBeforeAc_->setIcon(QPixmap(":/desktop/insertBefore.svg"));
    insertBeforeAc_->setToolTip(tr("Insert before cell"));

    addAction(insertBeforeAc_);

    connect(insertBeforeAc_, SIGNAL(triggered()),
            this, SLOT(slotInsertBeforeCell()));

    insertAfterAc_ = new QAction(this);
    insertAfterAc_->setText(tr("Insert after cell"));
    insertAfterAc_->setData("insertAfter");
    insertAfterAc_->setIcon(QPixmap(":/desktop/insertAfter.svg"));
    insertAfterAc_->setToolTip(tr("Insert after cell"));

    addAction(insertAfterAc_);

    connect(insertAfterAc_, SIGNAL(triggered()),
            this, SLOT(slotInsertAfterCell()));

    deleteCellAc_ = new QAction(this);
    deleteCellAc_->setText(tr("Delete cell"));
    deleteCellAc_->setData("deleteCell");
    deleteCellAc_->setIcon(QPixmap(":/desktop/deleteCell.svg"));
    deleteCellAc_->setToolTip(tr("Remove cell"));

    auto* sep1 = new QAction(this);
    sep1->setSeparator(true);
    addAction(sep1);

    addAction(deleteCellAc_);

    auto* sep2 = new QAction(this);
    sep2->setSeparator(true);
    addAction(sep2);

    connect(deleteCellAc_, SIGNAL(triggered()),
            this, SLOT(slotDeleteCell()));

    reverseLeftAc_ = new QAction(this);
    reverseLeftAc_->setText(tr("Reverse left"));
    reverseLeftAc_->setData("reverseLeft");
    reverseLeftAc_->setIcon(QPixmap(":/desktop/reverse_left.svg"));
    reverseLeftAc_->setToolTip(tr("Reverse colours in section on the left"));

    addAction(reverseLeftAc_);

    connect(reverseLeftAc_, SIGNAL(triggered()),
            this, SLOT(slotReverseLeft()));

    reverseRightAc_ = new QAction(this);
    reverseRightAc_->setText(tr("Reverse right"));
    reverseRightAc_->setData("reverseLeft");
    reverseRightAc_->setIcon(QPixmap(":/desktop/reverse_right.svg"));
    reverseRightAc_->setToolTip(tr("Reverse colours in section on the right"));

    addAction(reverseRightAc_);

    connect(reverseRightAc_, SIGNAL(triggered()),
            this, SLOT(slotReverseRight()));

    reverseAllAc_ = new QAction(this);
    reverseAllAc_->setText(tr("Reverse all"));
    reverseAllAc_->setData("reverseLeft");
    reverseAllAc_->setIcon(QPixmap(":/desktop/reverse_all.svg"));
    reverseAllAc_->setToolTip(tr("Reverse all colours"));

    addAction(reverseAllAc_);

    connect(reverseAllAc_, SIGNAL(triggered()),
            this, SLOT(slotReverseAll()));

    auto* sep3 = new QAction(this);
    sep3->setSeparator(true);
    addAction(sep3);

    copyColAc_ = new QAction(this);
    copyColAc_->setText(tr("&Copy colour"));
    copyColAc_->setShortcut(QKeySequence(tr("Ctrl+C")));
    copyColAc_->setData("ignore");
    addAction(copyColAc_);

    connect(copyColAc_, SIGNAL(triggered()),
            this, SLOT(slotCopyColour()));

    pasteColAc_ = new QAction(this);
    pasteColAc_->setText(tr("&Paste colour"));
    pasteColAc_->setShortcut(QKeySequence(tr("Ctrl+V")));
    pasteColAc_->setData("ignore");
    addAction(pasteColAc_);

    connect(pasteColAc_, SIGNAL(triggered()),
            this, SLOT(slotPasteColour()));

    auto* sep4 = new QAction(this);
    sep4->setSeparator(true);
    addAction(sep4);

    infoAc_ = new QAction(this);
    infoAc_->setText(tr("Show full list of colours"));
    infoAc_->setData("info");
    infoAc_->setIcon(QPixmap(":/desktop/info_grey.svg"));
    infoAc_->setToolTip(tr("Show full list of colours"));

    addAction(infoAc_);

    connect(infoAc_, SIGNAL(triggered()),
            this, SLOT(slotShowInfo()));


    validTechs_ << "rgb"
                << "hcl"
                << "hsl";
    validWayMethods_ << "both"
                     << "ignore"
                     << "left"
                     << "right";
    validDirections_ << "clockwise"
                     << "anti_clockwise"
                     << "shortest"
                     << "longest";
}

void MvQColourGradWidget::checkActionState()
{
    if (cells_.count() <= 1 || currentCell_ == -1) {
        deleteCellAc_->setEnabled(false);
        copyColAc_->setEnabled(false);
        pasteColAc_->setEnabled(false);
    }
    else {
        deleteCellAc_->setEnabled(true);
        copyColAc_->setEnabled(true);
        pasteColAc_->setEnabled(true);
    }

    if (cells_.count() <= 1 || currentCell_ < 1)
        reverseLeftAc_->setEnabled(false);
    else
        reverseLeftAc_->setEnabled(true);

    if (cells_.count() <= 1 || currentCell_ == -1 || currentCell_ >= cells_.count() - 1)
        reverseRightAc_->setEnabled(false);
    else
        reverseRightAc_->setEnabled(true);

    if (cells_.count() <= 2)
        reverseAllAc_->setEnabled(false);
    else
        reverseAllAc_->setEnabled(true);
}

QList<QColor> MvQColourGradWidget::colours() const
{
    QList<QColor> lst;
    for (int i = 0; i < cells_.count(); i++) {
        lst << cells_[i].col;
    }
    return lst;
}

void MvQColourGradWidget::reset(QList<QColor> cols,
                                const std::string& wayMethod, const std::vector<std::string>& techs,
                                const std::string& direction, const std::vector<std::string>& steps)
{
    cells_.clear();
    foreach (QColor c, cols) {
        ColorCell cell(c);
        cells_ << cell;
    }

    bool techChanged = setTechs(techs, false);
    bool stepChanged = setSteps(steps, false);
    bool wayMethodChanged = setWayMethod(wayMethod, false);
    bool directionChanged = setDirection(direction, false);

    if (!techChanged && !stepChanged && !wayMethodChanged && !directionChanged) {
        if (currentCell_ == -1 || currentCell_ >= cells_.count()) {
            setCurrentCell(0);
        }
        else {
            createPixmap();
            update();
        }
    }
    else {
        createPixmap();
        if (currentCell_ == -1 && cells_.count() > 0) {
            setCurrentCell(0);
        }
        else
            update();
    }

    checkActionState();
}


void MvQColourGradWidget::resizeEvent(QResizeEvent* /*event*/)
{
    createPixmap();
    update();
}

void MvQColourGradWidget::changeEvent(QEvent* event)
{
    if (event->type() == QEvent::EnabledChange) {
        createPixmap();
        update();
    }
    else {
        QWidget::changeEvent(event);
    }
}

void MvQColourGradWidget::paintEvent(QPaintEvent* /*event*/)
{
    QPainter painter(this);

    if (!pix_.isNull())
        painter.drawPixmap(0, 0, pix_);

    renderCurrentCell(&painter);
}

void MvQColourGradWidget::mousePressEvent(QMouseEvent* event)
{
    setCurrentCell(event->pos());
    emit singleCellClicked();
}

void MvQColourGradWidget::setCurrentCell(QPoint pos)
{
    if (cells_.count() == 0) {
        if (currentCell_ != -1) {
            currentCell_ = -1;
            update();
            checkActionState();
        }
        return;
    }
    else if (cells_.count() == 1) {
        if (currentCell_ != 0) {
            setCurrentCell(0);
        }
        return;
    }
    else {
        Q_ASSERT(cells_.count() >= 2);

        for (int i = 0; i < cells_.count(); i++) {
            int left = (i > 0) ? ((cells_[i].xp - cells_[i - 1].xp) / 2) : cells_[i].xp + 1;
            int right = (i < cells_.count() - 1) ? ((cells_[i + 1].xp - cells_[i].xp) / 2) : width() - cells_[i].xp + 1;
            Q_ASSERT(left >= 0);
            Q_ASSERT(right >= 0);
            int delta = cells_[i].xp - pos.x();

            if (delta == 0 || (delta > 0 && delta < left) ||
                (delta < 0 && abs(delta) < right)) {
                setCurrentCell(i);
                return;
            }
        }
    }
}

void MvQColourGradWidget::setCurrentCell(int index)
{
    if (index >= 0 && index < cells_.count()) {
        currentCell_ = index;
        update();
        emit currentChanged(cells_[currentCell_].col);
    }

    checkActionState();
}

void MvQColourGradWidget::createPixmap()
{
    if (!isEnabled()) {
        pix_ = QPixmap(width(), height());
        pix_.fill(palette().color(QPalette::Disabled, QPalette::Button));
        return;
    }

    // check directions
    if (direction_.isEmpty())
        setDirection("clockwise", false);

    // we will populate it with the new set of colours
    allColours_.clear();

    int cellHeight = 16;
    int cellGap = 0;
    int usedWidth = width();
    int usedHeight = height();

    pix_ = QPixmap(width(), height());
    pix_.fill(Qt::gray);
    QPainter painter(&pix_);
    painter.setPen(Qt::black);

    int xStart = 0;
    int yStart = 0;

    if (edited_) {
        pix_.fill(Qt::gray);
        usedWidth -= 2 * editorGapX_;
        xStart = editorGapX_;
        yStart = editorGapY_;
        usedHeight -= editorGapY_;
    }

    int gradHeight = 0;
    gradHeight = usedHeight - cellHeight - 3;

    int cellW = 12;
    if (cells_.count() > 1) {
        cellGap = (usedWidth - cells_.count() * cellW) / (cells_.count() - 1);
    }

    int xp = xStart;
    int yp = yStart;

    if (cells_.count() == 1) {
        QRect r(xp, yp, usedWidth, gradHeight);
        painter.fillRect(r, cells_[0].col);

        // if there is only one cell it appears in the middle
        cells_[0].xp = width() / 2 - cellW / 2 + 1;
        cells_[0].yp = yStart + gradHeight + 2;
        cells_[0].w = cellW;
        cells_[0].h = cellHeight;
        cells_[0].render(&painter);
        return;
    }

    else if (cells_.count() == 0)
        pix_.fill(palette().color(QPalette::Window));


    // Cells
    xp = xStart;
    yp = yStart + gradHeight + 2;

    for (int i = 0; i < cells_.count(); i++) {
        if (i == 0) {
            cells_[i].xp = xp + cellW / 2 + 1;
        }
        else if (i == cells_.count() - 1) {
            xp = xStart + usedWidth - cellW / 2 - 2;
            cells_[i].xp = xp;
        }
        else {
            cells_[i].xp = xp;
        }

        cells_[i].yp = yp;
        cells_[i].w = cellW;
        cells_[i].h = cellHeight;
        cells_[i].render(&painter);

        // Gradient
        if (i > 0) {
            QColor c1 = cells_[i - 1].col;
            QColor c2 = cells_[i].col;

            int gx1 = cells_[i - 1].xp;
            int gx2 = cells_[i].xp;

            if (i == 1)
                gx1 -= cells_[i - 1].w / 2;

            if (i == cells_.count() - 1)
                gx2 += cells_[i].w / 2;

            QRect r(gx1, yStart, gx2 - gx1 + 1, gradHeight);

            if (i == cells_.count() - 1) {
                r.setRight(xStart + usedWidth);
            }

            if (!techs_.isEmpty()) {
                int step = 0;
                if (steps_.count() > i - 1) {
                    step = steps_[i - 1];
                }
                else if (!steps_.isEmpty()) {
                    step = steps_.last();
                }
                QString tech;
                if (techs_.count() > i - 1) {
                    tech = techs_[i - 1];
                }
                else if (!techs_.isEmpty()) {
                    tech = techs_.last();
                }

                if (isTechValid(tech) && step >= 2) {
                    renderBlock(&painter, c1, c2, tech, step, r, i, cells_.count());
                }
            }
        }

        xp += cellW + cellGap;
    }
}

// The first block is actBlock=1 !!!!
void MvQColourGradWidget::renderBlock(QPainter* painter, QColor c1, QColor c2,
                                      QString tech, int num, QRect r, int actBlock, int blockNum)
{
    if (tech.isEmpty() || num <= 0) {
        return;
    }

    if (num == 1) {
        painter->fillRect(r, c1);
        return;
    }

    if (num > 100)
        num = 100;

    if (wayMethod_ == "both") {
        float w = static_cast<float>(r.width()) / (static_cast<float>(num));
        float xp = r.x();
        for (int i = 0; i < num; i++) {
            if (i == num - 1)
                w = r.right() - xp;

            QRectF cr(xp, r.y(), w, r.height());
            MvQPalette::paintAlphaBg(painter, cr.toRect());

            float pos = 0;
            if (i == 0)
                pos = 0.;
            else if (i == num - 1)
                pos = 1.;
            else
                pos = static_cast<float>(i) / static_cast<float>(num - 1);

            paintCell(painter, cr, c1, c2, pos, tech);
            xp += w;
        }
    }

    else if (wayMethod_ == "ignore") {
        float w = static_cast<float>(r.width()) / (static_cast<float>(num));
        float xp = r.x();
        for (int i = 0; i < num; i++) {
            if (i == num - 1)
                w = r.right() - xp;

            QRectF cr(xp, r.y(), w, r.height());
            MvQPalette::paintAlphaBg(painter, cr.toRect());

            float pos = 0;
            if (blockNum > 1) {
                if (actBlock == 1) {
                    if (i == 0)
                        pos = 0.;
                    else
                        pos = static_cast<float>(i) / static_cast<float>(num);
                }
                else if (actBlock == blockNum - 1) {
                    if (i == num - 1)
                        pos = 1.;
                    else
                        pos = static_cast<float>(i + 1) / static_cast<float>(num);
                }
                else {
                    pos = static_cast<float>(i + 1) / static_cast<float>(num + 1);
                }
            }
            else {
                if (i == 0)
                    pos = 0.;
                else if (i == num - 1)
                    pos = 1.;
                else
                    pos = static_cast<float>(i) / static_cast<float>(num - 1);
            }

            paintCell(painter, cr, c1, c2, pos, tech);
            xp += w;
        }
    }

    else if (wayMethod_ == "left") {
        float w = static_cast<float>(r.width()) / (static_cast<float>(num));
        float xp = r.x();
        for (int i = 0; i < num; i++) {
            if (i == num - 1)
                w = r.right() - xp;

            QRectF cr(xp, r.y(), w, r.height());
            MvQPalette::paintAlphaBg(painter, cr.toRect());

            float pos = 0;
            if (blockNum > 1) {
                if (actBlock == 1) {
                    if (i == 0)
                        pos = 0.;
                    else
                        pos = static_cast<float>(i) / static_cast<float>(num - 1);
                }
                else {
                    pos = static_cast<float>(i + 1) / static_cast<float>(num);
                }
            }
            else {
                if (i == 0)
                    pos = 0.;
                else if (i == num - 1)
                    pos = 1.;
                else
                    pos = static_cast<float>(i) / static_cast<float>(num - 1);
            }

            paintCell(painter, cr, c1, c2, pos, tech);
            xp += w;
        }
    }

    else if (wayMethod_ == "right") {
        float w = static_cast<float>(r.width()) / (static_cast<float>(num));
        float xp = r.x();
        for (int i = 0; i < num; i++) {
            if (i == num - 1)
                w = r.right() - xp;

            QRectF cr(xp, r.y(), w, r.height());
            MvQPalette::paintAlphaBg(painter, cr.toRect());

            float pos = 0;
            if (blockNum > 1) {
                if (actBlock == blockNum - 1) {
                    if (i == 0)
                        pos = 0.;
                    else
                        pos = static_cast<float>(i) / static_cast<float>(num - 1);
                }
                else {
                    pos = static_cast<float>(i) / static_cast<float>(num);
                }
            }
            else {
                if (i == 0)
                    pos = 0.;
                else if (i == num - 1)
                    pos = 1.;
                else
                    pos = static_cast<float>(i) / static_cast<float>(num - 1);
            }

            paintCell(painter, cr, c1, c2, pos, tech);
            xp += w;
        }
    }
}

void MvQColourGradWidget::paintCell(QPainter* painter, QRectF cr, QColor c1, QColor c2, float pos, QString tech)
{
    QColor c;

    if (tech == "rgb")
        c = interpolateLinear(c1, c2, pos);
    else if (tech == "hcl") {
        if (direction_ == "clockwise")
            c = interpolateHclClockwise(c1, c2, pos);
        else if (direction_ == "anti_clockwise")
            c = interpolateHclAntiClockwise(c1, c2, pos);
        else if (direction_ == "shortest")
            c = interpolateHclShort(c1, c2, pos);
        else if (direction_ == "longest")
            c = interpolateHclLong(c1, c2, pos);
    }
    else if (tech == "hsl") {
        if (direction_ == "clockwise")
            c = interpolateHslClockwise(c1, c2, pos);
        else if (direction_ == "anti_clockwise")
            c = interpolateHslAntiClockwise(c1, c2, pos);
        else if (direction_ == "shortest")
            c = interpolateHslShort(c1, c2, pos);
        else if (direction_ == "longest")
            c = interpolateHslLong(c1, c2, pos);
    }

    if (c.isValid()) {
        painter->fillRect(cr, c);
        allColours_ << c;
    }
}


QColor MvQColourGradWidget::interpolateLinear(QColor c1, QColor c2, float p)
{
    float r = c1.redF() * (1 - p) + c2.redF() * p;
    float g = c1.greenF() * (1 - p) + c2.greenF() * p;
    float b = c1.blueF() * (1 - p) + c2.blueF() * p;

    float alpha1 = c1.alphaF();
    float alpha2 = c2.alphaF();
    float alpha = alpha1;

    if (c1.alpha() != c2.alpha())
        alpha = alpha1 * (1 - p) + alpha2 * p;

    return QColor::fromRgbF(r, g, b, alpha);
}

QColor MvQColourGradWidget::interpolateHslClockwise(QColor c1, QColor c2, float p)
{
    float h1 = c1.hslHueF();
    float h2 = c2.hslHueF();

    if (h1 < 0)
        h1 = 0.;
    if (h2 < 0)
        h2 = 0.;

    float h = 0.;
    if (h1 >= h2) {
        h = h1 * (1. - p) + h2 * p;
    }
    else {
        h = h1 - p * (h1 + 1. - h2);
        if (h < 0)
            h += 1.;
    }

    float s = c1.hslSaturationF() * (1 - p) + c2.hslSaturationF() * p;
    float l = c1.lightnessF() * (1 - p) + c2.lightnessF() * p;

    float alpha1 = c1.alphaF();
    float alpha2 = c2.alphaF();
    float alpha = alpha1;

    if (c1.alpha() != c2.alpha())
        alpha = alpha1 * (1 - p) + alpha2 * p;

#ifdef DEBUG_MV_MVQCOLOURGRADLINE__
    qDebug() << " clockwise -->" << h << s << l << alpha;
#endif
    return QColor::fromHslF(h, s, l, alpha);
}

QColor MvQColourGradWidget::interpolateHslAntiClockwise(QColor c1, QColor c2, float p)
{
    float h1 = c1.hslHueF();
    float h2 = c2.hslHueF();

    if (h1 < 0)
        h1 = 0.;
    if (h2 < 0)
        h2 = 0.;

    float h = 0.;
    if (h2 >= h1) {
        h = h1 * (1. - p) + h2 * p;
    }
    else {
        h = h1 + p * (1. - h1 + h2);
        if (h > 1.)
            h -= 1.;
    }

    float s = c1.hslSaturationF() * (1 - p) + c2.hslSaturationF() * p;
    float l = c1.lightnessF() * (1 - p) + c2.lightnessF() * p;

    float alpha1 = c1.alphaF();
    float alpha2 = c2.alphaF();
    float alpha = alpha1;

    if (c1.alpha() != c2.alpha())
        alpha = alpha1 * (1 - p) + alpha2 * p;
#ifdef DEBUG_MV_MVQCOLOURGRADLINE__
    qDebug() << " anti -->" << h << s << l << alpha;
#endif
    return QColor::fromHslF(h, s, l, alpha);
}

QColor MvQColourGradWidget::interpolateHslShort(QColor c1, QColor c2, float p)
{
    float h1 = c1.hslHueF();
    float h2 = c2.hslHueF();

#ifdef DEBUG_MV_MVQCOLOURGRADLINE__
    qDebug() << "interpolateHslShort";
    qDebug() << c1 << c2 << p;
    qDebug() << h1 << h2;
#endif

    if (h1 > h2)
        if (h1 - h2 <= 0.5)
            return interpolateHslClockwise(c1, c2, p);
        else
            return interpolateHslAntiClockwise(c1, c2, p);
    else {
        if (h2 - h1 <= 0.5)
            return interpolateHslAntiClockwise(c1, c2, p);
        else
            return interpolateHslClockwise(c1, c2, p);
    }

    return {};
}

QColor MvQColourGradWidget::interpolateHslLong(QColor c1, QColor c2, float p)
{
    float h1 = c1.hslHueF();
    float h2 = c2.hslHueF();

#ifdef DEBUG_MV_MVQCOLOURGRADLINE__
    qDebug() << "interpolateHslLong";
    qDebug() << c1 << c2 << p;
    qDebug() << h1 << h2;
#endif

    if (h1 < 0)
        h1 = 0.;
    if (h2 < 0)
        h2 = 0.;

    if (h1 > h2)
        if (h1 - h2 <= 0.5)
            return interpolateHslAntiClockwise(c1, c2, p);
        else
            return interpolateHslClockwise(c1, c2, p);
    else {
        if (h2 - h1 <= 0.5)
            return interpolateHslClockwise(c1, c2, p);
        else
            return interpolateHslAntiClockwise(c1, c2, p);
    }

    return {};
}

// HCL=Hue,Chroma,Luminence
// Warning: Hue in HCL is naot the same as in HSL

QColor MvQColourGradWidget::interpolateHclClockwise(QColor c1, QColor c2, float p)
{
    float hue1, chroma1, lumin1, alpha1 = c1.alphaF();
    float hue2, chroma2, lumin2, alpha2 = c2.alphaF();

    MvQPalette::toHclLabF(c1, hue1, chroma1, lumin1);
    MvQPalette::toHclLabF(c2, hue2, chroma2, lumin2);
#ifdef DEBUG_MV_MVQCOLOURGRADLINE__
    qDebug() << "HCL 1" << c2 << hue2 << chroma2 << lumin1 << alpha1;
    qDebug() << "HCL 2" << c2 << hue2 << chroma2 << lumin2 << alpha2;
#endif
    float h = 0.;
    if (hue1 >= hue2) {
        h = hue1 * (1. - p) + hue2 * p;
    }
    else {
        h = hue1 - p * (hue1 + 1. - hue2);
        if (h < 0)
            h += 1.;
    }

    float chroma = chroma1 * (1 - p) + chroma2 * p;
    float lumin = lumin1 * (1 - p) + lumin2 * p;
    float alpha = alpha1;

    if (c1.alpha() != c2.alpha())
        alpha = alpha1 * (1 - p) + alpha2 * p;

    QColor res = MvQPalette::fromHclLabF(h, chroma, lumin);
    res.setAlphaF(alpha);
#ifdef DEBUG_MV_MVQCOLOURGRADLINE__
    // qDebug() << "res=" << res;
#endif
    return res;
}

QColor MvQColourGradWidget::interpolateHclAntiClockwise(QColor c1, QColor c2, float p)
{
    float hue1, chroma1, lumin1, alpha1 = c1.alphaF();
    float hue2, chroma2, lumin2, alpha2 = c2.alphaF();

    MvQPalette::toHclLabF(c1, hue1, chroma1, lumin1);
    MvQPalette::toHclLabF(c2, hue2, chroma2, lumin2);

    if (hue1 < 0)
        hue1 = 0.;
    if (hue2 < 0)
        hue2 = 0.;

    float h = 0.;
    if (hue2 >= hue1) {
        h = hue1 * (1. - p) + hue2 * p;
    }
    else {
        h = hue1 + p * (1. - hue1 + hue2);
        if (h > 1.)
            h -= 1.;
    }

    float chroma = chroma1 * (1 - p) + chroma2 * p;
    float lumin = lumin1 * (1 - p) + lumin2 * p;
    float alpha = alpha1;

    if (c1.alpha() != c2.alpha())
        alpha = alpha1 * (1 - p) + alpha2 * p;

    QColor res = MvQPalette::fromHclLabF(h, chroma, lumin);
    res.setAlphaF(alpha);
    return res;
}

QColor MvQColourGradWidget::interpolateHclShort(QColor c1, QColor c2, float p)
{
    float hue1, chroma1, lumin1;
    float hue2, chroma2, lumin2;

    MvQPalette::toHclLabF(c1, hue1, chroma1, lumin1);
    MvQPalette::toHclLabF(c2, hue2, chroma2, lumin2);

    float h1 = hue1;
    float h2 = hue2;

    if (h1 > h2)
        if (h1 - h2 <= 0.5)
            return interpolateHclClockwise(c1, c2, p);
        else
            return interpolateHclAntiClockwise(c1, c2, p);
    else {
        if (h2 - h1 <= 0.5)
            return interpolateHclAntiClockwise(c1, c2, p);
        else
            return interpolateHclClockwise(c1, c2, p);
    }

    return QColor();
}

QColor MvQColourGradWidget::interpolateHclLong(QColor c1, QColor c2, float p)
{
    float hue1, chroma1, lumin1;
    float hue2, chroma2, lumin2;

    MvQPalette::toHclLabF(c1, hue1, chroma1, lumin1);
    MvQPalette::toHclLabF(c2, hue2, chroma2, lumin2);

    float h1 = hue1;
    float h2 = hue2;

    if (h1 < 0)
        h1 = 0.;
    if (h2 < 0)
        h2 = 0.;

    if (h1 > h2)
        if (h1 - h2 <= 0.5)
            return interpolateHclAntiClockwise(c1, c2, p);
        else
            return interpolateHclClockwise(c1, c2, p);
    else {
        if (h2 - h1 <= 0.5)
            return interpolateHclClockwise(c1, c2, p);
        else
            return interpolateHclAntiClockwise(c1, c2, p);
    }

    return {};
}

void MvQColourGradWidget::renderCurrentCell(QPainter* painter)
{
    if (edited_ && cells_.count() > 0 && currentCell_ >= 0 && currentCell_ < cells_.count()) {
        /*int hue=colours_[currentCell_].hue();
        if(hue > 30 && hue < 210)
            painter->setPen(blackPen_);
        else
            painter->setPen(whitePen_);*/

        cells_[currentCell_].renderSelection(painter);

        painter->setPen(blackPen_);
    }
}

QColor MvQColourGradWidget::currentColour() const
{
    if (currentCell_ >= 0 && currentCell_ < cells_.count())
        return cells_[currentCell_].col;

    return {};
}

void MvQColourGradWidget::setCurrentColour(QColor col)
{
    if (col.isValid() &&
        currentCell_ >= 0 && currentCell_ < cells_.count()) {
        cells_[currentCell_].col = col;
        createPixmap();
        update();
        emit changed();
    }
}

void MvQColourGradWidget::slotInsertBeforeCell()
{
    insertBeforeCell(currentCell_);
}

void MvQColourGradWidget::slotInsertAfterCell()
{
    insertAfterCell(currentCell_);
}

void MvQColourGradWidget::slotDeleteCell()
{
    deleteCell(currentCell_);
}

void MvQColourGradWidget::slotCopyColour()
{
    QColor col = currentColour();
    if (col.isValid()) {
        MvQ::toClipboard("\"" + QString::fromStdString(MvQPalette::toString(col)) + "\"");
    }
}

void MvQColourGradWidget::slotPasteColour()
{
    QString txt = MvQ::fromClipboard();
    if (!txt.isEmpty()) {
        QColor col = MvQPalette::magics(txt.toStdString());
        if (col.isValid() && currentCell_ != -1) {
            cells_[currentCell_].col = col;
            update();
            emit currentChanged(cells_[currentCell_].col);
        }
    }
}

void MvQColourGradWidget::deleteCell(int index)
{
    if (cells_.count() > 1 && index >= 0 && index < cells_.count()) {
        cells_.remove(index);

        createPixmap();
        if (index < cells_.count() - 1)
            setCurrentCell(index);
        else
            setCurrentCell(cells_.count() - 1);

        emit changed();

        checkActionState();
    }
}

void MvQColourGradWidget::insertBeforeCell(int index)
{
    if (index >= 0 && index < cells_.count()) {
        QColor col = cells_[index].col;
        cells_.insert(index, ColorCell(col));

        createPixmap();
        setCurrentCell(index);
        emit changed();
    }
}


void MvQColourGradWidget::insertAfterCell(int index)
{
    if (index >= 0 && index < cells_.count()) {
        QColor col = cells_[index].col;
        cells_.insert(index + 1, ColorCell(col));

        createPixmap();
        setCurrentCell(index + 1);
        emit changed();
    }
}

void MvQColourGradWidget::slotReverseLeft()
{
    reverseLeft(currentCell_);
}

void MvQColourGradWidget::slotReverseRight()
{
    reverseRight(currentCell_);
}

void MvQColourGradWidget::slotReverseAll()
{
    if (cells_.count() > 0) {
        int n = cells_.count();
        std::vector<QColor> vec;
        for (int i = 0; i < n; i++)
            vec.push_back(cells_[i].col);

        for (int i = 0; i < n; i++)
            cells_[i].col = vec[n - i - 1];

        createPixmap();
        emit changed();
    }
}

void MvQColourGradWidget::reverseLeft(int index)
{
    if (index > 0 && index < cells_.count()) {
        Q_ASSERT(index - 1 >= 0);
        QColor col1 = cells_[index - 1].col;
        QColor col2 = cells_[index].col;

        cells_[index - 1].col = col2;
        cells_[index].col = col1;

        createPixmap();
        setCurrentCell(index);
        emit changed();
    }
}

void MvQColourGradWidget::reverseRight(int index)
{
    if (index >= 0 && index < cells_.count() - 1) {
        Q_ASSERT(index + 1 < cells_.count());
        QColor col1 = cells_[index].col;
        QColor col2 = cells_[index + 1].col;

        cells_[index].col = col2;
        cells_[index + 1].col = col1;

        createPixmap();
        setCurrentCell(index);
        emit changed();
    }
}

void MvQColourGradWidget::slotShowInfo()
{
    QStringList lst;
    for (int i = 0; i < allColours_.count(); i++) {
        lst << "\'" + QString::fromStdString(MvQPalette::toString(allColours_[i])) + "\'";
    }
    QString str = "[\n " + lst.join(",\n ") + "\n]";

    TextDialog dialog(this);
    dialog.setTitle("List of colours in gradient");
    dialog.setText(str);
    dialog.exec();
}

void MvQColourGradWidget::setEdited(bool edited)
{
    if (edited_ != edited) {
        edited_ = edited;
        createPixmap();
        update();
    }
}

bool MvQColourGradWidget::setWayMethod(const std::string& val, bool redraw)
{
    bool changed = false;
    QString qsVal = QString::fromStdString(val).toLower();
    if (wayMethod_ != qsVal) {
        wayMethod_ = qsVal;
        changed = true;
    }

    if (changed && redraw) {
        createPixmap();
        update();
    }

    return changed;
}

bool MvQColourGradWidget::setDirection(const std::string& val, bool redraw)
{
    bool changed = false;
    QString qsVal = QString::fromStdString(val).toLower();
    if (direction_ != qsVal) {
        direction_ = qsVal;
        changed = true;
    }

    if (changed && redraw) {
        createPixmap();
        update();
    }

    return changed;
}

bool MvQColourGradWidget::setTechs(const std::vector<std::string>& vals, bool redraw)
{
    bool changed = false;
    int valsNum = static_cast<int>(vals.size());

    for (int i = 0; i < valsNum; i++) {
        QString v = QString::fromStdString(vals[i]).toLower();
        if (isTechValid(v)) {
            if (techs_.count() > i) {
                if (techs_[i] != v) {
                    techs_[i] = v;
                    changed = true;
                }
            }
            else {
                techs_ << v;
                changed = true;
            }
        }
        else {
            if (techs_.count() > i) {
                if (isTechValid(techs_[i])) {
                    techs_[i] = QString();
                    changed = true;
                }
            }
            else {
                techs_ << QString();
                changed = true;
            }
        }
    }

    if (techs_.count() > valsNum) {
        while (techs_.count() > valsNum)
            techs_.removeLast();
        changed = true;
    }
#ifdef DEBUG_MV_MVQCOLOURGRADLINE__
    qDebug() << "techs" << techs_;
#endif
    if (changed && redraw) {
        createPixmap();
        update();
    }

    return changed;
}

bool MvQColourGradWidget::setSteps(const std::vector<std::string>& vals, bool redraw)
{
    bool changed = false;
    int valsNum = static_cast<int>(vals.size());

    for (int i = 0; i < valsNum; i++) {
        int v = QString::fromStdString(vals[i]).toInt();
        if (v > 100)
            v = 100;

        if (v >= 2) {
            if (steps_.count() > i) {
                if (steps_[i] != v) {
                    steps_[i] = v;
                    changed = true;
                }
            }
            else {
                steps_ << v;
                changed = true;
            }
        }
        else {
            if (steps_.count() > i) {
                if (steps_[i] >= 2) {
                    steps_[i] = 0;
                    changed = true;
                }
            }
            else {
                steps_ << 0;
                changed = true;
            }
        }
    }

    if (steps_.count() > valsNum) {
        steps_.resize(valsNum);
        changed = true;
    }
#ifdef DEBUG_MV_MVQCOLOURGRADLINE__
    qDebug() << "steps" << steps_;
#endif
    if (changed && redraw) {
        createPixmap();
        update();
    }

    return changed;
}

bool MvQColourGradWidget::isTechValid(QString v)
{
    return validTechs_.contains(v);
}

//==============================================
//
// MvQColourGradLine
//
//==============================================

MvQColourGradLine::MvQColourGradLine(RequestPanel& owner, const MvIconParameter& param) :
    MvQRequestPanelLine(owner, param),
    waypointParam_("CONTOUR_GRADIENTS_WAYPOINT_METHOD"),
    techParam_("CONTOUR_GRADIENTS_TECHNIQUE"),
    directionParam_("CONTOUR_GRADIENTS_TECHNIQUE_DIRECTION"),
    stepParam_("CONTOUR_GRADIENTS_STEP_LIST")
{
    widget_ = new MvQColourGradWidget(parentWidget_);

    QFont font;
    QFontMetrics fm(font);
    int h = fm.height() + 4 + 16;
    widget_->setFixedHeight(h);

    owner_.addWidget(widget_, row_, WidgetColumn);

    connect(widget_, SIGNAL(currentChanged(QColor)),
            this, SLOT(slotCurrentChanged(QColor)));

    connect(widget_, SIGNAL(changed()),
            this, SLOT(slotListChanged()));

    connect(widget_, SIGNAL(singleCellClicked()),
            this, SLOT(slotSingleCellClicked()));
}

void MvQColourGradLine::buildHelper()
{
    MvQRequestPanelLine::buildHelper();

    // Customisation
    if (helper_)
        helper_->setExternalActions(widget_->actions());

    /*connect(helper_,SIGNAL(insertBefore()),
                widget_,SLOT(slotInsertBeforeCell()));

    connect(helper_,SIGNAL(insertAfter()),
                widget_,SLOT(slotInsertAfterCell()));

    connect(helper_,SIGNAL(deleteCell()),
                widget_,SLOT(slotDeleteCell()));*/
}

void MvQColourGradLine::slotHelperOpened(bool b)
{
    widget_->setEdited(b);
    updateHelper();
}

void MvQColourGradLine::refresh(const std::vector<std::string>& values)
{
    if (values.size() > 0) {
        QList<QColor> colours;

        for (const auto& value : values) {
            colours << MvQPalette::magics(value);
        }

        Request r = owner_.request();
        std::vector<std::string> ways = r.get(waypointParam_.c_str());
        std::string wayMethod;
        if (ways.size() > 0)
            wayMethod = ways[0];

        std::vector<std::string> steps = r.get(stepParam_.c_str());

        std::vector<std::string> dirs = r.get(directionParam_.c_str());
        std::string direction;
        if (dirs.size() > 0)
            direction = dirs[0];

        std::vector<std::string> tech = r.get(techParam_.c_str());

        widget_->reset(colours, wayMethod, tech, direction, steps);
    }

    // changed_ = false;
}

void MvQColourGradLine::changed(const char* param, const std::vector<std::string>& vals)
{
    if (param && strcmp(param, stepParam_.c_str()) == 0) {
        widget_->setSteps(vals, true);
    }
}

void MvQColourGradLine::changed(const char* param, const std::string& val)
{
    if (param && strcmp(param, waypointParam_.c_str()) == 0) {
        widget_->setWayMethod(val, true);
    }
    else if (param && strcmp(param, directionParam_.c_str()) == 0) {
        widget_->setDirection(val, true);
    }
    else if (param && strcmp(param, techParam_.c_str()) == 0) {
        // Set directions. In "rgb" mode there is no direction so
        // we need to define it when we switch from "rgb" into "hsl" or "hcl".
        Request r = owner_.request();
        std::vector<std::string> dirs = r.get(directionParam_.c_str());
        std::string direction;
        if (dirs.size() > 0)
            direction = dirs[0];
        widget_->setDirection(direction, false);

        std::vector<std::string> vals;
        vals.push_back(val);
        widget_->setTechs(vals, true);
    }
}

void MvQColourGradLine::slotListChanged()
{
    std::vector<std::string> vals;
    foreach (QColor col, widget_->colours()) {
        vals.push_back(MvQPalette::toString(col));
    }

    owner_.set(param_.name(), vals);
}

void MvQColourGradLine::slotSingleCellClicked()
{
    if (helpTb_) {
        if (!helpTb_->isChecked())
            helpTb_->toggle();
    }
}

void MvQColourGradLine::updateHelper()
{
    if (!helper_)
        return;

    std::vector<std::string> vals;
    vals.push_back(MvQPalette::toString(widget_->currentColour()));
    helper_->refresh(vals);
}

void MvQColourGradLine::slotCurrentChanged(QColor)
{
    updateHelper();
}

void MvQColourGradLine::slotHelperEdited(const std::vector<std::string>& vals)
{
    if (vals.size() == 0)
        return;

    widget_->setCurrentColour(MvQPalette::magics(vals[0]));
}

static LineMaker<MvQColourGradLine> maker1("colourgrad");
