/***************************** 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 <QtGlobal>
#include <QFrame>
#include <QAction>
#include <QButtonGroup>
#include <QComboBox>
#include <QDebug>
#include <QFrame>
#include <QGridLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QMenu>
#include <QMouseEvent>
#include <QMultiMap>
#include <QPainter>
#include <QPainterPath>
#include <QPushButton>
#include <QSlider>
#include <QSpinBox>
#include <QStackedWidget>
#include <QTabWidget>
#include <QToolButton>
#include <QVBoxLayout>

#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
#include <QtCore5Compat/QRegExp>
#else
#include <QRegExp>
#endif

#include <cmath>
#include <vector>

#include "MvMiscellaneous.h"
#include "MvQMethods.h"
#include "MvQPalette.h"
#include "MvQTheme.h"
#include "MvQStreamOper.h"
#include "MvLog.h"
#include "MvSci.h"

//#define MV_UI_USE_HCL_

//==============================
//
// MvQCompactColourGrid
//
//===============================

// TODO: this class is work in progress
MvQCompactColourGrid::MvQCompactColourGrid(int minSize, QList<MvQColourItem> items, LayoutMode layoutMode, QWidget* parent) :
    QWidget(parent),
    minSize_(minSize),
    layoutMode_(layoutMode)
{
    //    minSize_ = cellsPerRow_*minCellSize_ + (cellsPerRow_+1) *cellGap_;

    gridCol_ = MvQTheme::colour("coloureditor", "compact_grid");
    highlightCol_ = MvQTheme::colour("coloureditor", "compact_grid_highlight");

    if (layoutMode_ == SingleRowMode) {
        items_ = items;
        rowNum_ = 1;
    }
    else {
        makeItems(items);
    }

    if (layoutMode_ == SingleRowMode || layoutMode_ == ColumnMode) {
        minSize_ = cellsPerRow_ * minCellSize_ + (cellsPerRow_ + 1) * cellGap_;
        int minYSize = rowNum_ * minCellSize_ + 2 * cellGap_;
        cellSize_ = minCellSize_;
        gridSizeX_ = minSize_;
        gridSizeY_ = minYSize;
    }
    else {
        gridSizeX_ = minSize;
        cellSize_ = (gridSizeX_ - 2 * cellGap_) / cellsPerRow_;
        int minYSize = rowNum_ * cellSize_ + (rowNum_ + 1) * cellGap_;
        if (minYSize > minSize) {
            cellSize_ = (minSize - (rowNum_ + 1) * cellGap_) / rowNum_;
            gridSizeY_ = minSize;
            gridSizeX_ = cellsPerRow_ * cellSize_ + 2 * cellGap_;
        }
        else {
            gridSizeY_ = minYSize;
        }
    }

    setFixedSize(gridSizeX_, gridSizeY_);
    //    setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum));
    //    setMinimumSize(QSize(minSize_, minYSize));

    setMouseTracking(true);

    setStyleSheet("QWidget {background-color: transparent;}");
    // setAutoFillBackground(true);
    createGrid();
}

void MvQCompactColourGrid::resizeEvent(QResizeEvent* /*event*/)
{
    //    int w     = width();
    //    int h     = height();
    ////    gridSize_ = (w < h) ? w : h;
    //    gridSize_ = w;
    //    cellSize_ = (gridSize_ - (cellsPerRow_+1)*cellGap_)/cellsPerRow_;
    //    createGrid();
}

void MvQCompactColourGrid::paintEvent(QPaintEvent* /*event*/)
{
    QPainter painter(this);
    QStyleOption opt;
    opt.initFrom(this);
    style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this);

    painter.drawPixmap(0, 0, pix_);
    if (highlightIndex_ != -1) {
        int row = highlightIndex_ / cellsPerRow_;
        int col = highlightIndex_ - row * cellsPerRow_;
        painter.setPen(QPen(highlightCol_, 2));
        QRect r(col * cellSize_ + (col + 1) * cellGap_,
                cellGap_ + row * cellSize_,
                cellSize_, cellSize_);
        painter.drawRect(r.adjusted(-2, -2, 2, 2));
    }
}

void MvQCompactColourGrid::replace(QList<QColor> items)
{
    items_.clear();
    for (auto c : items) {
        items_ << MvQColourItem(c, "");
    }

    createGrid();
    update();
}

void MvQCompactColourGrid::makeItems(QList<MvQColourItem> baseItems)
{
    if (baseItems.size() > 0) {
        if (layoutMode_ == ColumnMode) {
            rowNum_ = static_cast<int>(baseItems[0].shades_.size());
            for (int j = 0; j < rowNum_; j++) {
                for (int i = 0; i < cellsPerRow_ && i < baseItems.count(); i++) {
                    int factor = (j < static_cast<int>(baseItems[i].shades_.size())) ? baseItems[i].shades_[j] : 100;
                    QColor c;
                    if (factor < 0) {
                        c = baseItems[i].col_.lighter(100 - factor);
                    }
                    else if (factor > 0) {
                        c = baseItems[i].col_.darker(100 + factor);
                    }
                    else {
                        c = baseItems[i].col_;
                    }
                    items_ << MvQColourItem(c, baseItems[i].name_);
                }
            }
        }
        else if (layoutMode_ == RowMode) {
            rowNum_ = baseItems.count();
            cellsPerRow_ = baseItems[0].shades_.size();
            for (int j = 0; j < rowNum_; j++) {
                for (int i = 0; i < cellsPerRow_ && i < static_cast<int>(baseItems[j].shades_.size()); i++) {
                    int factor = (i < static_cast<int>(baseItems[j].shades_.size())) ? baseItems[j].shades_[i] : 100;
                    QColor c;
                    if (factor < 0) {
                        c = baseItems[j].col_.lighter(100 - factor);
                    }
                    else if (factor > 0) {
                        c = baseItems[j].col_.darker(100 + factor);
                    }
                    else {
                        c = baseItems[j].col_;
                    }
                    items_ << MvQColourItem(c, baseItems[j].name_);
                }
            }
        }
    }


    //    std::vector<int> factors = {-80, -60, -40, 25, 50};
    //    rowNum_ = static_cast<int>(factors.size());
    //    for (int j=0; j < rowNum_; j++) {
    //        int factor = factors[j];
    //        for (int i=0; i < cellsPerRow_ && i < baseItems.count(); i++) {
    //            QColor c;
    //            if (factor < 0) {
    //                c = baseItems[i].col_.lighter(100 - factor);
    //            } else if (factor > 0){
    //                c = baseItems[i].col_.darker(100 + factor);
    //            } else {
    //                c = baseItems[i].col_;
    //            }
    //            items_ << MvQColourItem(c, baseItems[i].name_);
    //        }
    //    }
}

void MvQCompactColourGrid::createGrid()
{
    pix_ = QPixmap(gridSizeX_, gridSizeY_);
    pix_.fill(Qt::transparent);
    QPainter painter(&pix_);
    painter.setPen(QPen(gridCol_));

    if (layoutMode_ == SingleRowMode || layoutMode_ == ColumnMode) {
        for (int i = 0; i < items_.count(); i++) {
            int row = i / cellsPerRow_;
            int col = i - row * cellsPerRow_;
            painter.setBrush(QBrush(items_[i].col_));
            painter.drawRect(col * cellSize_ + (col + 1) * cellGap_,
                             cellGap_ + row * cellSize_,
                             cellSize_, cellSize_);
            //        painter.fillRect(col * cellSize_ + (col+1)*cellGap_,
            //                         cellGap_ + row * cellSize_,
            //                         cellSize_, cellSize_, items_[i].col_);
        }
    }
    else {
        for (int i = 0; i < items_.count(); i++) {
            int row = i / cellsPerRow_;
            int col = i - row * cellsPerRow_;
            painter.setBrush(QBrush(items_[i].col_));
            painter.drawRect(cellGap_ + col * cellSize_,
                             row * cellSize_ + (row + 1) * cellGap_,
                             cellSize_, cellSize_);
            //        painter.fillRect(col * cellSize_ + (col+1)*cellGap_,
            //                         cellGap_ + 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 MvQCompactColourGrid::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 MvQCompactColourGrid::mouseMoveEvent(QMouseEvent* event)
{
    if (event->buttons() != Qt::NoButton)
        return;

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

void MvQCompactColourGrid::leaveEvent(QEvent*)
{
    if (highlightIndex_ != -1) {
        highlightIndex_ = -1;
        update();
    }
}

int MvQCompactColourGrid::index(QPoint pos)
{
    int row = (pos.y() - cellGap_) / (cellSize_);
    int col = pos.x() / (cellSize_ + cellGap_);

    return row * cellsPerRow_ + col;
}

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

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

    highlightCol_ = MvQTheme::colour("coloureditor", "grid_highlight");

    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&)));
}

// Sorting the colours
void MvQColourGrid::sort()
{
    QMultiMap<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.insert(sv, item);
    }

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

void MvQColourGrid::next(const std::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_ = std::min((w < h) ? w : h, maxGridSize_);
    cellSize_ = gridSize_ / cellsPerRow_;
    marginX_ = (w - gridSize_) / 2;
    marginY_ = (h - gridSize_) / 2;

    createGrid();
}

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

    // Render ring pixmap
    painter.drawPixmap(marginX_, marginY_, pix_);

    if (highlightIndex_ != -1) {
        int row = highlightIndex_ / cellsPerRow_;
        int col = highlightIndex_ - row * cellsPerRow_;
        painter.setPen(QPen(highlightCol_, 2));
        QRect r(marginX_ + col * cellSize_,
                marginY_ + row * cellSize_,
                cellSize_, cellSize_);
        painter.drawRect(r.adjusted(0, 0, 1, 1));
    }
}

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_);
        if (highlightIndex_ != idx) {
            highlightIndex_ = idx;
            update();
        }
    }
    else if (highlightIndex_ != -1) {
        highlightIndex_ = -1;
        update();
    }
}

void MvQColourGrid::leaveEvent(QEvent*)
{
    if (highlightIndex_ != -1) {
        highlightIndex_ = -1;
        update();
    }
}

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

    return row * cellsPerRow_ + col;
}

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

    auto* menu = new QMenu(this);
    QAction* ac = nullptr;

    ac = new QAction(this);
    ac->setText(tr("&Copy colour"));
    ac->setShortcut(QKeySequence(tr("Ctrl+C")));
    ac->setIcon(QPixmap(":/editor/copy_to_clipboard.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),
    outerRadius_(minSize),
    innerRadius_(minSize - 20)
{
    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 triangle/rect pixmap
    createShape();
}

void MvQColourWheel::paintEvent(QPaintEvent* /*event*/)
{
    QStyleOption opt;
    opt.initFrom(this);
    QPainter painter(this);

    // style()->drawPrimitive(QStyle::PE_Widget, &opt, &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
    renderRingSelector(&painter);

    // Render  triangle/rect
    painter.drawPixmap(centre_.x() - innerRadius_, centre_.y() - innerRadius_, shapePix_);

    // Render the selector onto the triangle/rect
    renderShapeSelector(&painter);
}

// Render the ring into a pixmap
void MvQColourWheel::createRing()
{
    ringPix_ = QPixmap(2 * outerRadius_, 2 * outerRadius_);
    ringPix_.fill(Qt::transparent);

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

    auto cg = makeRingGradient();
    painter.translate(outerRadius_, outerRadius_);

    QPainterPath pp;
    pp.addEllipse(QPoint(0, 0), outerRadius_, outerRadius_);
    pp.addEllipse(QPoint(0, 0), innerRadius_, innerRadius_);

    // Coloured circle
    QBrush brush(cg);
    painter.setPen(Qt::NoPen);
    painter.setBrush(brush);
    painter.drawPath(pp);
}

void MvQColourWheel::renderRingSelector(QPainter* painter)
{
    float alpha = MvSci::degToRad(-hue_);
    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::renderShapeSelector(QPainter* painter)
{
    QPointF pos;
    if (clsToPos(pos)) {
        shapePos_ = pos.toPoint();

        if (!isDark()) {
            painter->setPen(huePenBlack_);
        }
        else {
            painter->setPen(huePenWhite_);
        }
        painter->drawEllipse(centre_ + pos, shapeSelectorRadius_, shapeSelectorRadius_);
    }
}

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

    if (delta.x() * delta.x() + delta.y() * delta.y() <= shapeSelectorRadius_ * shapeSelectorRadius_) {
        shapeOffset_ = delta;
        dragType_ = ShapeDrag;
    }
    else {
        shapeOffset_ = QPoint();
        dragType_ = checkPoint(event->pos());
    }
}

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

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

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

    if (dragHappened)
        emit dragFinished();
}


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

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

    setHue(hue);
}

void MvQColourWheel::dragShapeSelector(QPoint pos)
{
    if (dragType_ != ShapeDrag)
        return;

    QPoint dp = pos - shapeOffset_ - centre_;

    int cls1 = 0, cls2 = 0;
    if (projectPosToCls(dp, cls1, cls2)) {
        setCls(cls1, cls2);
    }
}


//==============================
//
// MvQHslWheel
//
//===============================

MvQHslWheel::MvQHslWheel(int minSize, QWidget* parent) :
    MvQColourWheel(minSize, parent)
{
}

QConicalGradient MvQHslWheel::makeRingGradient()
{
    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);
    return cg;
}

void MvQHslWheel::createShape()
{
    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);

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

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

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

MvQHslWheel::DragType MvQHslWheel::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_ != ShapeDrag) {
        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 = 0, sat = 0;
        if (posToCls(pp, lgt, sat)) {
            // MvLog().info() << "ShapeDrag";
            setCls(lgt, sat);
            return ShapeDrag;
        }
        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 MvQHslWheel::clsToPos(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 = MvSci::degToRad((hue_ - 90));
    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 MvQHslWheel::posToCls(QPointF pos, int& lgt, int& sat)
{
    // Transform coordinate system
    float alpha = MvSci::degToRad(-(hue_ - 90));
    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 MvQHslWheel::projectPosToCls(QPointF pos, int& lgt, int& sat)
{
    // Transform coordinate system
    float alpha = MvSci::degToRad(-(hue_ - 90));
    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 MvQHslWheel::setHue(int hue)
{
    hue_ = hue;

    createShape();

    update();

    emit hslSelected(hue_, sat_, lgt_);
}

void MvQHslWheel::setCls(int lgt, int sat)
{
    lgt_ = lgt;
    sat_ = sat;

    update();

    emit hslSelected(hue_, sat_, lgt_);
}

void MvQHslWheel::slotSetColour(QColor col)
{
    // MvLog().info() << MV_FN_INFO << "hue=" << col.hslHue() <<  " col=" << col;
    hue_ = std::max(col.hslHue(), 0);
    lgt_ = col.lightness();
    sat_ = col.hslSaturation();

    createShape();
    update();
}

void MvQHslWheel::initColour(QColor col)
{
    //    int hue = std::min(col.hslHue(), 0);
    //    int lgt = col.lightness();
    //    int sat = col.hslSaturation();

    slotSetColour(col);
    // 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 MvQHslWheel::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 MvQHslWheel::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));
}

bool MvQHslWheel::isDark() const
{
    return lgt_ <= 110;
}

//==============================
//
// MvQHclWheel
//
//===============================

MvQHclWheel::MvQHclWheel(int minSize, QWidget* parent) :
    MvQColourWheel(minSize, parent)
{
}

QConicalGradient MvQHclWheel::makeRingGradient()
{
    QConicalGradient cg(0, 0, 0);
    for (int i = 0; i < 360; i += 5) {
        cg.setColorAt(static_cast<float>(i) / 360., MvQPalette::fromHclLuv(i, ringChroma_, ringLum_));
    }
    cg.setColorAt(1, MvQPalette::fromHclLuv(359, ringChroma_, ringLum_));
    return cg;
}

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

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

    QColor col;
    float chromaFactor = static_cast<float>(maxShapeChroma_) / 255.;
    float lumFactor = static_cast<float>(maxShapeLum_) / 255.;
    for (int j = 0; j < ny; j++) {
        int chroma = static_cast<int>(static_cast<float>(255 - j) * chromaFactor);
        for (int i = 0; i < nx; i++) {
            int lum = static_cast<int>(static_cast<float>(i) * lumFactor);
            if (abs(nx / 2 - i) < j / 2) {
                //                int sat = saturation(chroma, lgt);
                //                col     = QColor::fromHsl(hue_, sat, lgt);
                col = MvQPalette::fromHclLuv(hue_, chroma, lum);

                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);

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

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

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

MvQColourWheel::DragType MvQHclWheel::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_ != ShapeDrag) {
        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 chroma = 0, lum = 0;
        if (posToCls(pp, chroma, lum)) {
            setCls(chroma, lum);
            return ShapeDrag;
        }
        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 MvQHclWheel::clsToPos(QPointF& pos)
{
    int chroma = clipTo(chroma_, 0, maxShapeChroma_);
    float chroma255 = 255. * static_cast<float>(chroma) / static_cast<float>(maxShapeChroma_);
    float yt = innerRadius_ - (255. - chroma255) * (1.5 * static_cast<float>(innerRadius_)) / 255.;

    int lum = clipTo(lum_, 0, maxShapeLum_);
    float lum255 = 255. * static_cast<float>(lum) / static_cast<float>(maxShapeLum_);
    float xt = static_cast<float>(innerRadius_) * sqrt(3.) * (lum255 - 128) / 255.;

    if (yt < innerRadius_ && yt > -innerRadius_ / 2. &&
        fabs(xt) <= (innerRadius_ - yt) / 2) {
        //    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 = MvSci::degToRad(hue_ - 90);
        float x = cos(alpha) * xt - sin(alpha) * yt;
        float y = sin(alpha) * xt + cos(alpha) * yt;
        pos = QPointF(x, -y);
        return true;
    }
    return false;

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


bool MvQHclWheel::posToCls(QPointF pos, int& chroma, int& lum)
{
    // Transform coordinate system
    float alpha = MvSci::degToRad(-(hue_ - 90));
    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) {
        float chroma255 = 255. - 255. * (innerRadius_ - yt) / (1.5 * static_cast<float>(innerRadius_));
        // MvLog().info() << MV_FN_INFO << "chroma255=" << chroma255;
        chroma = scaleChromaFrom255(chroma255);
        // MvLog().info() << MV_FN_INFO << "  chroma=" << chroma;
        // chroma = std::max(std::min(static_cast<int>(static_cast<float>(maxShapeChroma_) * (chroma255/255.)), maxShapeChroma_),0);
        float lum255 = 128. + 128. * xt / (static_cast<float>(innerRadius_) * std::sqrt(3.));
        lum = scaleLumFrom255(lum255);
        return true;
    }
    return false;
}

bool MvQHclWheel::projectPosToCls(QPointF pos, int& chroma, int& lum)
{
    // Transform coordinate system
    float alpha = MvSci::degToRad(-(hue_ - 90));
    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);

        float chroma255 = 255. - 255. * (innerRadius_ - yt) / (1.5 * static_cast<float>(innerRadius_));
        // MvLog().info() << MV_FN_INFO << "chroma255=" << chroma255;
        chroma = scaleChromaFrom255(chroma255);
        // MvLog().info() << MV_FN_INFO << "  chroma=" << chroma;
        // chroma = std::max(std::min(static_cast<int>(static_cast<float>(maxShapeChroma_) * (chroma255/255.)), maxShapeChroma_),0);
        float lum255 = 128. + 128. * xt / (static_cast<float>(innerRadius_) * std::sqrt(3.));
        lum = scaleLumFrom255(lum255);

        //        lum = std::max(std::min(static_cast<int>(static_cast<float>(maxShapeLum_) * (lum255/255.)), maxShapeLum_),0);
        return true;
    }
    else if (yt < innerRadius_ && yt > -innerRadius_ / 2.) {
        float chroma255 = 255. - 255. * (innerRadius_ - yt) / (1.5 * static_cast<float>(innerRadius_));
        chroma = std::max(std::min(static_cast<int>(static_cast<float>(maxShapeChroma_) * (chroma255 / 255.)), maxShapeChroma_), 0);
        // lum = ((xt >0)?1:-1)*

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


        //        float lum255  = 128. + 255. * ((xt > 0) ? 1 : -1) / (static_cast<float>((innerRadius_ - yt) / 2) * std::sqrt(3.));
        lum = scaleLumFrom255(lum255);
        // MvLog().info() << "Size lum255=" << lum255 << " lum=" << lum;

        //        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) {
        float lum255 = 128. + 255. * xt / (static_cast<float>(innerRadius_) * std::sqrt(3.));
        lum = std::max(std::min(static_cast<int>(static_cast<float>(maxShapeLum_) * (lum255 / 255.)), maxShapeLum_), 0);

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

    return false;
}

void MvQHclWheel::setHue(int hue)
{
    hue_ = hue;
    createShape();
    update();
    emit colourSelected(MvQPalette::fromHclLuv(hue_, chroma_, lum_));
}

void MvQHclWheel::setCls(int chroma, int lum)
{
    chroma_ = chroma;
    lum_ = lum;
    update();
    emit colourSelected(MvQPalette::fromHclLuv(hue_, chroma_, lum_));
}

void MvQHclWheel::slotSetColour(QColor col)
{
    MvQPalette::toHclLuv(col, hue_, chroma_, lum_);
    hue_ = std::max(0, std::min(hue_, 359));
    // MvLog().info() << MV_FN_INFO << hue_ << " " << chroma_ << " " << lum_;
    createShape();
    update();
}

void MvQHclWheel::initColour(QColor col)
{
    slotSetColour(col);
    // 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);
}

bool MvQHclWheel::isDark() const
{
    return lum_ < 50;
}

int MvQHclWheel::scaleChromaFrom255(float v) const
{
    return clipTo(static_cast<float>(maxShapeChroma_) * (v / 255.), 0, maxShapeChroma_);
}

int MvQHclWheel::scaleLumFrom255(float v) const
{
    return clipTo(static_cast<float>(maxShapeLum_) * (v / 255.), 0, maxShapeLum_);
}

int MvQHclWheel::clipTo(int v, int minVal, int maxVal) const
{
    return std::max(minVal, std::min(v, maxVal));
}

#if 0
int MvQHslWheel::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 MvQHslWheel::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));
}
#endif


//==============================
//
// ColourSpaceSliderWidget
//
//===============================

void ColourSpaceSliderWidget::adjustGrooves(int ignoreIndex)
{
    for (int i = 0; i < 3; i++) {
        if (i != ignoreIndex) {
            adjustGroove(i);
        }
    }
}


void ColourSpaceSliderWidget::sliderChanged(int val)
{
    if (!ignoreSlider_) {
        auto w = dynamic_cast<QSlider*>(QObject::sender());
        if (w) {
            auto idx = sliders_.indexOf(w);
            if (idx >= 0) {
                ignoreSpin_ = true;
                spins_[idx]->setValue(val);
                ignoreSpin_ = false;
                emit colourChanged(currentColour());
                adjustGrooves(idx);
            }
        }
    }
}

void ColourSpaceSliderWidget::spinChanged(int val)
{
    if (!ignoreSpin_) {
        auto w = dynamic_cast<QSpinBox*>(QObject::sender());
        if (w) {
            auto idx = spins_.indexOf(w);
            if (idx >= 0) {
                ignoreSlider_ = true;
                sliders_[idx]->setValue(val);
                ignoreSlider_ = false;
                emit colourChanged(currentColour());
                adjustGrooves(idx);
            }
        }
    }
}

//==============================
//
// RGBWidget
//
//===============================

RGBWidget::RGBWidget(QWidget* parent) :
    ColourSpaceSliderWidget(parent)
{
    auto grid = new QGridLayout(this);
    grid->setVerticalSpacing(2);

    QStringList rgbNames = {tr("R:"), tr("G:"), tr("B:")};
    for (int i = 0; i < rgbNames.size(); i++) {
        auto label = new QLabel(rgbNames[i], this);
        grid->addWidget(label, i, 0);

        auto slider = new QSlider(this);
        slider->setOrientation(Qt::Horizontal);
        slider->setRange(0, 255);

        connect(slider, SIGNAL(valueChanged(int)),
                this, SLOT(sliderChanged(int)));
        grid->addWidget(slider, i, 1);
        sliders_ << slider;

        auto spin = new QSpinBox(this);
        spin->setRange(0, 255);

        grid->addWidget(spin, i, 2);
        connect(spin, SIGNAL(valueChanged(int)),
                this, SLOT(spinChanged(int)));
        grid->setColumnStretch(1, 1);
        spins_ << spin;
    }
    adjustGrooves(-1);
}


void RGBWidget::adjustGroove(int index)
{
    auto c = currentColour();
    QList<QColor> cols;
    QColor c1, c2;
    if (index == 0) {
        c1 = QColor(0, c.green(), c.blue());
        c2 = QColor(255, c.green(), c.blue());
    }
    else if (index == 1) {
        c1 = QColor(c.red(), 0, c.blue());
        c2 = QColor(c.red(), 255, c.blue());
    }
    else if (index == 2) {
        c1 = QColor(c.red(), c.green(), 0);
        c2 = QColor(c.red(), c.green(), 255);
    }

    QString sh(
        "QSlider::groove:horizontal {background: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 %1, stop:1 %2);}"
        "QSlider::handle:horizontal { "
        "background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #b4b4b4, stop:1 #8f8f8f); "
        "border: 1px solid #5c5c5c; "
        "width: 18px;"
        "margin: -2px 0;"
        "border-radius: 3px;}");

    sh = sh.arg(c1.name()).arg(c2.name());

    sliders_[index]->setStyleSheet(sh);
}

void RGBWidget::setColour(QColor col)
{
    ignoreSpin_ = true;
    ignoreSlider_ = true;

    auto v = col.red();
    sliders_[0]->setValue(v);
    spins_[0]->setValue(v);

    v = col.green();
    sliders_[1]->setValue(v);
    spins_[1]->setValue(v);

    v = col.blue();
    sliders_[2]->setValue(v);
    spins_[2]->setValue(v);

    ignoreSpin_ = false;
    ignoreSlider_ = false;

    adjustGrooves(-1);
}

QColor RGBWidget::currentColour() const
{
    return {spins_[0]->value(), spins_[1]->value(), spins_[2]->value()};
}

//==============================
//
// HSLWidget
//
//===============================

HSLWidget::HSLWidget(QWidget* parent) :
    ColourSpaceSliderWidget(parent)
{
    auto grid = new QGridLayout(this);
    QStringList rgbNames = {tr("H:"), tr("S:"), tr("L:")};
    for (int i = 0; i < rgbNames.size(); i++) {
        auto label = new QLabel(rgbNames[i], this);
        grid->addWidget(label, i, 0);

        auto slider = new QSlider(this);
        slider->setOrientation(Qt::Horizontal);
        if (i == 0) {
            slider->setRange(0, 359);
        }
        else {
            slider->setRange(0, 255);
        }
        connect(slider, SIGNAL(valueChanged(int)),
                this, SLOT(sliderChanged(int)));
        grid->addWidget(slider, i, 1);
        sliders_ << slider;

        auto spin = new QSpinBox(this);
        if (i == 0) {
            spin->setRange(0, 359);
        }
        else {
            spin->setRange(0, 255);
        }
        grid->addWidget(spin, i, 2);
        connect(spin, SIGNAL(valueChanged(int)),
                this, SLOT(spinChanged(int)));
        grid->setColumnStretch(1, 1);
        spins_ << spin;
    }
    adjustGrooves(-1);
}

void HSLWidget::adjustGroove(int index)
{
    auto c = currentColour();
    QList<QColor> cols;
    if (index == 0) {
        // hue range=0-359
        for (int i = 0; i < 360; i += 30) {
            cols << QColor::fromHsl(i, c.hslSaturation(), c.lightness());
        }
        cols << QColor::fromHsl(359, c.hslSaturation(), c.lightness());
    }
    else if (index == 1) {
        for (int i = 0; i <= 255; i += 15) {
            cols << QColor::fromHsl(c.hslHue(), i, c.lightness());
        }
    }
    else if (index == 2) {
        for (int i = 0; i <= 255; i += 15) {
            cols << QColor::fromHsl(c.hslHue(), c.hslSaturation(), i);
        }
    }

    QString sh(
        "QSlider::groove:horizontal {background: qlineargradient(x1:0, y1:0, x2:1, y2:0, %1);}"
        "QSlider::handle:horizontal { "
        "background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #b4b4b4, stop:1 #8f8f8f); "
        "border: 1px solid #5c5c5c; "
        "width: 18px;"
        "margin: -2px 0;"
        "border-radius: 3px;}");

    QString gr;
    QString t = "stop:%1 %2";
    for (int i = 0; i < cols.size(); i++) {
        gr += t.arg(static_cast<double>(i) / static_cast<double>(cols.size() - 1), 0, 'f', 3).arg(cols[i].name());
        if (i != cols.size() - 1) {
            gr += ",";
        }
    }
    sh = sh.arg(gr);
    sliders_[index]->setStyleSheet(sh);
}

void HSLWidget::setColour(QColor col)
{
    ignoreSpin_ = true;
    ignoreSlider_ = true;
    auto h = col.hslHue();
    if (h < 0)
        h = 0;

    sliders_[0]->setValue(h);
    spins_[0]->setValue(h);

    auto v = col.hslSaturation();
    sliders_[1]->setValue(v);
    spins_[1]->setValue(v);

    v = col.lightness();
    sliders_[2]->setValue(v);
    spins_[2]->setValue(v);

    ignoreSpin_ = false;
    ignoreSlider_ = false;

    adjustGrooves(-1);
}

QColor HSLWidget::currentColour() const
{
    return QColor::fromHsl(spins_[0]->value(), spins_[1]->value(), spins_[2]->value());
}

//==============================
//
// HCLabWidget
//
//===============================

HCLabWidget::HCLabWidget(QWidget* parent) :
    ColourSpaceSliderWidget(parent)
{
    auto grid = new QGridLayout(this);
    QStringList rgbNames = {tr("H:"), tr("C:"), tr("L:")};
    for (int i = 0; i < rgbNames.size(); i++) {
        auto label = new QLabel(rgbNames[i], this);
        grid->addWidget(label, i, 0);

        auto slider = new QSlider(this);
        slider->setOrientation(Qt::Horizontal);
        if (i == 0) {
            slider->setRange(0, 359);
        }
        else if (i == 1) {
            slider->setRange(0, 149);
        }
        else if (i == 2) {
            slider->setRange(0, 99);
        }
        connect(slider, SIGNAL(valueChanged(int)),
                this, SLOT(sliderChanged(int)));
        grid->addWidget(slider, i, 1);
        sliders_ << slider;

        auto spin = new QSpinBox(this);
        if (i == 0) {
            spin->setRange(0, 359);
        }
        else if (i == 1) {
            spin->setRange(0, 149);
        }
        else if (i == 2) {
            spin->setRange(0, 99);
        }

        grid->addWidget(spin, i, 2);
        connect(spin, SIGNAL(valueChanged(int)),
                this, SLOT(spinChanged(int)));
        grid->setColumnStretch(1, 1);
        spins_ << spin;
    }
    adjustGrooves(-1);
}

void HCLabWidget::adjustGroove(int index)
{
    auto col = currentColour();
    QList<QColor> cols;

    int h = 0, c = 0, l = 0;
    MvQPalette::toHclLab(col, h, c, l);

    if (index == 0) {
        for (int i = 0; i <= 360; i += 5) {
            cols << MvQPalette::fromHclLab(i, c, l);
        }
    }
    else if (index == 1) {
        for (int i = 0; i <= 150; i += 5) {
            cols << MvQPalette::fromHclLab(h, i, l);
        }
    }
    else if (index == 2) {
        for (int i = 0; i <= 100; i += 5) {
            cols << MvQPalette::fromHclLab(h, c, i);
        }
    }

    QString sh(
        "QSlider::groove:horizontal {background: qlineargradient(x1:0, y1:0, x2:1, y2:0, %1);}"
        "QSlider::handle:horizontal { "
        "background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #b4b4b4, stop:1 #8f8f8f); "
        "border: 1px solid #5c5c5c; "
        "width: 18px;"
        "margin: -2px 0;"
        "border-radius: 3px;}");

    QString gr;
    QString t = "stop:%1 %2";
    for (int i = 0; i < cols.size(); i++) {
        gr += t.arg(static_cast<double>(i) / static_cast<double>(cols.size() - 1), 0, 'f', 3).arg(cols[i].name());
        if (i != cols.size() - 1) {
            gr += ",";
        }
    }
    sh = sh.arg(gr);
    sliders_[index]->setStyleSheet(sh);
}

void HCLabWidget::setColour(QColor col)
{
    ignoreSpin_ = true;
    ignoreSlider_ = true;
    int h = 0, ch = 0, l = 0;
    MvQPalette::toHclLab(col, h, ch, l);

    qDebug() << "HCL=" << h << ch << l;

    sliders_[0]->setValue(h);
    spins_[0]->setValue(h);
    sliders_[1]->setValue(ch);
    spins_[1]->setValue(ch);
    sliders_[2]->setValue(l);
    spins_[2]->setValue(l);

    ignoreSpin_ = false;
    ignoreSlider_ = false;

    adjustGrooves(-1);
}

QColor HCLabWidget::currentColour() const
{
    return MvQPalette::fromHclLab(spins_[0]->value(), spins_[1]->value(), spins_[2]->value());
}

//==============================
//
// HCLuvWidget
//
//===============================

HCLuvWidget::HCLuvWidget(QWidget* parent) :
    ColourSpaceSliderWidget(parent)
{
    auto grid = new QGridLayout(this);
    QStringList rgbNames = {tr("H:"), tr("C:"), tr("L:")};
    for (int i = 0; i < rgbNames.size(); i++) {
        auto label = new QLabel(rgbNames[i], this);
        grid->addWidget(label, i, 0);

        auto slider = new QSlider(this);
        slider->setOrientation(Qt::Horizontal);
        if (i == 0) {
            slider->setRange(0, 359);
        }
        else if (i == 1) {
            slider->setRange(0, 149);
        }
        else if (i == 2) {
            slider->setRange(0, 99);
        }
        connect(slider, SIGNAL(valueChanged(int)),
                this, SLOT(sliderChanged(int)));
        grid->addWidget(slider, i, 1);
        sliders_ << slider;

        auto spin = new QSpinBox(this);
        if (i == 0) {
            spin->setRange(0, 359);
        }
        else if (i == 1) {
            spin->setRange(0, 149);
        }
        else if (i == 2) {
            spin->setRange(0, 99);
        }

        grid->addWidget(spin, i, 2);
        connect(spin, SIGNAL(valueChanged(int)),
                this, SLOT(spinChanged(int)));
        grid->setColumnStretch(1, 1);
        spins_ << spin;
    }
    adjustGrooves(-1);
}

void HCLuvWidget::adjustGroove(int index)
{
    auto col = currentColour();
    QList<QColor> cols;

    int h = 0, c = 0, l = 0;
    MvQPalette::toHclLuv(col, h, c, l);

    if (index == 0) {
        for (int i = 0; i <= 360; i += 5) {
            cols << MvQPalette::fromHclLuv(i, c, l);
        }
    }
    else if (index == 1) {
        for (int i = 0; i <= 150; i += 5) {
            cols << MvQPalette::fromHclLuv(h, i, l);
        }
    }
    else if (index == 2) {
        for (int i = 0; i <= 100; i += 5) {
            cols << MvQPalette::fromHclLuv(h, c, i);
        }
    }

    QString sh(
        "QSlider::groove:horizontal {background: qlineargradient(x1:0, y1:0, x2:1, y2:0, %1);}"
        "QSlider::handle:horizontal { "
        "background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #b4b4b4, stop:1 #8f8f8f); "
        "border: 1px solid #5c5c5c; "
        "width: 18px;"
        "margin: -2px 0;"
        "border-radius: 3px;}");

    QString gr;
    QString t = "stop:%1 %2";
    for (int i = 0; i < cols.size(); i++) {
        gr += t.arg(static_cast<double>(i) / static_cast<double>(cols.size() - 1), 0, 'f', 3).arg(cols[i].name());
        if (i != cols.size() - 1) {
            gr += ",";
        }
    }
    sh = sh.arg(gr);
    sliders_[index]->setStyleSheet(sh);
}

void HCLuvWidget::setColour(QColor col)
{
    ignoreSpin_ = true;
    ignoreSlider_ = true;
    int h = 0, ch = 0, l = 0;
    MvQPalette::toHclLuv(col, h, ch, l);

    qDebug() << "HCL=" << h << ch << l;

    sliders_[0]->setValue(h);
    spins_[0]->setValue(h);
    sliders_[1]->setValue(ch);
    spins_[1]->setValue(ch);
    sliders_[2]->setValue(l);
    spins_[2]->setValue(l);

    ignoreSpin_ = false;
    ignoreSlider_ = false;

    adjustGrooves(-1);
}

QColor HCLuvWidget::currentColour() const
{
    return MvQPalette::fromHclLuv(spins_[0]->value(), spins_[1]->value(), spins_[2]->value());
}

//==============================
//
// GreyScaleWidget
//
//===============================

GreyScaleWidget::GreyScaleWidget(QWidget* parent) :
    ColourSpaceSliderWidget(parent)
{
    auto grid = new QGridLayout(this);
    grid->setVerticalSpacing(2);

    QStringList rgbNames = {tr("Grey:")};
    for (int i = 0; i < rgbNames.size(); i++) {
        auto label = new QLabel(rgbNames[i], this);
        grid->addWidget(label, i, 0);

        auto slider = new QSlider(this);
        slider->setOrientation(Qt::Horizontal);
        slider->setRange(0, 255);

        connect(slider, SIGNAL(valueChanged(int)),
                this, SLOT(sliderChanged(int)));
        grid->addWidget(slider, i, 1);
        sliders_ << slider;

        auto spin = new QSpinBox(this);
        spin->setRange(0, 255);

        grid->addWidget(spin, i, 2);
        connect(spin, SIGNAL(valueChanged(int)),
                this, SLOT(spinChanged(int)));
        grid->setColumnStretch(1, 1);
        spins_ << spin;
    }
    // there is only one (static) groove
    adjustGroove(0);
}

void GreyScaleWidget::adjustGroove(int /*index*/)
{
    // this is a static groove so should only be adjusted on creation
    QColor c1, c2;
    c1 = QColor(0, 0, 0);
    c2 = QColor(255, 255, 255);

    QString sh(
        "QSlider::groove:horizontal {background: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 %1, stop:1 %2);}"
        "QSlider::handle:horizontal { "
        "background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #b4b4b4, stop:1 #8f8f8f); "
        "border: 1px solid #5c5c5c; "
        "width: 18px;"
        "margin: -2px 0;"
        "border-radius: 3px;}");

    sh = sh.arg(c1.name()).arg(c2.name());

    sliders_[0]->setStyleSheet(sh);
}

void GreyScaleWidget::setColour(QColor col)
{
    ignoreSpin_ = true;
    ignoreSlider_ = true;

    auto v = col.red();
    sliders_[0]->setValue(v);
    spins_[0]->setValue(v);

    ignoreSpin_ = false;
    ignoreSlider_ = false;

    // adjustGrooves(-1);
}

QColor GreyScaleWidget::currentColour() const
{
    auto v = spins_[0]->value();
    return {v, v, v};
}


//==============================
//
// AlphaSlider
//
//===============================

AlphaSlider::AlphaSlider(QWidget* parent) :
    QWidget(parent)
{
    setMouseTracking(true);
    setFixedWidth(grooveRect_.width() + 4);
    setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding));
}

void AlphaSlider::paintEvent(QPaintEvent*)
{
    auto painter = QPainter(this);
    auto pen = QPen(Qt::white);
    auto brush = QBrush(Qt::red);

    MvQPalette::paintAlphaBg(&painter, grooveRect_);

    auto c1 = QColor(colour_);
    auto c2 = QColor(colour_);
    c1.setAlpha(255);
    c2.setAlpha(0);
    auto grad = QLinearGradient(0, 0, 0, 1);
    grad.setColorAt(0, c1);
    grad.setColorAt(1, c2);
#if QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)
    grad.setCoordinateMode(QGradient::ObjectMode);
#else
    grad.setCoordinateMode(QGradient::ObjectBoundingMode);
#endif
    painter.fillRect(grooveRect_, QBrush(grad));

    grad = QLinearGradient(0, 0, 0, 1);
    grad.setColorAt(0, QColor("#b4b4b4"));
    grad.setColorAt(1, QColor("#8f8f8f"));
#if QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)
    grad.setCoordinateMode(QGradient::ObjectMode);
#else
    grad.setCoordinateMode(QGradient::ObjectBoundingMode);
#endif
    pen = QPen(QColor("#8f8f8f"));
    brush = QBrush(grad);
    painter.setPen(pen);
    painter.setBrush(brush);
    painter.drawRect(handlerRect_);
}

void AlphaSlider::resizeEvent(QResizeEvent*)
{
    auto marginX = (width() - grooveRect_.width()) / 2;
    auto marginY = 2;
    grooveRect_.setX(marginX);
    grooveRect_.setY(marginY);
    grooveRect_.setHeight(height() - 2 * marginY);
    adjustHandler();
    update();
}

void AlphaSlider::mousePressEvent(QMouseEvent* event)
{
    auto pos = event->pos();
    if (handlerRect_.contains(pos)) {
        dragStartPos_ = pos;
        dragStartAlpha_ = colour_.alpha();
        inDrag_ = true;
    }
    else {
        inDrag_ = false;
    }
}

void AlphaSlider::mouseMoveEvent(QMouseEvent* event)
{
    if (inDrag_ && event->buttons()) {
        auto dy = event->pos().y() - dragStartPos_.y();
        if (std::abs(dy) > 0) {
            moveHandlerBy(dy);
        }
    }
}

void AlphaSlider::mouseReleaseEvent(QMouseEvent*)
{
    inDrag_ = false;
}

void AlphaSlider::moveHandlerBy(int dy)
{
    auto da = int(255.0 * dy / grooveRect_.height());
    auto alpha = dragStartAlpha_ - da;
    alpha = std::max(std::min(alpha, 255), 0);
    if (alpha != colour_.alpha()) {
        colour_.setAlpha(alpha);
        adjustHandler();
        update();
        emit valueChanged(alpha);
    }
}

void AlphaSlider::adjustHandler()
{
    auto alpha = colour_.alpha();
    if (alpha >= 0 && alpha <= 255) {
        auto y = grooveRect_.y() + grooveRect_.height() - int(grooveRect_.height() * alpha / 255.0);

        handlerRect_.moveTop(y - handlerRect_.height() / 2);
        if (handlerRect_.y() < grooveRect_.y()) {
            handlerRect_.moveTop(grooveRect_.y());
        }
        else if (handlerRect_.y() + handlerRect_.height() > grooveRect_.y() + grooveRect_.height()) {
            handlerRect_.moveTop(grooveRect_.y() + grooveRect_.height() - handlerRect_.height());
        }
    }
}

int AlphaSlider::value() const
{
    return colour_.alpha();
}

void AlphaSlider::setColour(QColor col)
{
    colour_ = QColor(col.red(), col.green(), col.blue(), col.alpha());
    adjustHandler();
    update();
}

void AlphaSlider::setValue(int alpha)
{
    auto col = colour_;
    col.setAlpha(alpha);
    setColour(col);
}

//==============================
//
// AlphaWidget
//
//===============================

AlphaWidget::AlphaWidget(QWidget* parent, QVBoxLayout* layout) :
    QWidget(parent)
{
    QVBoxLayout* grid = nullptr;
    if (layout) {
        grid = layout;
    }
    else {
        grid = new QVBoxLayout(this);
        grid->setAlignment(grid, Qt::AlignHCenter);
    }

    auto label = new QLabel("Alpha", this);
    label->setStyleSheet("QLabel {background: transparent;}");
    grid->addWidget(label);
    grid->setAlignment(label, Qt::AlignHCenter);

    slider_ = new AlphaSlider(this);

    connect(slider_, SIGNAL(valueChanged(int)),
            this, SLOT(sliderChanged(int)));

    grid->addWidget(slider_);
    grid->setAlignment(slider_, Qt::AlignHCenter);

    spin_ = new QSpinBox(this);
    spin_->setRange(0, 255);
    grid->addWidget(spin_);
    grid->setAlignment(spin_, Qt::AlignHCenter);

    connect(spin_, SIGNAL(valueChanged(int)),
            this, SLOT(spinChanged(int)));

    if (!layout) {
        grid->addStretch(1);
    }
}

void AlphaWidget::sliderChanged(int val)
{
    ignoreSpin_ = true;
    spin_->setValue(val);
    ignoreSpin_ = false;
    emit valueChanged(currentValue());
}

void AlphaWidget::spinChanged(int val)
{
    ignoreSlider_ = true;
    slider_->setValue(val);
    ignoreSlider_ = false;
    emit valueChanged(currentValue());
}

void AlphaWidget::setColour(QColor col)
{
    colour_ = QColor(col);
    ignoreSpin_ = true;
    ignoreSlider_ = true;
    slider_->setColour(colour_);
    spin_->setValue(colour_.alpha());
    ignoreSpin_ = false;
    ignoreSlider_ = false;
}

void AlphaWidget::setValue(int value)
{
    ignoreSpin_ = true;
    ignoreSlider_ = true;
    slider_->setValue(value);
    spin_->setValue(value);
    ignoreSpin_ = false;
    ignoreSlider_ = false;
}

int AlphaWidget::currentValue() const
{
    return spin_->value();
}

//==============================
//
// ColourTextWidget
//
//===============================

ColourTextWidget::ColourTextWidget(QWidget* parent) :
    QWidget(parent)
{
    auto grid = new QGridLayout(this);
    grid->setVerticalSpacing(2);

    int row = 0;
    auto label = new QLabel(tr("HTML:"), this);
    htmlEdit_ = new QLineEdit(this);
    grid->addWidget(label, row, 0);
    grid->addWidget(htmlEdit_, row, 1);
    row++;

    auto font = QFont();
    font.setPointSize(font.pointSize() - 2);

    QMap<LabelType, QString> labelDef = {{RgbInt, "RGB int"}, {RgbFloat, "RGB float"}, {HslInt, "HSL int"}, {HslFloat, "HSL float"}};
    QMapIterator<LabelType, QString> it(labelDef);
    while (it.hasNext()) {
        it.next();
        auto txt = it.value();
        label = new QLabel(txt + ":", this);
        label->setFont(font);
        auto w = new QLabel(this);
        w->setFont(font);
        w->setProperty("type", "colour");
        w->setTextInteractionFlags(Qt::TextSelectableByMouse);
        grid->addWidget(label, row, 0);
        grid->addWidget(w, row, 1);
        row++;
        labels_[it.key()] = w;
    }

    connect(htmlEdit_, SIGNAL(textChanged(QString)),
            this, SLOT(htmlChanged(QString)));
}

void ColourTextWidget::setColour(QColor col)
{
    ignoreHtmlEdit_ = true;
    htmlEdit_->setText(col.name());
    setLabels(col);
    ignoreHtmlEdit_ = false;
}

void ColourTextWidget::setLabels(QColor col)
{
    labels_[RgbInt]->setText(MvQPalette::toRgbIntString(col).toLower());
    labels_[RgbFloat]->setText(MvQPalette::toRgbString(col).toLower());
    labels_[HslInt]->setText(MvQPalette::toHslIntString(col).toLower());
    labels_[HslFloat]->setText(MvQPalette::toHslString(col).toLower());
}

void ColourTextWidget::htmlChanged(QString txt)
{
    if (!ignoreHtmlEdit_) {
        auto col = QColor(txt);
        if (col.isValid()) {
            emit colourChanged(col);
        }
        setLabels(col);
    }
}

//======================================
//
// MvQColourTextControl
//
//======================================

MvQColourTextControl::MvQColourTextControl(QGridLayout* grid, QWidget* parent) :
    QObject(parent)
{
    // html
    auto label = new QLabel("HTML:", parent);
    htmlEdit_ = new QLineEdit(parent);

    auto copyTb = new QToolButton(parent);
    copyTb->setToolTip("Copy to clipboard");
    copyTb->setIcon(QPixmap(":/editor/copy_to_clipboard.svg"));
    copyTb->setAutoRaise(true);
    connect(copyTb, SIGNAL(clicked()),
            this, SLOT(copyHtml()));

    grid->addWidget(label, 0, 0);
    grid->addWidget(htmlEdit_, 0, 1);
    grid->addWidget(copyTb, 0, 2);

    // metview
    label = new QLabel("Metview:", parent);
    mvEdit_ = new QLineEdit(parent);

    copyTb = new QToolButton(parent);
    copyTb->setToolTip("Copy to clipboard");
    copyTb->setIcon(QPixmap(":/editor/copy_to_clipboard.svg"));
    copyTb->setAutoRaise(true);
    connect(copyTb, SIGNAL(clicked()),
            this, SLOT(copyMv()));

    grid->addWidget(label, 1, 0);
    grid->addWidget(mvEdit_, 1, 1);
    grid->addWidget(copyTb, 1, 2);

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

    connect(mvEdit_, SIGNAL(textChanged(QString)),
            this, SLOT(mvChanged(QString)));
}

void MvQColourTextControl::copyHtml()
{
    MvQ::toClipboard(htmlEdit_->text());
}

void MvQColourTextControl::copyMv()
{
    MvQ::toClipboard(mvEdit_->text());
}

void MvQColourTextControl::htmlChanged(QString txt)
{
    if (!ignoreHtmlEdit_) {
        auto col = QColor(txt);
        if (col.isValid()) {
            emit colourChanged(col);
        }
        adjustMvEdit(col);
    }
}

void MvQColourTextControl::mvChanged(QString txt)
{
    if (!ignoreMvEdit_) {
        auto col = MvQPalette::magics(txt.toStdString());
        if (col.isValid()) {
            emit colourChanged(col);
        }
        adjustHtmlEdit(col);
    }
}

void MvQColourTextControl::adjustHtmlEdit(QColor col)
{
    ignoreHtmlEdit_ = true;
    htmlEdit_->setText(col.name());
    ignoreHtmlEdit_ = false;
}

void MvQColourTextControl::adjustMvEdit(QColor col)
{
    ignoreMvEdit_ = true;
    mvEdit_->setText(MvQPalette::toRgbString(col).toLower());
    ignoreMvEdit_ = false;
}

void MvQColourTextControl::setColour(QColor col)
{
    adjustHtmlEdit(col);
    adjustMvEdit(col);
}


MvQColourWheelControl::MvQColourWheelControl(QWidget* parent) :
    QWidget(parent)
{
    auto tbFont = QFont();
    tbFont.setPointSize(tbFont.pointSize() - 3);

    auto vb = new QVBoxLayout(this);

    // buttons at the bottom
    auto hb = new QHBoxLayout();
    hb->setSpacing(1);
    bGroup_ = new QButtonGroup(this);

    hb->addStretch(1);

#ifdef MV_UI_USE_HCL_
    QStringList names = {"HSL", "HCL"};
    for (int i = 0; i < names.size(); i++) {
        auto tb = new QToolButton(this);
        tb->setText(names[i]);
        tb->setFont(tbFont);
        tb->setCheckable(true);
        bGroup_->addButton(tb, i);
        hb->addWidget(tb);
    }
    hb->addStretch(1);
#endif

    // colour wheels on the right
    stacked_ = new QStackedWidget(this);
    wheels_.push_back(new MvQHslWheel(128, this));
    stacked_->addWidget(wheels_[0]);
#ifdef MV_UI_USE_HCL_
    wheels_.push_back(new MvQHclWheel(128, this));
    stacked_->addWidget(wheels_[1]);
#endif
    vb->addWidget(stacked_);

    vb->addLayout(hb);

#ifdef MV_UI_USE_HCL_
    connect(bGroup_, SIGNAL(buttonClicked(QAbstractButton*)),
            this, SLOT(buttonSelection(QAbstractButton*)));

    bGroup_->button(0)->setChecked(true);
#else
    stacked_->setCurrentIndex(0);
#endif

    for (auto w : wheels_) {
        connect(w, SIGNAL(hslSelected(int, int, int)),
                this, SIGNAL(hslSelected(int, int, int)));

        connect(w, SIGNAL(colourSelected(QColor)),
                this, SIGNAL(colourSelected(QColor)));

        connect(w, SIGNAL(dragFinished()),
                this, SIGNAL(dragFinished()));
    }
}

MvQColourWheel* MvQColourWheelControl::currentWheel() const
{
    int idx = stacked_->currentIndex();
    if (idx != -1) {
        return wheels_[idx];
    }
    return nullptr;
}

void MvQColourWheelControl::initColour(QColor col)
{
    if (auto cw = currentWheel()) {
        cw->initColour(col);
    }
}

void MvQColourWheelControl::setColour(QColor col)
{
    if (auto cw = currentWheel()) {
        cw->slotSetColour(col);
    }
}

void MvQColourWheelControl::buttonSelection(QAbstractButton* b)
{
    int id = bGroup_->id(b);
    if (id != -1) {
        stacked_->setCurrentIndex(id);
    }
}

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

MvQColourSelectionWidget::MvQColourSelectionWidget(QWidget* parent, bool embedded) :
    QWidget(parent)
{
    auto font = QFont();
    // font.setPointSize(font.pointSize() - 1);
    auto fm = QFontMetrics(font);

    auto hb = new QHBoxLayout(this);
    hb->setContentsMargins(10, 2, 2, 2);
    hb->setSpacing(10);

    // WARN: the top of the left area (abot 70-100 pixels) is not clickable on MacOS.
    // Several solutions were tried but none of them worked!
    if (embedded) {
        hb->addSpacing(80);
    }

    auto spinTab = new QTabWidget(this);
    spinTab->setIconSize({16, 16});

    wheel_ = new MvQColourWheelControl(this);
    spinTab->addTab(wheel_, QIcon(QPixmap(":/editor/colour_wheel.svg")), "");
    spinTab->setTabToolTip(spinTab->count() - 1, tr("Colour wheel"));

    grid_ = new MvQColourGrid(128, this);
    auto gridHolderW = new QWidget(this);
    auto gridHolderLayout = new QVBoxLayout(gridHolderW);
    gridHolderLayout->setContentsMargins(3, 3, 3, 3);
    gridHolderLayout->setSpacing(2);
    gridHolderLayout->addWidget(grid_, 1);
    spinTab->addTab(gridHolderW, QIcon(QPixmap(":/editor/colour_grid.svg")), "");
    spinTab->setTabToolTip(spinTab->count() - 1, tr("Grid of named Magics colours"));

#if 0
    // grid
    QList<MvQColourItem> items;
    //auto shades = {-70, -50, -20, -5, 15, 40};
    items.clear();

    std::vector<int> shades = {0, 20, 40, 60, 80, 100};
    items << MvQColourItem(QColor(255,255,255), "grey", shades);


    items << MvQColourItem(QColor(210, 0, 0), "red", {-120, -80, -50, 0, 30, 60});
    items << MvQColourItem(QColor(255, 192, 4), "orange", {-70, -50, -20, 5, 20, 45});
    items << MvQColourItem(QColor(255, 255, 0), "yellow", {-70, -50, -20, 8, 20, 45});
    items << MvQColourItem(QColor(146, 208, 81), "light green", {-80, -50, -20, -5, 15, 45});
    items << MvQColourItem(QColor(7, 176, 80), "green", {-120, -50, -20, -5, 15, 50});
    items << MvQColourItem(QColor(3, 175, 240), "cyan", {-95, -70, -30, 0, 20, 50});
    items << MvQColourItem(QColor(55, 113, 200), "mid-blue", {-95, -70, -50, -30, 0, 30});
    items << MvQColourItem(QColor(0, 65, 219), "blue", {-95, -70, -50, -30, 0, 30});
    items << MvQColourItem(QColor(147, 112, 219), "violet", {-60, -44, -29, -10, 10, 50});
    items << MvQColourItem(QColor(255, 1, 255), "magenta", {-85, -74, -45, 5, 20, 50});

    MvQCompactColourGrid *grid1 = new MvQCompactColourGrid(180,items,MvQCompactColourGrid::RowMode, this);
    spinTab->addTab(grid1, "HH");
#endif

    hb->addWidget(spinTab);

    auto vb = new QVBoxLayout();
    vb->setSpacing(3);
    hb->addLayout(vb);

    tab_ = new QTabWidget(this);
    vb->addWidget(tab_);
    QString t;
    for (int i = 0; i < 32; i++) {
        t += "A";
    }
    tab_->setFixedWidth(MvQ::textWidth(fm, t));

    rgb_ = new RGBWidget(this);
    tab_->addTab(rgb_, tr("RGB"));
    rgbTabIndex_ = tab_->count() - 1;

    hsl_ = new HSLWidget(this);
    tab_->addTab(hsl_, tr("HSL"));
    hslTabIndex_ = tab_->count() - 1;

#ifdef MV_UI_USE_HCL_
    hclUv_ = new HCLuvWidget(this);
    tab_->addTab(hclUv_, tr("HCL"));
    hclUvTabIndex_ = tab_->count() - 1;
#endif

    greyW_ = new GreyScaleWidget(this);
    tab_->addTab(greyW_, tr("Grey"));
    greyTabIndex_ = tab_->count() - 1;

    // text grid
    auto textGrid = new QGridLayout();
    vb->addLayout(textGrid);
    textControl_ = new MvQColourTextControl(textGrid, this);

    // alpha
    auto alphaVb = new QVBoxLayout();
    alphaVb->setSpacing(2);
    alphaVb->setAlignment(Qt::AlignHCenter);
    alphaW_ = new AlphaWidget(this, alphaVb);
    hb->addLayout(alphaVb);

    hb->addStretch(1);


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

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

    connect(grid_, SIGNAL(selected(QColor)),
            this, SLOT(gridChangedColour(QColor)));

    connect(rgb_, SIGNAL(colourChanged(QColor)),
            this, SLOT(rgbChanged(QColor)));

    connect(hsl_, SIGNAL(colourChanged(QColor)),
            this, SLOT(hslChanged(QColor)));

    //    connect(hclAb_, SIGNAL(colourChanged(QColor)),
    //            this, SLOT(hclAbChanged(QColor)));

#ifdef MV_UI_USE_HCL_
    connect(hclUv_, SIGNAL(colourChanged(QColor)),
            this, SLOT(hclUvChanged(QColor)));
#endif

    connect(greyW_, SIGNAL(colourChanged(QColor)),
            this, SLOT(greyChanged(QColor)));

    connect(alphaW_, SIGNAL(valueChanged(int)),
            this, SLOT(alphaChanged(int)));

    connect(textControl_, SIGNAL(colourChanged(QColor)),
            this, SLOT(textChanged(QColor)));

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

    connect(tab_, SIGNAL(currentChanged(int)),
            this, SLOT(tabIndexChanged(int)));
}

void MvQColourSelectionWidget::setColour(QColor col)
{
    alphaW_->setColour(col);

    // 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::buttonSelection(QAbstractButton* b)
{
    int id = bGroup_->id(b);
    if (id != -1) {
        stacked_->setCurrentIndex(id);
    }
}

void MvQColourSelectionWidget::tabIndexChanged(int /*index*/)
{
    // adjustWheel(currentColour_);
    adjustRgb(currentColour_);
    adjustHsl(currentColour_);
    adjustHclUv(currentColour_);
    adjustGrey(currentColour_);
    adjustText(currentColour_);
}

void MvQColourSelectionWidget::adjustWheel(QColor col)
{
    wheel_->setColour(col);
}

void MvQColourSelectionWidget::adjustRgb(QColor col)
{
    if (tab_->currentIndex() == rgbTabIndex_) {
        rgb_->setColour(col);
    }
}

void MvQColourSelectionWidget::adjustHsl(QColor col)
{
    if (tab_->currentIndex() == hslTabIndex_) {
        hsl_->setColour(col);
    }
}

void MvQColourSelectionWidget::adjustHclUv(QColor col)
{
#ifdef MV_UI_USE_HCL_
    if (tab_->currentIndex() == hclUvTabIndex_) {
        hclUv_->setColour(col);
    }
#endif
}

void MvQColourSelectionWidget::adjustGrey(QColor col)
{
    if (tab_->currentIndex() == greyTabIndex_) {
        greyW_->setColour(col);
    }
}

void MvQColourSelectionWidget::adjustText(QColor col)
{
    textControl_->setColour(col);
}

void MvQColourSelectionWidget::slotWheelChangedHsl(int hue, int sat, int lgt)
{
    QColor col = QColor::fromHsl(hue, sat, lgt);
    slotWheelChangedColour(col);
}

void MvQColourSelectionWidget::slotWheelChangedColour(QColor col)
{
    col.setAlpha(alphaW_->currentValue());
    adjustRgb(col);
    adjustHsl(col);
    adjustHclUv(col);
    adjustGrey(col);
    alphaW_->setColour(col);
    adjustText(col);
    setCurrentColour(col);
}

void MvQColourSelectionWidget::gridChangedColour(QColor col)
{
    col.setAlpha(alphaW_->currentValue());
    adjustWheel(col);
    adjustRgb(col);
    adjustHsl(col);
    adjustHclUv(col);
    adjustGrey(col);
    alphaW_->setColour(col);
    adjustText(col);
    setCurrentColour(col);
}

void MvQColourSelectionWidget::rgbChanged(QColor col)
{
    col.setAlpha(alphaW_->currentValue());
    adjustHsl(col);
    adjustHclUv(col);
    adjustGrey(col);
    alphaW_->setColour(col);
    adjustText(col);
    adjustWheel(col);
    setCurrentColour(col);
}

void MvQColourSelectionWidget::hslChanged(QColor col)
{
    col.setAlpha(alphaW_->currentValue());
    adjustRgb(col);
    adjustHclUv(col);
    adjustGrey(col);
    alphaW_->setColour(col);
    adjustText(col);
    adjustWheel(col);
    setCurrentColour(col);
}

void MvQColourSelectionWidget::hclAbChanged(QColor col)
{
    col.setAlpha(alphaW_->currentValue());
    adjustRgb(col);
    adjustHsl(col);
    adjustHclUv(col);
    adjustGrey(col);
    alphaW_->setColour(col);
    adjustText(col);
    adjustWheel(col);
    setCurrentColour(col);
}

void MvQColourSelectionWidget::hclUvChanged(QColor col)
{
#ifdef MV_UI_USE_HCL_
    col.setAlpha(alphaW_->currentValue());
    adjustRgb(col);
    adjustHsl(col);
    adjustGrey(col);
    alphaW_->setColour(col);
    adjustText(col);
    adjustWheel(col);
    setCurrentColour(col);
#endif
}

void MvQColourSelectionWidget::greyChanged(QColor col)
{
    col.setAlpha(alphaW_->currentValue());
    adjustRgb(col);
    adjustHsl(col);
    adjustHclUv(col);
    alphaW_->setColour(col);
    adjustText(col);
    adjustWheel(col);
    setCurrentColour(col);
}

void MvQColourSelectionWidget::alphaChanged(int value)
{
    auto col = currentColour_;
    col.setAlpha(value);
    adjustText(col);
    setCurrentColour(col);
}

void MvQColourSelectionWidget::textChanged(QColor col)
{
    col.setAlpha(alphaW_->currentValue());
    adjustRgb(col);
    adjustHsl(col);
    adjustHclUv(col);
    adjustGrey(col);
    alphaW_->setColour(col);
    adjustWheel(col);
    setCurrentColour(col);
}

void MvQColourSelectionWidget::paintEvent(QPaintEvent*)
{
    QStyleOption opt;
    opt.initFrom(this);
    QPainter p(this);
    style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
}

//===========================================
//
// MvQColourCombo
//
//===========================================

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);

    auto* 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()));

    auto* 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 std::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 std::vector<std::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();

    std::vector<std::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)
{
    auto* vb = new QVBoxLayout(this);
    vb->setContentsMargins(0, 0, 0, 0);
    vb->setSpacing(1);

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

    selector_ = new MvQColourSelectionWidget(this, false);
    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()));
}
