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

 Copyright 2019 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 "MvQColourWidget.h"

#include <QAction>
#include <QComboBox>
#include <QDebug>
#include <QFrame>
#include <QGridLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QMenu>
#include <QMouseEvent>
#include <QPainter>
#include <QPushButton>
#include <QRegExpValidator>
#include <QSlider>
#include <QSpinBox>
#include <QTabWidget>
#include <QToolButton>
#include <QVBoxLayout>

#include <math.h>
#include <vector>

#include "MvMiscelaneous.h"
#include "MvQMethods.h"
#include "MvQPalette.h"

static const float toRadFactor = 3.14159265359 / 180.;
static const float toDegFactor = 180. / 3.14159265359;

//==============================
//
// MvQColourGrid
//
//===============================

MvQColourGrid::MvQColourGrid(int minSize, QWidget* parent) :
    QWidget(parent),
    minSize_(minSize),
    cellsPerRow_(8)
{
    MvQPalette::scan(*this);

    sort();

    setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum));
    setMinimumSize(QSize(minSize_, minSize_));

    setMouseTracking(true);

    setContextMenuPolicy(Qt::CustomContextMenu);

    connect(this, SIGNAL(customContextMenuRequested(const QPoint&)),
            this, SLOT(slotContextMenu(const QPoint&)));
}

//Soring the colours
void MvQColourGrid::sort()
{
    QMap<float, MvQColourItem> sm;
    foreach (MvQColourItem item, items_) {
        float sv = item.col_.hueF();
        //float sv=item.col_.lightnessF() * 5. + item.col_.hslSaturationF() * 2 + item.col_.hueF();
        //float sv=sqrt(0.299 * pow(item.col_.redF(),2.0) + 0.587 * pow(item.col_.greenF(),2.0) + 0.114 * pow(item.col_.blueF(),2.));
        sm.insertMulti(sv, item);
    }

    items_.clear();
    QMap<float, MvQColourItem>::const_iterator it = sm.constBegin();
    while (it != sm.constEnd()) {
        items_ << it.value();
        ++it;
    }
}

void MvQColourGrid::next(const string& name, QColor col, bool pseudo)
{
    if (!pseudo)
        items_ << MvQColourItem(col, QString::fromStdString(name));
}

void MvQColourGrid::resizeEvent(QResizeEvent* /*event*/)
{
    int w     = width();
    int h     = height();
    gridSize_ = (w < h) ? w : h;
    cellSize_ = gridSize_ / cellsPerRow_;

    createGrid();
}

void MvQColourGrid::paintEvent(QPaintEvent* /*event*/)
{
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);

    //Render ring pixmap
    painter.drawPixmap(0, 0, pix_);
}

void MvQColourGrid::createGrid()
{
    QPalette pal = palette();

    pix_ = QPixmap(gridSize_, gridSize_);

    QPainter painter(&pix_);
    //painter.setRenderHint(QPainter::Antialiasing);
    //pal.window().color()

    pix_.fill(pal.window().color());

    for (int i = 0; i < items_.count(); i++) {
        int row = i / cellsPerRow_;
        int col = i - row * cellsPerRow_;
        painter.fillRect(col * cellSize_, row * cellSize_, cellSize_, cellSize_, items_[i].col_);
    }

    painter.setPen(pal.window().color());
    for (int i = 1; i < cellsPerRow_; i++) {
        painter.drawLine(i * cellSize_, 0, i * cellSize_, gridSize_);
        painter.drawLine(0, i * cellSize_, gridSize_, i * cellSize_);
    }
}

void MvQColourGrid::mousePressEvent(QMouseEvent* event)
{
    int idx = index(event->pos());
    if (idx >= 0 && idx < items_.count()) {
        if (event->button() == Qt::LeftButton)
            emit selected(items_[idx].col_);
    }
}

void MvQColourGrid::mouseMoveEvent(QMouseEvent* event)
{
    if (event->buttons() != Qt::NoButton)
        return;

    int idx = index(event->pos());
    if (idx >= 0 && idx < items_.count()) {
        setToolTip(items_[idx].name_);
    }
}

int MvQColourGrid::index(QPoint pos)
{
    int row = pos.y() / cellSize_;
    int col = pos.x() / cellSize_;

    return row * cellsPerRow_ + col;
}

void MvQColourGrid::slotContextMenu(const QPoint& pos)
{
    int idx = index(pos);
    if (idx < 0 || idx >= items_.count())
        return;

    QList<QAction*> lst;
    QMenu* menu = new QMenu(this);
    QAction* ac;

    ac = new QAction(this);
    ac->setText(tr("&Copy colour"));
    ac->setShortcut(QKeySequence(tr("Ctrl+C")));
    ac->setIcon(QPixmap(":/desktop/editcopy.svg"));
    ac->setData("copy");
    menu->addAction(ac);

    //Show the context menu and check selected action
    ac = menu->exec(mapToGlobal(pos));
    if (ac && !ac->isSeparator()) {
        if (ac->data().toString() == "copy") {
            MvQ::toClipboard(QString::fromStdString(MvQPalette::toString(items_[idx].col_)));
        }
    }
}

//==============================
//
// MvQColourWheel
//
//===============================

MvQColourWheel::MvQColourWheel(int minSize, QWidget* parent) :
    QWidget(parent),
    minSize_(minSize),
    ringWidth_(14),
    outerRadius_(minSize),
    innerRadius_(minSize-20),
    margin_(4),
    hue_(0),
    sat_(128),
    lgt_(128),
    lgtSatSelectorRadius_(4),
    dragType_(NoDrag)
{
    huePenBlack_ = QPen(Qt::black, 2., Qt::SolidLine);
    huePenWhite_ = QPen(Qt::white, 2., Qt::SolidLine);

    setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum));
    //setMinimumSize(QSize(150 + 2 * margin_, 150));
    setMinimumSize(QSize(minSize_ + 2 * margin_, minSize_ + margin_));

    setMouseTracking(true);
}

void MvQColourWheel::resizeEvent(QResizeEvent* /*event*/)
{
    int w        = width() - 2 * margin_;
    int h        = height();
    outerRadius_ = (w < h) ? w : h;
    outerRadius_ /= 2;
    innerRadius_ = outerRadius_ - ringWidth_;
    centre_      = QPoint(margin_ + w / 2, h / 2);

    //Create a hue ring pixmap
    createRing();

    //Create the lgt-sat triangle pixmap
    createTriangle();
}

void MvQColourWheel::paintEvent(QPaintEvent* /*event*/)
{
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);

    //Render ring pixmap
    painter.drawPixmap(centre_.x() - outerRadius_, centre_.y() - outerRadius_, ringPix_);

    //Render the hue selector onto the ring
    renderHueSelector(&painter);

    //Render lgt-sat triangle
    painter.drawPixmap(centre_.x() - innerRadius_, centre_.y() - innerRadius_, triPix_);

    //Render the lgt-sat selector onto the triangle
    renderLgtSatSelector(&painter);
}

//Render the ring into a pixmap
void MvQColourWheel::createRing()
{
    QPalette pal = palette();

    ringPix_ = QPixmap(2 * outerRadius_, 2 * outerRadius_);

    QPainter painter(&ringPix_);
    painter.setRenderHint(QPainter::Antialiasing);

    ringPix_.fill(pal.window().color());

    QConicalGradient cg(0, 0, 0);
    cg.setColorAt(0.0, Qt::red);
    cg.setColorAt(60.0 / 360.0, Qt::yellow);
    cg.setColorAt(120.0 / 360.0, Qt::green);
    cg.setColorAt(180.0 / 360.0, Qt::cyan);
    cg.setColorAt(240.0 / 360.0, Qt::blue);
    cg.setColorAt(300.0 / 360.0, Qt::magenta);
    cg.setColorAt(1.0, Qt::red);

    painter.translate(outerRadius_, outerRadius_);

    //Coloured circle
    QBrush brush(cg);
    painter.setPen(Qt::NoPen);
    painter.setBrush(brush);
    painter.drawEllipse(QPoint(0, 0), outerRadius_, outerRadius_);

    //Inner clipping circle
    painter.setBrush(pal.window());
    painter.drawEllipse(QPoint(0, 0), innerRadius_, innerRadius_);
}

void MvQColourWheel::createTriangle()
{
    int nx        = 256;
    int ny        = 256;
    float tFactor = sqrt(3) / 2.;

    QImage img(nx, ny, QImage::Format_ARGB32_Premultiplied);

    QColor col;
    for (int j = 0; j < ny; j++) {
        int chroma = 255 - j;
        for (int i = 0; i < nx; i++) {
            int lgt = i;
            if (abs(nx / 2 - i) < j / 2) {
                int sat = saturation(chroma, lgt);
                col     = QColor::fromHsl(hue_, sat, lgt);
                img.setPixel(i, j, col.rgb());
            }
            else {
                img.setPixel(i, j, qRgba(0, 0, 0, 0));
            }
        }
    }

    float h = innerRadius_ * 1.5;
    float w = h / tFactor;
    img     = img.scaled(w, h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);

    triPix_ = QPixmap(innerRadius_ * 2, innerRadius_ * 2);
    triPix_.fill(Qt::transparent);

    QPainter painter(&triPix_);
    painter.setRenderHint(QPainter::Antialiasing);

    painter.translate(innerRadius_, innerRadius_);
    painter.rotate(90 - hue_);
    painter.translate(-w / 2, -(innerRadius_));
    painter.drawImage(0, 0, img);
}

void MvQColourWheel::renderHueSelector(QPainter* painter)
{
    float alpha = -hue_ * toRadFactor;
    float x     = innerRadius_ * cos(alpha);
    float y     = innerRadius_ * sin(alpha);
    float x1    = outerRadius_ * cos(alpha);
    float y1    = outerRadius_ * sin(alpha);

    painter->translate(centre_.x(), centre_.y());

    if (hue_ > 20 && hue_ < 200)
        painter->setPen(huePenBlack_);
    else
        painter->setPen(huePenWhite_);
    painter->drawLine(x, y, x1, y1);
    painter->translate(-centre_.x(), -centre_.y());
}

void MvQColourWheel::renderLgtSatSelector(QPainter* painter)
{
    QPointF pos;
    lgtSatToPos(pos);
    lgtSatPos_ = pos.toPoint();

    if (lgt_ > 110)
        painter->setPen(huePenBlack_);
    else
        painter->setPen(huePenWhite_);

    painter->drawEllipse(centre_ + pos, lgtSatSelectorRadius_, lgtSatSelectorRadius_);
}

void MvQColourWheel::mousePressEvent(QMouseEvent* event)
{
    QPoint delta = event->pos() - centre_ - lgtSatPos_;

    if (delta.x() * delta.x() + delta.y() * delta.y() <= lgtSatSelectorRadius_ * lgtSatSelectorRadius_) {
        lgtSatOffset_ = delta;
        dragType_     = TriangleDrag;
    }
    else {
        lgtSatOffset_ = QPoint();
        dragType_     = checkPoint(event->pos());
    }
}

void MvQColourWheel::mouseMoveEvent(QMouseEvent* event)
{
    if (dragType_ == TriangleDrag) {
        dragTriangleSelector(event->pos());
    }
    else if (dragType_ == RingDrag) {
        dragRingSelector(event->pos());
    }
}

void MvQColourWheel::mouseReleaseEvent(QMouseEvent* /*event*/)
{
    //Drag happened
    bool dragHappened = (dragType_ != NoDrag);

    dragType_     = NoDrag;
    lgtSatOffset_ = QPoint(0, 0);
    dragPos_      = QPoint();

    if (dragHappened)
        emit dragFinished();
}


void MvQColourWheel::dragRingSelector(QPoint pos)
{
    if (dragType_ != RingDrag)
        return;

    QPoint dp = pos - centre_;
    int hue   = atan2(dp.x(), dp.y()) * toDegFactor - 90.;
    if (hue < 0)
        hue += 360;

    setHue(hue);
}

void MvQColourWheel::dragTriangleSelector(QPoint pos)
{
    if (dragType_ != TriangleDrag)
        return;

    QPoint dp = pos - lgtSatOffset_ - centre_;

    int lgt, sat;
    if (projectPosToLgtSat(dp, lgt, sat)) {
        setLgtSat(lgt, sat);
    }
}

MvQColourWheel::DragType MvQColourWheel::checkPoint(QPoint pos)
{
    //int fullRad=(width()-2*margin_)/2;
    int rad           = width() / 2 - margin_ - ringWidth_;
    int dx            = pos.x() - width() / 2;
    int dy            = pos.y() - height() / 2;
    int d             = sqrt(dx * dx + dy * dy);
    int ringTolerance = 10;

    if (d > rad && d < rad + ringWidth_ + ringTolerance && dragType_ != TriangleDrag) {
        int hue = atan2(dx, dy) * 180. / 3.14 - 90;
        if (hue < 0)
            hue += 360;

        setHue(hue);
        return RingDrag;
    }

    else if (d <= rad && dragType_ != RingDrag) {
        QPointF pp(dx, dy);
        int lgt, sat;
        if (posToLgtSat(pp, lgt, sat)) {
            setLgtSat(lgt, sat);
            return TriangleDrag;
        }
        else if (d >= rad - ringTolerance && d < rad + ringWidth_ + ringTolerance) {
            int hue = atan2(dx, dy) * 180. / 3.14 - 90;
            if (hue < 0)
                hue += 360;

            setHue(hue);
            return RingDrag;
        }
    }

    return NoDrag;
}

bool MvQColourWheel::lgtSatToPos(QPointF& pos)
{
    int chromaVal = chroma(sat_, lgt_);

    float yt = innerRadius_ - (255. - chromaVal) * (1.5 * static_cast<float>(innerRadius_)) / 255.;
    float xt = static_cast<float>(innerRadius_) * sqrt(3.) * (lgt_ - 128) / 255.;

    float alpha = (hue_ - 90) * toRadFactor;
    float x     = cos(alpha) * xt - sin(alpha) * yt;
    float y     = sin(alpha) * xt + cos(alpha) * yt;

    pos = QPointF(x, -y);

    //qDebug() << "lgtSat" << sat_ << lgt_ << pos;

    return true;
}

bool MvQColourWheel::posToLgtSat(QPointF pos, int& lgt, int& sat)
{
    //Transform coordinate system
    float alpha = -(hue_ - 90) * toRadFactor;
    float xt    = cos(alpha) * pos.x() + sin(alpha) * pos.y();
    float yt    = sin(alpha) * pos.x() - cos(alpha) * pos.y();

    if (yt < innerRadius_ && yt > -innerRadius_ / 2. &&
        fabs(xt) <= (innerRadius_ - yt) / 2) {
        int chroma = 255. - 255. * (innerRadius_ - yt) / (1.5 * static_cast<float>(innerRadius_));
        lgt        = 128. + 255. * xt / (static_cast<float>(innerRadius_) * sqrt(3.));
        sat        = saturation(chroma, lgt);

        return true;
    }

    return false;
}

bool MvQColourWheel::projectPosToLgtSat(QPointF pos, int& lgt, int& sat)
{
    //Transform coordinate system
    float alpha = -(hue_ - 90) * toRadFactor;
    float xt    = cos(alpha) * pos.x() + sin(alpha) * pos.y();
    float yt    = sin(alpha) * pos.x() - cos(alpha) * pos.y();

    if (yt < innerRadius_ && yt > -innerRadius_ / 2. &&
        fabs(xt) <= (innerRadius_ - yt) / 2) {
        int chroma = 255. - 255. * (innerRadius_ - yt) / (1.5 * static_cast<float>(innerRadius_));
        lgt        = 128. + 255. * xt / (static_cast<float>(innerRadius_) * sqrt(3.));
        sat        = saturation(chroma, lgt);

        return true;
    }
    else if (yt < innerRadius_ && yt > -innerRadius_ / 2.) {
        int chroma = 255. - 255. * (innerRadius_ - yt) / (1.5 * static_cast<float>(innerRadius_));

        lgt = 128. + 255. * ((xt > 0) ? 1 : -1) * ((innerRadius_ - yt) / 2) / (static_cast<float>(innerRadius_) * sqrt(3.));

        sat = saturation(chroma, lgt);
        return true;
    }
    else if (fabs(xt) <= (innerRadius_ - yt) / 2) {
        int chromaVal = chroma(sat_, lgt_);
        lgt           = 128. + 255. * xt / (static_cast<float>(innerRadius_) * sqrt(3.));
        sat           = saturation(chromaVal, lgt);
        return true;
    }

    return false;
}


void MvQColourWheel::setHue(int hue)
{
    hue_ = hue;

    createTriangle();

    update();

    emit hslSelected(hue_, sat_, lgt_);
}

void MvQColourWheel::setLgtSat(int lgt, int sat)
{
    lgt_ = lgt;
    sat_ = sat;

    update();

    emit hslSelected(hue_, sat_, lgt_);
}

void MvQColourWheel::slotSetColour(int hue, int sat, int lgt)
{
    hue_ = hue;
    lgt_ = lgt;
    sat_ = sat;

    createTriangle();

    update();
}

void MvQColourWheel::initColour(QColor col)
{
    int hue = col.hslHue();
    if (hue < 0)
        hue = 0;
    int lgt = col.lightness();
    int sat = col.hslSaturation();

    slotSetColour(hue, sat, lgt);
    //The problem is that several RGB values can be mapped to the same
    //HSL value. So what we emit here is the original colour!
    //emit hslSelected(hue_,sat_,lgt_);
    emit colourSelected(col);
}

int MvQColourWheel::saturation(int chroma, int lgt)
{
    float chromaNorm = static_cast<float>(chroma) / 255.;
    float lgtNorm    = static_cast<float>(lgt) / 255.;

    return (chroma == 0) ? 0 : 255. * chromaNorm / (1 - fabs(2. * lgtNorm - 1));
}

int MvQColourWheel::chroma(int sat, int lgt)
{
    float satNorm = static_cast<float>(sat) / 255.;
    float lgtNorm = static_cast<float>(lgt) / 255.;

    return (sat == 0) ? 0 : 255. * satNorm * (1 - fabs(2. * lgtNorm - 1));
}

//==============================
//
// MvQColourSelectionWidget
//
//===============================

MvQColourSelectionWidget::MvQColourSelectionWidget(bool tightMode, QWidget* parent) :
    QWidget(parent),
    tightMode_(tightMode),
    ignoreRgbSpin_(false),
    ignoreHslSpin_(false),
    ignoreAlphaSpin_(false),
    ignoreAlphaSlider_(false),
    ignoreHtmlEdit_(false),
    ignoreMacroEdit_(false)
{
    QFont sysFont;
    QFontMetrics sysFm(sysFont);

    QLabel* label;

    QHBoxLayout* hb = new QHBoxLayout(this);
    hb->setContentsMargins(2,2,2,2);
    hb->setSpacing(4);

    //--------------
    // Left
    //--------------

    tab_ = new QTabWidget(this);
    hb->addWidget(tab_);

    wheel_ = new MvQColourWheel(128, this);
    tab_->addTab(wheel_, tr("Wheel"));

    grid_ = new MvQColourGrid(128, this);
    tab_->addTab(grid_, tr("Grid"));

    //----------------
    // Right
    //----------------

    QVBoxLayout* rightVb = new QVBoxLayout();
    rightVb->setSpacing(2);
    hb->addSpacing(sysFm.width("H"));
    hb->addLayout(rightVb);

    //Col def hb
    QHBoxLayout* colDefHb = new QHBoxLayout();
    colDefHb->setSpacing(4);
    rightVb->addLayout(colDefHb);
    rightVb->addSpacing(sysFm.width("H"));

    //RGB
    QGridLayout* rgbGrid = new QGridLayout();
    colDefHb->addLayout(rgbGrid);

    QStringList rgbNames;
    rgbNames << tr("Red:") << tr("Green:") << tr("Blue:");
    for (int i = 0; i < 3; i++) {
        label = new QLabel(rgbNames[i], this);
        rgbGrid->addWidget(label, i, 0);

        QSpinBox* sp = new QSpinBox(this);
        sp->setRange(0, 255);
        rgbGrid->addWidget(sp, i, 1);
        rgbSpin_ << sp;
    }
    rgbGrid->setColumnStretch(1, 1);

    colDefHb->addSpacing(sysFm.width("HHHHH"));
    //colDefHb->addStretch(0.2);

    //HSL
    QGridLayout* hslGrid = new QGridLayout();
    colDefHb->addLayout(hslGrid);

    QStringList hslNames;
    hslNames << tr("Hue:") << tr("Saturation:") << tr("Lightness:");
    for (int i = 0; i < 3; i++) {
        //QHBoxLayout *colHb=new QHBoxLayout(this);
        //hslgrid->addLayout(colHb,i,0);

        label = new QLabel(hslNames[i], this);
        hslGrid->addWidget(label, i, 0);

        QSpinBox* sp = new QSpinBox(this);
        sp->setRange(0, (i == 0) ? 359 : 255);
        hslGrid->addWidget(sp, i, 1);
        hslSpin_ << sp;
    }
    hslGrid->setColumnStretch(1, 1);

    //Separator
    QFrame* sep1 = new QFrame(this);
    sep1->setFrameShape(QFrame::HLine);
    sep1->setFrameShadow(QFrame::Sunken);
    rightVb->addWidget(sep1);

    //Alpha
    QHBoxLayout* alphaHb = new QHBoxLayout();
    rightVb->addLayout(alphaHb);

    label = new QLabel("Opacity:", this);

    alphaSlider_ = new QSlider(this);
    alphaSlider_->setOrientation(Qt::Horizontal);
    alphaSlider_->setMinimum(0);
    alphaSlider_->setMaximum(255);

    setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum));
    //setMinimumSize(QSize(minSize_, minSize_));

    alphaSpin_ = new QSpinBox(this);
    alphaSpin_->setRange(0, 255);
    alphaSpin_->setValue(255);

    alphaHb->addWidget(label);
    alphaHb->addWidget(alphaSlider_);
    alphaHb->addWidget(alphaSpin_);

    //Separator
    QFrame* sep2 = new QFrame(this);
    sep2->setFrameShape(QFrame::HLine);
    sep2->setFrameShadow(QFrame::Sunken);
    rightVb->addWidget(sep2);

    //Html
    QHBoxLayout* selHb = new QHBoxLayout();
    rightVb->addLayout(selHb);

    label     = new QLabel(tr("HTML:"), this);
    htmlEdit_ = new QLineEdit(this);
    selHb->addWidget(label);
    selHb->addWidget(htmlEdit_);

    //HTML colour validator
    //QRegExp rx("#?[A-f|0-9]{6}");
    //QRegExpValidator* val = new QRegExpValidator(rx, this);
    //htmlEdit_->setValidator(val);
    //selHb->addStretch(1);

    // set highlighter on HTML colour - it is a bit tricky
    // because there is no highlighter support for QLineEdit
    QList<QTextLayout::FormatRange> formats;

    QTextCharFormat f;
    f.setFontWeight(QFont::Bold);
    f.setForeground(QColor(100,100,100));
    QTextLayout::FormatRange fr_hash;
    fr_hash.start = 0;
    fr_hash.length = 1;
    fr_hash.format = f;

    f.setForeground(QColor(Qt::red).darker(120));
    QTextLayout::FormatRange fr_red;
    fr_red.start = 1;
    fr_red.length = 2;
    fr_red.format = f;

    f.setForeground(QColor(Qt::green).darker(170));
    QTextLayout::FormatRange fr_green;
    fr_green.start = 3;
    fr_green.length = 2;
    fr_green.format = f;

    f.setForeground(QColor(Qt::blue).darker(110));
    QTextLayout::FormatRange fr_blue;
    fr_blue.start = 5;
    fr_blue.length = 2;
    fr_blue.format = f;

    formats.append(fr_hash);
    formats.append(fr_red);
    formats.append(fr_green);
    formats.append(fr_blue);

    MvQ::setLineEditTextFormat(htmlEdit_, formats);

    hb->addStretch(1);

    //Metview macro colour
    QHBoxLayout* macroHb = new QHBoxLayout();
    rightVb->addLayout(macroHb);

    label      = new QLabel(tr("Macro:"), this);
    macroEdit_ = new QLineEdit(this);
    macroHb->addWidget(label);
    macroHb->addWidget(macroEdit_);

    //Macro colour validator
    //QRegExp rxMacro("\\'[RGB|rgb]\\(\\d*\\.?\\d*,\\d*\\.?\\d*,\\d*\\.?\\d*\\)\\'");
    //rxMacro.setCaseSensitivity(Qt::CaseInsensitive);
    //QRegExpValidator* valMacro = new QRegExpValidator(rxMacro, this);
    //macroEdit_->setValidator(valMacro);

    //----------------
    //Signals and slots
    //----------------

    //Wheel
    connect(wheel_, SIGNAL(hslSelected(int, int, int)),
            this, SLOT(slotWheelChanged(int, int, int)));


    connect(wheel_, SIGNAL(colourSelected(QColor)),
            this, SLOT(slotWheelChanged(QColor)));

    for (int i = 0; i < 3; i++) {
        connect(hslSpin_[i], SIGNAL(valueChanged(int)),
                this, SLOT(slotHslChanged(int)));
    }

    for (int i = 0; i < 3; i++) {
        connect(rgbSpin_[i], SIGNAL(valueChanged(int)),
                this, SLOT(slotRgbChanged(int)));
    }

    connect(alphaSpin_, SIGNAL(valueChanged(int)),
            this, SLOT(slotAlphaChanged(int)));

    connect(alphaSlider_, SIGNAL(valueChanged(int)),
            this, SLOT(slotAlphaSliderChanged(int)));

    connect(htmlEdit_, SIGNAL(textChanged(QString)),
            this, SLOT(slotHtmlChanged(QString)));

    connect(macroEdit_, SIGNAL(textChanged(QString)),
            this, SLOT(slotMacroChanged(QString)));

    connect(this, SIGNAL(colourChanged(int, int, int)),
            wheel_, SLOT(slotSetColour(int, int, int)));


    //Grid
    connect(grid_, SIGNAL(selected(QColor)),
            this, SLOT(slotGridSelected(QColor)));

    //Notify about drag finished
    connect(wheel_, SIGNAL(dragFinished()),
            this, SIGNAL(dragFinished()));
}

void MvQColourSelectionWidget::setColour(QColor col)
{
    ignoreAlphaSpin_   = true;
    ignoreAlphaSlider_ = true;
    alphaSpin_->setValue(col.alpha());
    alphaSlider_->setValue(col.alpha());
    ignoreAlphaSpin_   = false;
    ignoreAlphaSlider_ = false;

    //This will set all the editors in the end
    //through the signal/slot mechanism
    wheel_->initColour(col);
}

void MvQColourSelectionWidget::setCurrentColour(QColor colour)
{
    currentColour_ = colour;
    emit colourSelected(currentColour_);
}

void MvQColourSelectionWidget::slotGridSelected(QColor col)
{
    col.setAlpha(alphaSpin_->value());

    ignoreRgbSpin_   = true;
    ignoreHslSpin_   = true;
    ignoreHtmlEdit_  = true;
    ignoreMacroEdit_ = true;

    rgbSpin_[0]->setValue(col.red());
    rgbSpin_[1]->setValue(col.green());
    rgbSpin_[2]->setValue(col.blue());

    int hue = col.hslHue();
    if (hue < 0)
        hue = 0;

    hslSpin_[0]->setValue(hue);
    hslSpin_[1]->setValue(col.hslSaturation());
    hslSpin_[2]->setValue(col.lightness());

    htmlEdit_->setText(col.name());
    macroEdit_->setText(macroToString(col));

    ignoreRgbSpin_   = false;
    ignoreHslSpin_   = false;
    ignoreHtmlEdit_  = false;
    ignoreMacroEdit_ = false;

    //notify the wheel
    emit colourChanged(hslSpin_[0]->value(),
                       hslSpin_[1]->value(),
                       hslSpin_[2]->value());

    setCurrentColour(col);
}

void MvQColourSelectionWidget::slotWheelChanged(int hue, int sat, int lgt)
{
    QColor col = QColor::fromHsl(hue, sat, lgt);
    col.setAlpha(alphaSpin_->value());

    ignoreRgbSpin_   = true;
    ignoreHslSpin_   = true;
    ignoreHtmlEdit_  = true;
    ignoreMacroEdit_ = true;

    rgbSpin_[0]->setValue(col.red());
    rgbSpin_[1]->setValue(col.green());
    rgbSpin_[2]->setValue(col.blue());

    hslSpin_[0]->setValue(hue);
    hslSpin_[1]->setValue(sat);
    hslSpin_[2]->setValue(lgt);

    htmlEdit_->setText(col.name());
    macroEdit_->setText(macroToString(col));

    ignoreRgbSpin_   = false;
    ignoreHslSpin_   = false;
    ignoreHtmlEdit_  = false;
    ignoreMacroEdit_ = false;

    setCurrentColour(col);
}

void MvQColourSelectionWidget::slotWheelChanged(QColor col)
{
    col.setAlpha(alphaSpin_->value());

    ignoreRgbSpin_   = true;
    ignoreHslSpin_   = true;
    ignoreHtmlEdit_  = true;
    ignoreMacroEdit_ = true;

    rgbSpin_[0]->setValue(col.red());
    rgbSpin_[1]->setValue(col.green());
    rgbSpin_[2]->setValue(col.blue());

    int hue = col.hslHue();
    if (hue < 0)
        hue = 0;

    hslSpin_[0]->setValue(hue);
    hslSpin_[1]->setValue(col.hslSaturation());
    hslSpin_[2]->setValue(col.lightness());

    htmlEdit_->setText(col.name());
    macroEdit_->setText(macroToString(col));

    ignoreRgbSpin_   = false;
    ignoreHslSpin_   = false;
    ignoreHtmlEdit_  = false;
    ignoreMacroEdit_ = false;

    setCurrentColour(col);
}

void MvQColourSelectionWidget::slotRgbChanged(int)
{
    if (ignoreRgbSpin_)
        return;

    ignoreHslSpin_   = true;
    ignoreHtmlEdit_  = true;
    ignoreMacroEdit_ = true;

    QColor col(rgbSpin_[0]->value(),
               rgbSpin_[1]->value(),
               rgbSpin_[2]->value(),
               alphaSpin_->value());

    int hue = col.hslHue();
    if (hue < 0)
        hue = 0;

    hslSpin_[0]->setValue(hue);
    hslSpin_[1]->setValue(col.hslSaturation());
    hslSpin_[2]->setValue(col.lightness());

    htmlEdit_->setText(col.name());
    macroEdit_->setText(macroToString(col));

    ignoreHslSpin_   = false;
    ignoreHtmlEdit_  = false;
    ignoreMacroEdit_ = false;

    emit colourChanged(hslSpin_[0]->value(),
                       hslSpin_[1]->value(),
                       hslSpin_[2]->value());

    setCurrentColour(col);
}

void MvQColourSelectionWidget::slotHslChanged(int)
{
    if (ignoreHslSpin_)
        return;

    ignoreRgbSpin_   = true;
    ignoreHtmlEdit_  = true;
    ignoreMacroEdit_ = true;

    QColor col = QColor::fromHsl(hslSpin_[0]->value(),
                                 hslSpin_[1]->value(),
                                 hslSpin_[2]->value(),
                                 alphaSpin_->value());

    rgbSpin_[0]->setValue(col.red());
    rgbSpin_[1]->setValue(col.green());
    rgbSpin_[2]->setValue(col.blue());

    htmlEdit_->setText(col.name());
    macroEdit_->setText(macroToString(col));

    ignoreRgbSpin_   = false;
    ignoreHtmlEdit_  = false;
    ignoreMacroEdit_ = false;

    emit colourChanged(hslSpin_[0]->value(),
                       hslSpin_[1]->value(),
                       hslSpin_[2]->value());

    setCurrentColour(col);
}

void MvQColourSelectionWidget::slotAlphaChanged(int alpha)
{
    if (ignoreAlphaSpin_)
        return;

    ignoreAlphaSlider_ = true;
    ignoreMacroEdit_   = true;

    QColor col = currentColour_;
    col.setAlpha(alpha);

    alphaSlider_->setValue(alpha);
    macroEdit_->setText(macroToString(col));

    ignoreAlphaSlider_ = false;
    ignoreMacroEdit_   = false;

    setCurrentColour(col);
}

void MvQColourSelectionWidget::slotAlphaSliderChanged(int alpha)
{
    if (ignoreAlphaSlider_)
        return;

    ignoreAlphaSpin_ = true;
    ignoreMacroEdit_ = true;

    QColor col = currentColour_;
    col.setAlpha(alpha);

    alphaSpin_->setValue(alpha);
    macroEdit_->setText(macroToString(col));

    ignoreAlphaSpin_ = false;
    ignoreMacroEdit_ = false;

    setCurrentColour(col);
}

void MvQColourSelectionWidget::slotHtmlChanged(QString txt)
{
    if (ignoreHtmlEdit_)
        return;

    QColor col(txt);
    if (!col.isValid())
        return;

    col.setAlpha(alphaSpin_->value());

    ignoreRgbSpin_   = true;
    ignoreHslSpin_   = true;
    ignoreMacroEdit_ = true;

    rgbSpin_[0]->setValue(col.red());
    rgbSpin_[1]->setValue(col.green());
    rgbSpin_[2]->setValue(col.blue());

    int hue = col.hslHue();
    if (hue < 0)
        hue = 0;

    hslSpin_[0]->setValue(hue);
    hslSpin_[1]->setValue(col.hslSaturation());
    hslSpin_[2]->setValue(col.lightness());

    macroEdit_->setText(macroToString(col));

    ignoreRgbSpin_   = false;
    ignoreHslSpin_   = false;
    ignoreMacroEdit_ = false;

    emit colourChanged(hslSpin_[0]->value(),
                       hslSpin_[1]->value(),
                       hslSpin_[2]->value());

    setCurrentColour(col);
}

void MvQColourSelectionWidget::slotMacroChanged(QString txt)
{
    if (ignoreMacroEdit_)
        return;

    QColor col(macroToColour(txt));
    if (!col.isValid())
        return;

    ignoreRgbSpin_     = true;
    ignoreHslSpin_     = true;
    ignoreAlphaSpin_   = true;
    ignoreAlphaSlider_ = true;
    ignoreHtmlEdit_    = true;

    rgbSpin_[0]->setValue(col.red());
    rgbSpin_[1]->setValue(col.green());
    rgbSpin_[2]->setValue(col.blue());

    int hue = col.hslHue();
    if (hue < 0)
        hue = 0;

    hslSpin_[0]->setValue(hue);
    hslSpin_[1]->setValue(col.hslSaturation());
    hslSpin_[2]->setValue(col.lightness());

    alphaSpin_->setValue(col.alpha());
    alphaSlider_->setValue(col.alpha());

    htmlEdit_->setText(col.name());

    ignoreRgbSpin_     = false;
    ignoreHslSpin_     = false;
    ignoreAlphaSpin_   = false;
    ignoreAlphaSlider_ = false;
    ignoreHtmlEdit_    = false;

    //Notify the wheel
    emit colourChanged(hslSpin_[0]->value(),
                       hslSpin_[1]->value(),
                       hslSpin_[2]->value());

    setCurrentColour(col);
}


QString MvQColourSelectionWidget::macroToString(QColor col)
{
    bool hasAlpha = (col.alpha() != 255);

    QString txt = "'RGB(";

    if (hasAlpha) {
        txt = "'RGBA(";
    }

    txt += QString::number(col.redF(), 'f', 4) + ",";
    txt += QString::number(col.greenF(), 'f', 4) + ",";
    txt += QString::number(col.blueF(), 'f', 4);

    if (!hasAlpha)
        txt += ")'";
    else
        txt += "," + QString::number(col.alphaF(), 'f', 4) + ")'";

    return txt;
}

QColor MvQColourSelectionWidget::macroToColour(QString name)
{
    QColor col;

    QRegExp rx("\\'RGB\\((\\d*\\.?\\d*),(\\d*\\.?\\d*),(\\d*\\.?\\d*)\\)\\'");
    if (rx.indexIn(name) > -1) {
        int n = rx.captureCount();
        if (n == 3) {
            col = QColor::fromRgbF(rx.cap(1).toFloat(),
                                   rx.cap(2).toFloat(),
                                   rx.cap(3).toFloat());
            return col;
        }
    }

    rx = QRegExp("\\'RGBA\\((\\d*\\.?\\d*),(\\d*\\.?\\d*),(\\d*\\.?\\d*),(\\d*\\.?\\d*)\\)\\'");
    if (rx.indexIn(name) > -1) {
        int n = rx.captureCount();
        if (n == 4) {
            col = QColor::fromRgbF(rx.cap(1).toFloat(),
                                   rx.cap(2).toFloat(),
                                   rx.cap(3).toFloat(), rx.cap(4).toFloat());
            return col;
        }
    }
    return col;
}

MvQColourCombo::MvQColourCombo(QWidget* parent) :
    QComboBox(parent),
    broadcastChange_(true),
    customColIndex_(-1)
{
    QFont font;
    QFontMetrics fm(font);
    int h = fm.height() + 4;
    int w = h * 2;
    setIconSize(QSize(w, h));

    MvQPalette::scan(*this);

    customColIndex_ = count();

    connect(this, SIGNAL(currentIndexChanged(int)),
            this, SLOT(slotCurrentChanged(int)));

    //----------------------------------
    // Context menu
    //-----------------------------------
    setContextMenuPolicy(Qt::ActionsContextMenu);

    QAction* acCopyCol = new QAction(this);
    acCopyCol->setText(tr("&Copy colour"));
    acCopyCol->setShortcut(QKeySequence(tr("Ctrl+C")));
    //acCopyCol->setIcon(QPixmap(":/desktop/editcopy.svg"));
    addAction(acCopyCol);

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

    QAction* acPasteCol = new QAction(this);
    acPasteCol->setText(tr("&Paste colour"));
    acPasteCol->setShortcut(QKeySequence(tr("Ctrl+V")));
    //acPasteCol->setIcon(QPixmap(":/desktop/editpaste.svg"));
    addAction(acPasteCol);

    connect(acPasteCol, SIGNAL(triggered()),
            this, SLOT(slotPasteColour()));
}

//void MvQColourLine::next(const Parameter&, const char* first, const char* /*second*/)
//{
//    addColour(MvQPalette::magics(first), QString::fromStdString(param_.beautifiedName(first)), QString(first));
//}

void MvQColourCombo::next(const string& name, QColor col, bool pseudo)
{
    if (!pseudo)
        addColour(col, QString::fromStdString(metview::beautify(name)), QString::fromStdString(name));
}

void MvQColourCombo::setColour(int idx, QColor col)
{
    if (idx < 0 || idx > count() - 1)
        return;

    QIcon icon;

    if (pix_.isNull()) {
        int w = iconSize().width();
        int h = iconSize().height();
        pix_  = QPixmap(w, h);
        pix_.fill(Qt::transparent);
    }

    QPainter painter(&pix_);

    if (col.isValid()) {
        //paint the chequered background when alpha is set
        if (col.alpha() != 255) {
            MvQPalette::paintAlphaBg(&painter, QRect(0, 0, pix_.width(), pix_.height()));
#if 0
            QColor c1(170,170,170);
            QColor c2(190,190,190);
            int rs=10;
            int nx=pix_.width()/rs;
            int ny=pix_.height()/rs;
            QColor cAct=c1;
            for(int i=0; i <= nx; i++)
                for(int j=0; j <= ny; j++)
            {
                QRect r(1+i*rs,1+j*rs,rs,rs);
                if(j%2 ==0)
                {
                    if(i%2 == 0) cAct=c1;
                    else cAct=c2;
                }
                else
                {
                    if(i%2 == 0) cAct=c2;
                    else cAct=c1;
                }
                painter.fillRect(r,cAct);
            }
#endif
            painter.setPen(QColor(0, 0, 0, col.alpha()));
        }
        else {
            painter.setPen(Qt::black);
        }

        painter.setBrush(QBrush(col));
        painter.drawRect(1, 1, pix_.width() - 2, pix_.height() - 2);
    }
    else {
        pix_.fill(Qt::transparent);
        //painter.setBrush(Qt::white);
        painter.setPen(QPen(Qt::gray, 0., Qt::DashLine));
        painter.drawRect(1, 1, pix_.width() - 2, pix_.height() - 3);
    }

    icon.addPixmap(pix_);

    setItemIcon(idx, icon);
}


void MvQColourCombo::addColour(QColor color, QString name, QString realName, bool setCurrent)
{
    bool pseudoCol = MvQPalette::isPseudo(realName.toStdString());

    if (!color.isValid() && !pseudoCol)
        return;

    for (int i = 0; i < count(); i++) {
        if (name.compare(itemText(i), Qt::CaseInsensitive) == 0) {
            if (setCurrent) {
                setCurrentIndex(i);
            }
            return;
        }
    }

    int idx = -1;
    if (count() == 0) {
        addItem(name, realName);
        idx = 0;
    }
    else {
        int i = 0;
        while (i < count() && itemText(i) < name)
            i++;

        idx = i;
        insertItem(idx, name, realName);
        if (setCurrent)
            setCurrentIndex(idx);
    }

    if (idx != -1) {
        setColour(idx, color);
    }
}

//There can be only one custom colour
void MvQColourCombo::setCustomColour(QColor color, QString name, QString realName, bool setCurrent)
{
    if (!color.isValid())
        return;

    if (count() != customColIndex_+1) {
        Q_ASSERT(count() == customColIndex_);
        insertItem(customColIndex_, name, realName);
    } else {
        setItemText(customColIndex_, name);
        setItemData(customColIndex_, realName);
    }
    setColour(customColIndex_, color);

    if (setCurrent) {
        setCurrentIndex(customColIndex_);
        // although we set the current index, this does not always trigger
        // slotCurrentChanged because all custom RGB values will have the
        // same index (42), even though they might be different colours
        // (which could be an issue if we open two different icons which both
        // have custom colours for the same parameter).
        // Therefore we manually trigger slotCurrentChanged here to make sure
        slotCurrentChanged(customColIndex_);
    }
}

#if 0
void MvQColourLine::refresh(const vector<string>& values)
{
    if (values.size() > 0) {
        for (int i = 0; i < colCb_->count(); i++) {
            if (colCb_->itemData(i).toString().toStdString() == values[0]) {
                colCb_->setCurrentIndex(i);
                return;
            }
        }

        QString name = QString::fromStdString(values[0]);
        //This sets the current index as well
        setCustomColour(MvQPalette::magics(values[0]), name, name, true);
    }

    //changed_ = false;
}
#endif

void MvQColourCombo::slotCurrentChanged(int index)
{
    if (index >= 0 && index < count()) {
        QString name = itemData(index).toString();
        //owner_.set(param_.name(), name.toStdString());
        if (!MvQPalette::isPseudo(name.toStdString())) {
            //if (helper_ && helper_->widget())
            //    helper_->widget()->setEnabled(true);
            //updateHelper();
            if (broadcastChange_)
                emit colourSelected(name);
        }
        else {
            //if (helper_ && helper_->widget())
            //    helper_->widget()->setEnabled(false);
        }
    }
}

// force selection from the outside
void MvQColourCombo::changeSelection(QString val)
{
    for (int i = 0; i < count(); i++) {
        if (itemData(i).toString() == val) {
            broadcastChange_ = false;
            setCurrentIndex(i);
            broadcastChange_ = true;
            return;
        }
    }

    //This sets the current index as well
    broadcastChange_ = false;
    setCustomColour(MvQPalette::magics(val.toStdString()), val, val, true);
    broadcastChange_ = true;

    //If the current index did not change the slotCurrentChanged() was
    //not called, so here we need to notify the owner about the change!!
    //if(colCb_->currentIndex() ==  currentPrev)
    //{
    //	owner_.set(param_.name(),name.toStdString());
    //}
}

#if 0
void MvQColourCombo::updateHelper()
{
    if (!helper_)
        return;

    int index = colCb_->currentIndex();

    vector<string> vals;
    vals.push_back(colCb_->itemData(index).toString().toStdString());
    helper_->refresh(vals);
}

void MvQColourCombo::slotHelperEditConfirmed()
{
    int index = currentIndex();
    if (index != -1) {
        QString name = itemData(index).toString();
        //owner_.set(param_.name(), name.toStdString());
    }
}
#endif

void MvQColourCombo::slotCopyColour()
{
    int index = currentIndex();
    if (index != -1) {
        QString name = itemData(index).toString();
        QColor col   = MvQPalette::magics(name.toStdString());
        MvQ::toClipboard("\"" + QString::fromStdString(MvQPalette::toString(col)) + "\"");
    }
}

void MvQColourCombo::slotPasteColour()
{
    QString txt = MvQ::fromClipboard();
    if (!txt.isEmpty()) {
        QColor col = MvQPalette::magics(txt.toStdString());
        if (col.isValid()) {
            emit colourSelected(QString::fromStdString(MvQPalette::toString(col)));
        }
    }
}

//==================================================
//
//  MvQColourWidget
//
//==================================================

MvQColourWidget::MvQColourWidget(QWidget *parent) : QWidget(parent)
{
    QVBoxLayout *vb = new QVBoxLayout(this);
    vb->setContentsMargins(0,0,0,0);
    vb->setSpacing(1);

    colCb_ = new MvQColourCombo(this);
    vb->addWidget(colCb_);

    selector_ = new MvQColourSelectionWidget(true, this);
    vb->addWidget(selector_);

    connect(selector_, SIGNAL(colourSelected(QColor)),
            this, SLOT(slotSelectorEdited(QColor)));

    connect(colCb_, SIGNAL(colourSelected(QString)),
            this, SLOT(slotComboEdited(QString)));

    //connect(selector_, SIGNAL(dragFinished()),
    //        this, SIGNAL(editConfirmed()));
}

void MvQColourWidget::initColour(QString colStr)
{
    oriName_ = colStr;

    QColor col = MvQPalette::magics(colStr.toStdString());
    if (col.isValid())
        selector_->setColour(col);
}

void MvQColourWidget::slotSelectorEdited(QColor col)
{
    QString val;
    if (MvQPalette::magics(oriName_.toStdString()) == col)
        val = oriName_;
    else
        val = QString::fromStdString(MvQPalette::toString(col));

    colCb_->changeSelection(val);

    emit valueChanged(val);
}


void MvQColourWidget::slotComboEdited(QString val)
{
    if (val.isEmpty())
        return;

    QColor col = MvQPalette::magics(val.toStdString());
    if ((selector_->currentColour() == col ||
         MvQPalette::toString(selector_->currentColour()) == val.toStdString()))
        return;

    if (col.isValid()) {
        selector_->setColour(col);
    }

    emit valueChanged(val);
}

QString MvQColourWidget::currentValue() const
{
    QColor col = MvQPalette::magics(oriName_.toStdString());
    if (selector_->currentColour() == col)
            return oriName_;

    return QString::fromStdString(MvQPalette::toString(selector_->currentColour()));
}
